things-api 0.9.0 → 0.11.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 +57 -12
- package/dist/audit/log.js +56 -2
- package/dist/audit/log.js.map +1 -1
- package/dist/audit/schema.d.ts +26 -8
- package/dist/audit/schema.js +42 -1
- package/dist/audit/schema.js.map +1 -1
- package/dist/cli/clock.d.ts +8 -0
- package/dist/cli/clock.js +24 -0
- package/dist/cli/clock.js.map +1 -0
- package/dist/cli/commands/area.d.ts +26 -3
- package/dist/cli/commands/area.js +129 -75
- package/dist/cli/commands/area.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +1 -1
- package/dist/cli/commands/doctor.js +44 -5
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/install-skill.d.ts +45 -0
- package/dist/cli/commands/install-skill.js +207 -0
- package/dist/cli/commands/install-skill.js.map +1 -0
- package/dist/cli/commands/mcp.js +23 -4
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/project.d.ts +18 -1
- package/dist/cli/commands/project.js +74 -35
- package/dist/cli/commands/project.js.map +1 -1
- package/dist/cli/commands/reads.d.ts +15 -2
- package/dist/cli/commands/reads.js +459 -165
- package/dist/cli/commands/reads.js.map +1 -1
- package/dist/cli/commands/repeat-flags.d.ts +20 -0
- package/dist/cli/commands/repeat-flags.js +76 -0
- package/dist/cli/commands/repeat-flags.js.map +1 -0
- package/dist/cli/commands/setup.js +8 -4
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/show.js +53 -30
- package/dist/cli/commands/show.js.map +1 -1
- package/dist/cli/commands/todo.d.ts +1 -1
- package/dist/cli/commands/todo.js +20 -13
- package/dist/cli/commands/todo.js.map +1 -1
- package/dist/cli/commands/writes.js +388 -161
- package/dist/cli/commands/writes.js.map +1 -1
- package/dist/cli/did-you-mean.d.ts +1 -1
- package/dist/cli/excess-args.d.ts +15 -0
- package/dist/cli/excess-args.js +51 -0
- package/dist/cli/excess-args.js.map +1 -0
- package/dist/cli/glyphs.d.ts +55 -2
- package/dist/cli/glyphs.js +114 -21
- package/dist/cli/glyphs.js.map +1 -1
- package/dist/cli/help.d.ts +54 -0
- package/dist/cli/help.js +405 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/main.js +64 -26
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/move-hint.d.ts +45 -0
- package/dist/cli/move-hint.js +196 -0
- package/dist/cli/move-hint.js.map +1 -0
- package/dist/cli/period.d.ts +9 -8
- package/dist/cli/period.js +56 -30
- package/dist/cli/period.js.map +1 -1
- package/dist/cli/read-driver.d.ts +26 -19
- package/dist/cli/read-driver.js +77 -33
- package/dist/cli/read-driver.js.map +1 -1
- package/dist/cli/render.d.ts +92 -28
- package/dist/cli/render.js +333 -103
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/resolve-invocation.d.ts +42 -4
- package/dist/cli/resolve-invocation.js +97 -14
- package/dist/cli/resolve-invocation.js.map +1 -1
- package/dist/cli/skill-check.d.ts +21 -0
- package/dist/cli/skill-check.js +82 -0
- package/dist/cli/skill-check.js.map +1 -0
- package/dist/cli/skill.d.ts +62 -0
- package/dist/cli/skill.js +142 -0
- package/dist/cli/skill.js.map +1 -0
- package/dist/cli/tag-filters.d.ts +63 -0
- package/dist/cli/tag-filters.js +59 -0
- package/dist/cli/tag-filters.js.map +1 -0
- package/dist/cli/verb-hint.d.ts +25 -0
- package/dist/cli/verb-hint.js +163 -0
- package/dist/cli/verb-hint.js.map +1 -0
- package/dist/cli/version.d.ts +4 -0
- package/dist/cli/version.js +18 -0
- package/dist/cli/version.js.map +1 -0
- package/dist/cli/width.d.ts +135 -0
- package/dist/cli/width.js +313 -0
- package/dist/cli/width.js.map +1 -0
- package/dist/client.d.ts +160 -24
- package/dist/client.js +132 -19
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +11 -0
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/contracts.d.ts +97 -17
- package/dist/contracts.js +44 -1
- package/dist/contracts.js.map +1 -1
- package/dist/db/fingerprint.d.ts +12 -0
- package/dist/db/fingerprint.js +15 -1
- package/dist/db/fingerprint.js.map +1 -1
- package/dist/db/locate.js +1 -1
- package/dist/db/locate.js.map +1 -1
- package/dist/diagnose.d.ts +63 -0
- package/dist/diagnose.js +38 -1
- package/dist/diagnose.js.map +1 -1
- package/dist/index.d.ts +36 -5
- package/dist/index.js +38 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +9 -1
- package/dist/mcp/server.js +1038 -436
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/clock.d.ts +61 -0
- package/dist/model/clock.js +71 -0
- package/dist/model/clock.js.map +1 -0
- package/dist/model/dates.d.ts +26 -3
- package/dist/model/dates.js +84 -3
- package/dist/model/dates.js.map +1 -1
- package/dist/model/entities.d.ts +48 -10
- package/dist/model/entities.js.map +1 -1
- package/dist/model/mappers.d.ts +9 -2
- package/dist/model/mappers.js +32 -9
- package/dist/model/mappers.js.map +1 -1
- package/dist/model/serialize.d.ts +28 -0
- package/dist/model/serialize.js +80 -0
- package/dist/model/serialize.js.map +1 -0
- package/dist/model/when-sugar.d.ts +47 -0
- package/dist/model/when-sugar.js +45 -0
- package/dist/model/when-sugar.js.map +1 -0
- package/dist/read/area-view.d.ts +8 -1
- package/dist/read/area-view.js +55 -17
- package/dist/read/area-view.js.map +1 -1
- package/dist/read/detail.d.ts +1 -1
- package/dist/read/detail.js +11 -4
- package/dist/read/detail.js.map +1 -1
- package/dist/read/filter-contract.d.ts +117 -0
- package/dist/read/filter-contract.js +78 -0
- package/dist/read/filter-contract.js.map +1 -0
- package/dist/read/log-boundary.d.ts +1 -1
- package/dist/read/log-boundary.js +13 -3
- package/dist/read/log-boundary.js.map +1 -1
- package/dist/read/predicates.d.ts +30 -0
- package/dist/read/predicates.js +30 -0
- package/dist/read/predicates.js.map +1 -1
- package/dist/read/project-view.d.ts +12 -1
- package/dist/read/project-view.js +45 -12
- package/dist/read/project-view.js.map +1 -1
- package/dist/read/queries.d.ts +152 -7
- package/dist/read/queries.js +277 -36
- package/dist/read/queries.js.map +1 -1
- package/dist/read/sections.d.ts +51 -0
- package/dist/read/sections.js +37 -0
- package/dist/read/sections.js.map +1 -0
- package/dist/read/sidebar-order.js +2 -1
- package/dist/read/sidebar-order.js.map +1 -1
- package/dist/read/snapshot.d.ts +1 -1
- package/dist/read/snapshot.js +9 -2
- package/dist/read/snapshot.js.map +1 -1
- package/dist/read/tags.d.ts +27 -3
- package/dist/read/tags.js +83 -12
- package/dist/read/tags.js.map +1 -1
- package/dist/read/truncation.d.ts +71 -0
- package/dist/read/{pagination.js → truncation.js} +103 -94
- package/dist/read/truncation.js.map +1 -0
- package/dist/read/views.d.ts +147 -22
- package/dist/read/views.js +282 -80
- package/dist/read/views.js.map +1 -1
- package/dist/surface-copy.d.ts +19 -0
- package/dist/surface-copy.js +32 -0
- package/dist/surface-copy.js.map +1 -1
- package/dist/sync-health.d.ts +78 -0
- package/dist/sync-health.js +312 -0
- package/dist/sync-health.js.map +1 -0
- package/dist/write/accessibility-probe.d.ts +12 -0
- package/dist/write/accessibility-probe.js +71 -0
- package/dist/write/accessibility-probe.js.map +1 -0
- package/dist/write/automation-probe.d.ts +8 -1
- package/dist/write/automation-probe.js +16 -1
- package/dist/write/automation-probe.js.map +1 -1
- package/dist/write/batch.js +5 -2
- package/dist/write/batch.js.map +1 -1
- package/dist/write/capabilities.d.ts +8 -0
- package/dist/write/capabilities.js +13 -6
- package/dist/write/capabilities.js.map +1 -1
- package/dist/write/commands.d.ts +3 -1
- package/dist/write/commands.js +602 -49
- package/dist/write/commands.js.map +1 -1
- package/dist/write/edit-checklist.js +3 -2
- package/dist/write/edit-checklist.js.map +1 -1
- package/dist/write/guards.d.ts +2 -7
- package/dist/write/guards.js +87 -6
- package/dist/write/guards.js.map +1 -1
- package/dist/write/heading.js +2 -0
- package/dist/write/heading.js.map +1 -1
- package/dist/write/lock.d.ts +40 -2
- package/dist/write/lock.js +91 -14
- package/dist/write/lock.js.map +1 -1
- package/dist/write/make-repeating-project.d.ts +4 -0
- package/dist/write/make-repeating-project.js +253 -0
- package/dist/write/make-repeating-project.js.map +1 -0
- package/dist/write/operations.d.ts +145 -1
- package/dist/write/operations.js +48 -0
- package/dist/write/operations.js.map +1 -1
- package/dist/write/pipeline.d.ts +73 -2
- package/dist/write/pipeline.js +218 -43
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/planner.js +8 -1
- package/dist/write/planner.js.map +1 -1
- package/dist/write/pre-state.d.ts +105 -3
- package/dist/write/pre-state.js +144 -3
- package/dist/write/pre-state.js.map +1 -1
- package/dist/write/recurrence-rule-blob.d.ts +65 -0
- package/dist/write/recurrence-rule-blob.js +150 -0
- package/dist/write/recurrence-rule-blob.js.map +1 -0
- package/dist/write/reopen.js +3 -2
- package/dist/write/reopen.js.map +1 -1
- package/dist/write/reorder.js +27 -17
- package/dist/write/reorder.js.map +1 -1
- package/dist/write/repeat-rule.d.ts +22 -0
- package/dist/write/repeat-rule.js +258 -0
- package/dist/write/repeat-rule.js.map +1 -0
- package/dist/write/reversibility.js +49 -0
- package/dist/write/reversibility.js.map +1 -1
- package/dist/write/tag-refs.d.ts +47 -0
- package/dist/write/tag-refs.js +126 -0
- package/dist/write/tag-refs.js.map +1 -0
- package/dist/write/undo.d.ts +74 -5
- package/dist/write/undo.js +493 -80
- package/dist/write/undo.js.map +1 -1
- package/dist/write/vectors/registry.d.ts +15 -1
- package/dist/write/vectors/registry.js +65 -2
- package/dist/write/vectors/registry.js.map +1 -1
- package/dist/write/vectors/simulator.d.ts +44 -0
- package/dist/write/vectors/simulator.js +1136 -0
- package/dist/write/vectors/simulator.js.map +1 -0
- package/dist/write/vectors/types.d.ts +154 -3
- package/dist/write/vectors/ui-certification.d.ts +54 -0
- package/dist/write/vectors/ui-certification.js +43 -0
- package/dist/write/vectors/ui-certification.js.map +1 -0
- package/dist/write/vectors/ui-drag.d.ts +188 -0
- package/dist/write/vectors/ui-drag.js +1095 -0
- package/dist/write/vectors/ui-drag.js.map +1 -0
- package/dist/write/vectors/ui-recipes.d.ts +83 -0
- package/dist/write/vectors/ui-recipes.js +610 -0
- package/dist/write/vectors/ui-recipes.js.map +1 -0
- package/dist/write/vectors/ui.d.ts +152 -0
- package/dist/write/vectors/ui.js +704 -0
- package/dist/write/vectors/ui.js.map +1 -0
- package/dist/write/verify/delta.d.ts +141 -4
- package/dist/write/verify/delta.js +199 -21
- package/dist/write/verify/delta.js.map +1 -1
- package/dist/write/verify/poller.d.ts +7 -1
- package/dist/write/verify/poller.js +21 -1
- package/dist/write/verify/poller.js.map +1 -1
- package/package.json +18 -3
- package/skills/things-cli/SKILL.md +53 -0
- package/skills/things-cli/references/contracts.md +41 -0
- package/skills/things-cli/references/gui.md +11 -0
- package/skills/things-cli/references/model.md +51 -0
- package/dist/cli/exit-codes.d.ts +0 -26
- package/dist/cli/exit-codes.js +0 -26
- package/dist/cli/exit-codes.js.map +0 -1
- package/dist/cli/output.d.ts +0 -42
- package/dist/cli/output.js +0 -16
- package/dist/cli/output.js.map +0 -1
- package/dist/read/pagination.d.ts +0 -104
- package/dist/read/pagination.js.map +0 -1
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
import { saveConfigKey } from "../../
|
|
4
|
-
import {
|
|
5
|
-
import { ThingsDbOpenError } from "../../db/connection.js";
|
|
6
|
-
import { capabilitiesTable } from "../../write/capabilities.js";
|
|
7
|
-
import { outcomeFailed } from "../../write/batch.js";
|
|
8
|
-
import { BOUNCE_MAX_ITEMS } from "../../write/reorder.js";
|
|
9
|
-
import { errorEnvelope, ExitCode, okEnvelope } from "../../contracts.js";
|
|
2
|
+
import { addRepeatRuleFlags, repeatRuleFlagsFromOpts } from "./repeat-flags.js";
|
|
3
|
+
import { aggregateExitCode, blockedCode, BOUNCE_MAX_ITEMS, capabilitiesTable, ClockError, errorEnvelope, ExitCode, okEnvelope, openThings, outcomeFailed, ReferenceResolutionError, saveConfigKey, splitWhenSugar, ThingsDbNotFoundError, ThingsDbOpenError, verifyFailedCode, } from "../../index.js";
|
|
4
|
+
import { usageError } from "../read-driver.js";
|
|
10
5
|
function addWriteFlags(cmd) {
|
|
11
6
|
return cmd
|
|
12
7
|
.option("--json", "emit versioned JSON envelope on stdout")
|
|
13
8
|
.option("--db <path>", "explicit database path")
|
|
14
9
|
.option("--dry-run", "preview the planned change and its expected effect; nothing executes")
|
|
15
|
-
.option("--vector <id>", "force how the change is delivered: url-scheme | applescript | shortcuts")
|
|
10
|
+
.option("--vector <id>", "force how the change is delivered: url-scheme | applescript | shortcuts | ui")
|
|
16
11
|
.option("--allow-disruptive", "permit changes that briefly steal window focus")
|
|
17
12
|
.option("--allow-very-disruptive", "permit changes that visibly drive the Things UI")
|
|
18
13
|
.option("--verify-timeout <ms>", "how long to wait for the change to take effect")
|
|
19
14
|
.option("--actor <name>", "author name recorded for this change (default: from config)");
|
|
20
15
|
}
|
|
16
|
+
/** A commander flag value when present-with-value (bare presence yields `true`). */
|
|
17
|
+
const flagVal = (v) => (typeof v === "string" ? v : undefined);
|
|
18
|
+
/** Writes one batch result as an NDJSON line to stdout. */
|
|
19
|
+
const emit = (r) => {
|
|
20
|
+
process.stdout.write(`${JSON.stringify(r)}\n`);
|
|
21
|
+
};
|
|
21
22
|
function writeOptionsFrom(opts, extra = {}) {
|
|
22
23
|
const maxDisruption = opts.allowVeryDisruptive
|
|
23
24
|
? 3
|
|
@@ -30,31 +31,44 @@ function writeOptionsFrom(opts, extra = {}) {
|
|
|
30
31
|
...(maxDisruption !== undefined && { maxDisruption }),
|
|
31
32
|
...(opts.verifyTimeout !== undefined && { verifyTimeoutMs: Number(opts.verifyTimeout) }),
|
|
32
33
|
...(opts.actor !== undefined && { actor: opts.actor }),
|
|
34
|
+
...(opts.dangerouslyDriveGui === true && { dangerouslyDriveGui: true }),
|
|
33
35
|
...extra,
|
|
34
36
|
};
|
|
35
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* `--create-tags` on a tag-accepting command: create any named tag that does
|
|
40
|
+
* not exist yet (mkdir-p for `parent/child`) before applying, instead of
|
|
41
|
+
* refusing. Nesting is via the clean `make new tag` path.
|
|
42
|
+
*/
|
|
43
|
+
function addCreateTagsFlag(cmd) {
|
|
44
|
+
return cmd.option("--create-tags", "create any missing tag (nesting parent/child) instead of failing on an unknown tag");
|
|
45
|
+
}
|
|
46
|
+
/** WriteOptions extra carrying createTags when the flag is set. */
|
|
47
|
+
function createTagsExtra(opts) {
|
|
48
|
+
return opts["createTags"] === true ? { createTags: true } : {};
|
|
49
|
+
}
|
|
50
|
+
/** Add the mandatory GUI-drive acknowledgement to a ui-vector command. */
|
|
51
|
+
function addDriveGuiFlag(cmd) {
|
|
52
|
+
return cmd.option("--dangerously-drive-gui", "required: visibly drives the Things app to make a change it offers nowhere else; " +
|
|
53
|
+
"also needs `things config set ui-enabled true`");
|
|
54
|
+
}
|
|
36
55
|
function collect(value, previous) {
|
|
37
56
|
return [...previous, value];
|
|
38
57
|
}
|
|
39
58
|
/**
|
|
40
59
|
* URL-style `--when DATE@TIME` sugar: splits into when + reminder for the
|
|
41
|
-
* ops that take both (an explicit --reminder alongside the suffix errors).
|
|
60
|
+
* ops that take both (an explicit --reminder alongside the suffix errors). The
|
|
61
|
+
* `@` split and its usage copy are the shared {@link splitWhenSugar} core; this
|
|
62
|
+
* only mutates the parsed opts on a successful split.
|
|
42
63
|
*/
|
|
43
64
|
function applyWhenSugar(opts) {
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
46
|
-
return
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (date === "" || time === "" || time.includes("@")) {
|
|
51
|
-
return `invalid --when "${when}" — expected today | evening | anytime | someday | YYYY-MM-DD (set a reminder with --reminder HH:mm)`;
|
|
52
|
-
}
|
|
53
|
-
if (opts["reminder"] !== undefined) {
|
|
54
|
-
return `--when "${when}" carries an @time suffix and --reminder was also given — use one`;
|
|
65
|
+
const r = splitWhenSugar(opts["when"], opts["reminder"] !== undefined);
|
|
66
|
+
if (r.kind === "error")
|
|
67
|
+
return r.message;
|
|
68
|
+
if (r.kind === "split") {
|
|
69
|
+
opts["when"] = r.when;
|
|
70
|
+
opts["reminder"] = r.reminder;
|
|
55
71
|
}
|
|
56
|
-
opts["when"] = date;
|
|
57
|
-
opts["reminder"] = time;
|
|
58
72
|
return null;
|
|
59
73
|
}
|
|
60
74
|
/** Apply the sugar or print the usage error; false = caller returns. */
|
|
@@ -62,8 +76,7 @@ function whenSugarOk(opts) {
|
|
|
62
76
|
const err = applyWhenSugar(opts);
|
|
63
77
|
if (err === null)
|
|
64
78
|
return true;
|
|
65
|
-
|
|
66
|
-
process.exitCode = ExitCode.Usage;
|
|
79
|
+
usageError(opts, err);
|
|
67
80
|
return false;
|
|
68
81
|
}
|
|
69
82
|
function splitCsv(value) {
|
|
@@ -80,12 +93,14 @@ async function runWrite(opts, fn) {
|
|
|
80
93
|
const meta = (client_) => {
|
|
81
94
|
let dbVersion = null;
|
|
82
95
|
let fingerprint = "unknown";
|
|
96
|
+
let clock;
|
|
83
97
|
if (client_ !== null) {
|
|
84
98
|
const fp = client_.fingerprint();
|
|
85
99
|
dbVersion = fp.observation.databaseVersion;
|
|
86
100
|
fingerprint = fp.kind === "ok" ? "ok" : fp.kind === "drift" ? "drift" : "unknown";
|
|
101
|
+
clock = client_.clockMeta();
|
|
87
102
|
}
|
|
88
|
-
return { dbVersion, fingerprint, elapsedMs: Date.now() - started };
|
|
103
|
+
return { dbVersion, fingerprint, elapsedMs: Date.now() - started, ...(clock && { clock }) };
|
|
89
104
|
};
|
|
90
105
|
try {
|
|
91
106
|
client = openThings(opts.db ? { dbPath: opts.db } : {});
|
|
@@ -93,6 +108,28 @@ async function runWrite(opts, fn) {
|
|
|
93
108
|
emitResult(result, opts, meta(client));
|
|
94
109
|
}
|
|
95
110
|
catch (err) {
|
|
111
|
+
// An unresolved write target (uuid/partial-uuid/name that is ambiguous or
|
|
112
|
+
// not-found) is a usage-class failure carrying machine-readable candidates
|
|
113
|
+
// — never the generic `unexpected`.
|
|
114
|
+
if (err instanceof ReferenceResolutionError) {
|
|
115
|
+
if (opts.json) {
|
|
116
|
+
process.stdout.write(`${JSON.stringify(errorEnvelope({
|
|
117
|
+
code: err.code,
|
|
118
|
+
message: err.message,
|
|
119
|
+
details: { candidates: err.candidates },
|
|
120
|
+
}, meta(client)))}\n`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
process.stderr.write(`error: ${err.message}\n`);
|
|
124
|
+
}
|
|
125
|
+
process.exitCode = ExitCode.Usage;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// A malformed THINGS_TZ / THINGS_NOW fails closed as a usage error.
|
|
129
|
+
if (err instanceof ClockError) {
|
|
130
|
+
usageError(opts, err.message);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
96
133
|
const isEnv = err instanceof ThingsDbNotFoundError || err instanceof ThingsDbOpenError;
|
|
97
134
|
const message = err instanceof Error ? err.message : String(err);
|
|
98
135
|
if (opts.json) {
|
|
@@ -159,7 +196,7 @@ function emitResult(result, opts, meta) {
|
|
|
159
196
|
case "verify-failed": {
|
|
160
197
|
if (opts.json) {
|
|
161
198
|
process.stdout.write(`${JSON.stringify(errorEnvelope({
|
|
162
|
-
code:
|
|
199
|
+
code: verifyFailedCode(result),
|
|
163
200
|
message: result.detail,
|
|
164
201
|
...(result.likelyCause !== undefined && { likelyCause: result.likelyCause }),
|
|
165
202
|
...(result.hint !== undefined && { remediation: result.hint }),
|
|
@@ -179,7 +216,7 @@ function emitResult(result, opts, meta) {
|
|
|
179
216
|
const code = result.reason === "drift" ? ExitCode.DriftBlocked : ExitCode.Blocked;
|
|
180
217
|
if (opts.json) {
|
|
181
218
|
process.stdout.write(`${JSON.stringify(errorEnvelope({
|
|
182
|
-
code:
|
|
219
|
+
code: blockedCode(result),
|
|
183
220
|
message: result.detail,
|
|
184
221
|
...(result.likelyCause !== undefined && { likelyCause: result.likelyCause }),
|
|
185
222
|
remediation: result.remediation,
|
|
@@ -223,22 +260,23 @@ function group(program, name, description) {
|
|
|
223
260
|
const containerRef = (value) => value === undefined ? undefined : { uuid: value, title: value };
|
|
224
261
|
export function registerWriteCommands(program) {
|
|
225
262
|
const todo = group(program, "todo", "To-do–scoped operations");
|
|
226
|
-
addWriteFlags(todo
|
|
263
|
+
addCreateTagsFlag(addWriteFlags(todo
|
|
227
264
|
.command("add <title>")
|
|
228
|
-
.description("Create a to-do; its uuid is printed on success.
|
|
229
|
-
"
|
|
230
|
-
"
|
|
265
|
+
.description("Create a to-do; its uuid is printed on success. Projects, areas, and headings " +
|
|
266
|
+
"must name existing items — unknown or ambiguous references are rejected. A tag " +
|
|
267
|
+
"may be a name or a parent/child path, and must exist unless " +
|
|
268
|
+
"--create-tags. Adding into a completed/canceled project reopens that project — " +
|
|
231
269
|
"requires --acknowledge-project-reopen.")
|
|
232
270
|
.option("--notes <text>", "notes body")
|
|
233
271
|
.option("--when <value>", "today | evening | anytime | someday | YYYY-MM-DD")
|
|
234
272
|
.option("--reminder <HH:mm>", "time-of-day reminder (24h); requires --when today|evening|YYYY-MM-DD")
|
|
235
273
|
.option("--deadline <date>", "YYYY-MM-DD")
|
|
236
|
-
.option("--tags <list>", "comma-separated
|
|
274
|
+
.option("--tags <list>", "comma-separated tags; each a name or a parent/child path (must exist unless --create-tags)")
|
|
237
275
|
.option("--checklist-item <text>", "checklist item (repeatable)", collect, [])
|
|
238
276
|
.option("--project <ref>", "destination project (uuid or unique name)")
|
|
239
277
|
.option("--area <ref>", "destination area (uuid or unique name)")
|
|
240
278
|
.option("--heading <name>", "existing heading in the destination project")
|
|
241
|
-
.option("--acknowledge-project-reopen", "allow adding into a completed/canceled project")).action(async (title, opts) => {
|
|
279
|
+
.option("--acknowledge-project-reopen", "allow adding into a completed/canceled project"))).action(async (title, opts) => {
|
|
242
280
|
const checklist = opts["checklistItem"];
|
|
243
281
|
const tags = splitCsv(opts["tags"]);
|
|
244
282
|
const project = containerRef(opts["project"]);
|
|
@@ -260,6 +298,7 @@ export function registerWriteCommands(program) {
|
|
|
260
298
|
...(opts["acknowledgeProjectReopen"] !== undefined && {
|
|
261
299
|
acknowledgeProjectReopen: opts["acknowledgeProjectReopen"],
|
|
262
300
|
}),
|
|
301
|
+
...createTagsExtra(opts),
|
|
263
302
|
})));
|
|
264
303
|
});
|
|
265
304
|
addWriteFlags(todo
|
|
@@ -282,13 +321,11 @@ export function registerWriteCommands(program) {
|
|
|
282
321
|
.option("--clear-deadline", "remove the deadline")).action(async (uuid, opts) => {
|
|
283
322
|
const notesModes = ["notes", "appendNotes", "prependNotes"].filter((k) => opts[k] !== undefined);
|
|
284
323
|
if (notesModes.length > 1) {
|
|
285
|
-
|
|
286
|
-
process.exitCode = ExitCode.Usage;
|
|
324
|
+
usageError(opts, "--notes, --append-notes, --prepend-notes are exclusive");
|
|
287
325
|
return;
|
|
288
326
|
}
|
|
289
327
|
if (opts["reminder"] !== undefined && opts["clearReminder"] === true) {
|
|
290
|
-
|
|
291
|
-
process.exitCode = ExitCode.Usage;
|
|
328
|
+
usageError(opts, "pass at most one of --reminder / --clear-reminder");
|
|
292
329
|
return;
|
|
293
330
|
}
|
|
294
331
|
if (!whenSugarOk(opts))
|
|
@@ -336,8 +373,7 @@ export function registerWriteCommands(program) {
|
|
|
336
373
|
const detach = opts["detach"] === true;
|
|
337
374
|
const dest = project !== undefined || area !== undefined || opts["heading"] !== undefined;
|
|
338
375
|
if ((inbox && (dest || detach)) || (detach && dest)) {
|
|
339
|
-
|
|
340
|
-
process.exitCode = ExitCode.Usage;
|
|
376
|
+
usageError(opts, "--inbox/--detach are exclusive with each other and with --project/--area/--heading");
|
|
341
377
|
return;
|
|
342
378
|
}
|
|
343
379
|
await runWrite(opts, (c) => c.write.moveTodo(uuid, {
|
|
@@ -359,34 +395,31 @@ export function registerWriteCommands(program) {
|
|
|
359
395
|
"available for repeating to-dos.")).action(async (uuid, opts) => {
|
|
360
396
|
await runWrite(opts, (c) => c.write.duplicateTodo(uuid, writeOptionsFrom(opts)));
|
|
361
397
|
});
|
|
362
|
-
addWriteFlags(todo
|
|
398
|
+
addCreateTagsFlag(addWriteFlags(todo
|
|
363
399
|
.command("tags <uuid>")
|
|
364
400
|
.description("Set or extend a to-do's tags. --set REPLACES the full tag set (an empty value " +
|
|
365
|
-
"clears all tags); --add merges with the current tags.
|
|
366
|
-
"
|
|
367
|
-
.option("--set <list>", "comma-separated
|
|
368
|
-
.option("--add <list>", "comma-separated
|
|
401
|
+
"clears all tags); --add merges with the current tags. Each tag may be a name " +
|
|
402
|
+
"or a parent/child path, and must exist unless --create-tags.")
|
|
403
|
+
.option("--set <list>", "comma-separated tags: full replacement")
|
|
404
|
+
.option("--add <list>", "comma-separated tags: merge with existing"))).action(async (uuid, opts) => {
|
|
369
405
|
const set = splitCsv(opts["set"]);
|
|
370
406
|
const add = splitCsv(opts["add"]);
|
|
371
407
|
if ((set === undefined) === (add === undefined)) {
|
|
372
|
-
|
|
373
|
-
process.exitCode = ExitCode.Usage;
|
|
408
|
+
usageError(opts, "pass exactly one of --set or --add");
|
|
374
409
|
return;
|
|
375
410
|
}
|
|
376
411
|
await runWrite(opts, (c) => set !== undefined
|
|
377
|
-
? c.write.setTags(uuid, set, writeOptionsFrom(opts))
|
|
378
|
-
: c.write.addTags(uuid, add ?? [], writeOptionsFrom(opts)));
|
|
412
|
+
? c.write.setTags(uuid, set, writeOptionsFrom(opts, createTagsExtra(opts)))
|
|
413
|
+
: c.write.addTags(uuid, add ?? [], writeOptionsFrom(opts, createTagsExtra(opts))));
|
|
379
414
|
});
|
|
380
415
|
addWriteFlags(todo
|
|
381
416
|
.command("checklist <uuid>")
|
|
382
417
|
.description("Edit a to-do's checklist. WHOLESALE: --item (repeatable) replaces the whole list, " +
|
|
383
418
|
"discarding the existing items and their checked states — requires " +
|
|
384
|
-
"--acknowledge-checklist-reset when items exist. GRANULAR (one per call): " +
|
|
385
|
-
"
|
|
386
|
-
"
|
|
387
|
-
"
|
|
388
|
-
"resolved best-effort (check → first unchecked, etc.). Checklist item uuids are " +
|
|
389
|
-
"internal and never exposed.")
|
|
419
|
+
"--acknowledge-checklist-reset when items exist. GRANULAR (one action per call): " +
|
|
420
|
+
"the flags below add, remove, check, uncheck, rename, or move a single item, with " +
|
|
421
|
+
"every other item's checked state PRESERVED. Target an item by title or by --index " +
|
|
422
|
+
"(1-based); duplicate titles resolve best-effort. Checklist items have no exposed uuid.")
|
|
390
423
|
.option("--item <text>", "wholesale: checklist item in order (repeatable)", collect, [])
|
|
391
424
|
.option("--acknowledge-checklist-reset", "accept wholesale replacement of existing items")
|
|
392
425
|
.option("--add <title>", "granular: append an item")
|
|
@@ -401,36 +434,30 @@ export function registerWriteCommands(program) {
|
|
|
401
434
|
.option("--to-position <n>", "1-based position for --move-item")).action(async (uuid, opts) => {
|
|
402
435
|
const granular = ["add", "remove", "check", "uncheck", "rename", "moveItem"].filter((k) => opts[k] !== undefined);
|
|
403
436
|
if (granular.length > 1) {
|
|
404
|
-
|
|
405
|
-
process.exitCode = ExitCode.Usage;
|
|
437
|
+
usageError(opts, "pass at most ONE granular checklist action per call");
|
|
406
438
|
return;
|
|
407
439
|
}
|
|
408
440
|
const action = granular[0];
|
|
409
441
|
if (action !== undefined) {
|
|
410
442
|
if (opts["item"].length > 0) {
|
|
411
|
-
|
|
412
|
-
process.exitCode = ExitCode.Usage;
|
|
443
|
+
usageError(opts, "--item (wholesale) is exclusive with granular actions");
|
|
413
444
|
return;
|
|
414
445
|
}
|
|
415
446
|
if (action === "rename" && opts["to"] === undefined) {
|
|
416
|
-
|
|
417
|
-
process.exitCode = ExitCode.Usage;
|
|
447
|
+
usageError(opts, "--rename requires --to <title>");
|
|
418
448
|
return;
|
|
419
449
|
}
|
|
420
450
|
if (action === "moveItem" && opts["toPosition"] === undefined) {
|
|
421
|
-
|
|
422
|
-
process.exitCode = ExitCode.Usage;
|
|
451
|
+
usageError(opts, "--move-item requires --to-position <n>");
|
|
423
452
|
return;
|
|
424
453
|
}
|
|
425
454
|
// Target: --index (1-based) OR the action flag's title value. When a
|
|
426
455
|
// targeting flag is present without a value commander yields `true`.
|
|
427
|
-
const flagVal = (v) => (typeof v === "string" ? v : undefined);
|
|
428
456
|
const target = opts["index"] !== undefined
|
|
429
457
|
? { index: Number(opts["index"]) }
|
|
430
458
|
: { item: flagVal(opts[action]) ?? "" };
|
|
431
459
|
if (action !== "add" && opts["index"] === undefined && target.item === "") {
|
|
432
|
-
|
|
433
|
-
process.exitCode = ExitCode.Usage;
|
|
460
|
+
usageError(opts, `--${action} needs a title, or use --index <n>`);
|
|
434
461
|
return;
|
|
435
462
|
}
|
|
436
463
|
const edit = action === "add"
|
|
@@ -472,12 +499,10 @@ export function registerWriteCommands(program) {
|
|
|
472
499
|
});
|
|
473
500
|
addWriteFlags(todo
|
|
474
501
|
.command("clear-reminder <uuid>")
|
|
475
|
-
.description("Clear a to-do's time-of-day reminder
|
|
476
|
-
"
|
|
477
|
-
"
|
|
478
|
-
"
|
|
479
|
-
"and back. Reversible with `things undo`. Force a delivery path with --vector " +
|
|
480
|
-
"shortcuts|url-scheme.")).action(async (uuid, opts) => {
|
|
502
|
+
.description("Clear a to-do's time-of-day reminder, keeping its scheduled date. With the proxy " +
|
|
503
|
+
"shortcuts installed (`things setup shortcuts`) this is in place, and is the only " +
|
|
504
|
+
"way for a repeating to-do; otherwise a date-scheduled to-do is cleared by a brief " +
|
|
505
|
+
"re-schedule through Today. Reversible with `things undo`.")).action(async (uuid, opts) => {
|
|
481
506
|
await runWrite(opts, (c) => c.write.clearReminder(uuid, writeOptionsFrom(opts)));
|
|
482
507
|
});
|
|
483
508
|
addWriteFlags(todo
|
|
@@ -509,6 +534,63 @@ export function registerWriteCommands(program) {
|
|
|
509
534
|
...(opts["notes"] !== undefined && { notes: opts["notes"] }),
|
|
510
535
|
}, writeOptionsFrom(opts)));
|
|
511
536
|
});
|
|
537
|
+
// --- ui vector: GUI-driven transforms (two-key gated) --------------------
|
|
538
|
+
const REPEAT_FREQ_HELP = "daily | weekly | monthly | yearly";
|
|
539
|
+
const REPEAT_INTERVAL_HELP = "every N units (1–99)";
|
|
540
|
+
for (const [verb, op, desc] of [
|
|
541
|
+
[
|
|
542
|
+
"make-repeating",
|
|
543
|
+
"todo.make-repeating",
|
|
544
|
+
"Turn a plain to-do into a repeating one. This REPLACES the to-do with a new repeating " +
|
|
545
|
+
"series — the original disappears and a fresh recurring item takes its place " +
|
|
546
|
+
"(cannot be undone). Set the rule with the flags below; see `things help repeating`.",
|
|
547
|
+
],
|
|
548
|
+
[
|
|
549
|
+
"reschedule-repeat",
|
|
550
|
+
"todo.reschedule-repeat",
|
|
551
|
+
"Change an existing repeating to-do's rule in place (the item keeps its identity). Set the " +
|
|
552
|
+
"new rule with the flags below; see `things help repeating`. `things undo` restores the " +
|
|
553
|
+
"previous rule.",
|
|
554
|
+
],
|
|
555
|
+
]) {
|
|
556
|
+
addDriveGuiFlag(addRepeatRuleFlags(addWriteFlags(todo
|
|
557
|
+
.command(`${verb} <uuid>`)
|
|
558
|
+
.description(desc)
|
|
559
|
+
.requiredOption("--frequency <freq>", REPEAT_FREQ_HELP)
|
|
560
|
+
.requiredOption("--interval <n>", REPEAT_INTERVAL_HELP)))).action(async (uuid, opts) => {
|
|
561
|
+
const frequency = opts["frequency"];
|
|
562
|
+
await runWrite(opts, (c) => c.write.run(op, {
|
|
563
|
+
uuid,
|
|
564
|
+
frequency,
|
|
565
|
+
interval: Number(opts["interval"]),
|
|
566
|
+
...repeatRuleFlagsFromOpts(opts, frequency),
|
|
567
|
+
}, writeOptionsFrom(opts)));
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
for (const [verb, op, desc] of [
|
|
571
|
+
[
|
|
572
|
+
"pause-repeat",
|
|
573
|
+
"todo.pause-repeat",
|
|
574
|
+
"Pause a repeating to-do: it stops spawning new occurrences but keeps its rule. Reversible " +
|
|
575
|
+
"with `things todo resume-repeat`.",
|
|
576
|
+
],
|
|
577
|
+
[
|
|
578
|
+
"resume-repeat",
|
|
579
|
+
"todo.resume-repeat",
|
|
580
|
+
"Resume a paused repeating to-do: it starts spawning occurrences again.",
|
|
581
|
+
],
|
|
582
|
+
[
|
|
583
|
+
"convert-to-project",
|
|
584
|
+
"todo.convert-to-project",
|
|
585
|
+
"Convert a to-do into a project. This REPLACES the to-do with a new project (its notes are " +
|
|
586
|
+
"kept); the to-do's identity is gone and it cannot be undone. The new project's uuid is " +
|
|
587
|
+
"printed on success.",
|
|
588
|
+
],
|
|
589
|
+
]) {
|
|
590
|
+
addDriveGuiFlag(addWriteFlags(todo.command(`${verb} <uuid>`).description(desc))).action(async (uuid, opts) => {
|
|
591
|
+
await runWrite(opts, (c) => c.write.run(op, { uuid }, writeOptionsFrom(opts)));
|
|
592
|
+
});
|
|
593
|
+
}
|
|
512
594
|
const heading = group(program, "heading", "Heading-scoped operations");
|
|
513
595
|
addWriteFlags(heading
|
|
514
596
|
.command("add <project> <title>")
|
|
@@ -556,7 +638,88 @@ export function registerWriteCommands(program) {
|
|
|
556
638
|
return outcome.heading;
|
|
557
639
|
});
|
|
558
640
|
});
|
|
641
|
+
addDriveGuiFlag(addWriteFlags(heading
|
|
642
|
+
.command("convert-to-project <uuid>")
|
|
643
|
+
.description("Convert a heading into a project. This REPLACES the heading with a new project — it " +
|
|
644
|
+
"is promoted alongside its parent project (into the same area) and the heading's " +
|
|
645
|
+
"to-dos move under the new project. The heading's identity is gone and it cannot be " +
|
|
646
|
+
"undone. The new project's uuid is printed on success."))).action(async (uuid, opts) => {
|
|
647
|
+
await runWrite(opts, (c) => c.write.run("heading.convert-to-project", { uuid }, writeOptionsFrom(opts)));
|
|
648
|
+
});
|
|
559
649
|
const project = group(program, "project", "Project-scoped operations");
|
|
650
|
+
// --- ui vector: repeating-project transforms (two-key gated) -------------
|
|
651
|
+
addDriveGuiFlag(addRepeatRuleFlags(addWriteFlags(project
|
|
652
|
+
.command("reschedule-repeat <ref>")
|
|
653
|
+
.description("Change an existing repeating project's rule in place (target by uuid or unique name; " +
|
|
654
|
+
"the project keeps its identity). Set the new rule with the flags below; see " +
|
|
655
|
+
"`things help repeating`. `things undo` restores the previous rule.")
|
|
656
|
+
.requiredOption("--frequency <freq>", REPEAT_FREQ_HELP)
|
|
657
|
+
.requiredOption("--interval <n>", REPEAT_INTERVAL_HELP)))).action(async (uuid, opts) => {
|
|
658
|
+
const frequency = opts["frequency"];
|
|
659
|
+
await runWrite(opts, (c) => c.write.run("project.reschedule-repeat", {
|
|
660
|
+
uuid,
|
|
661
|
+
frequency,
|
|
662
|
+
interval: Number(opts["interval"]),
|
|
663
|
+
...repeatRuleFlagsFromOpts(opts, frequency),
|
|
664
|
+
}, writeOptionsFrom(opts)));
|
|
665
|
+
});
|
|
666
|
+
for (const [verb, op, desc] of [
|
|
667
|
+
[
|
|
668
|
+
"pause-repeat",
|
|
669
|
+
"project.pause-repeat",
|
|
670
|
+
"Pause a repeating project: it stops spawning new occurrences but keeps its rule. Reversible " +
|
|
671
|
+
"with `things project resume-repeat`.",
|
|
672
|
+
],
|
|
673
|
+
[
|
|
674
|
+
"resume-repeat",
|
|
675
|
+
"project.resume-repeat",
|
|
676
|
+
"Resume a paused repeating project: it starts spawning occurrences again.",
|
|
677
|
+
],
|
|
678
|
+
]) {
|
|
679
|
+
addDriveGuiFlag(addWriteFlags(project.command(`${verb} <ref>`).description(desc))).action(async (uuid, opts) => {
|
|
680
|
+
await runWrite(opts, (c) => c.write.run(op, { uuid }, writeOptionsFrom(opts)));
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
addDriveGuiFlag(addRepeatRuleFlags(addWriteFlags(project
|
|
684
|
+
.command("make-repeating <ref>")
|
|
685
|
+
.description("Turn a project into a repeating one. This REPLACES the project with a new repeating " +
|
|
686
|
+
"series — the original disappears and a fresh recurring project takes its place (its " +
|
|
687
|
+
"area is kept; cannot be undone). An Anytime project with no area is moved to Someday " +
|
|
688
|
+
"first (a cleanup-free intermediate step, shown in --dry-run). Set the rule with the " +
|
|
689
|
+
"flags below; see `things help repeating`.")
|
|
690
|
+
.requiredOption("--frequency <freq>", REPEAT_FREQ_HELP)
|
|
691
|
+
.requiredOption("--interval <n>", REPEAT_INTERVAL_HELP)))).action(async (uuid, opts) => {
|
|
692
|
+
const frequency = opts["frequency"];
|
|
693
|
+
await runWrite(opts, (c) => c.write.makeRepeatingProject(uuid, {
|
|
694
|
+
frequency,
|
|
695
|
+
interval: Number(opts["interval"]),
|
|
696
|
+
...repeatRuleFlagsFromOpts(opts, frequency),
|
|
697
|
+
}, writeOptionsFrom(opts)));
|
|
698
|
+
});
|
|
699
|
+
addDriveGuiFlag(addWriteFlags(project
|
|
700
|
+
.command("create-repeating <title>")
|
|
701
|
+
.description("Create a project and turn it into a repeating series in ONE call. Two operations: the " +
|
|
702
|
+
"project is created first and PERSISTS even if the make-repeating step refuses; then it " +
|
|
703
|
+
"is promoted (which drives the GUI). Give --area to place it, or omit it to create in " +
|
|
704
|
+
"Someday. The new repeating project's uuid is printed on success.")
|
|
705
|
+
.option("--notes <text>", "notes body")
|
|
706
|
+
.option("--area <ref>", "destination area (uuid or unique name)")
|
|
707
|
+
.option("--deadline <date>", "YYYY-MM-DD")
|
|
708
|
+
.option("--todo <title>", "initial child to-do (repeatable)", collect, [])
|
|
709
|
+
.requiredOption("--frequency <freq>", REPEAT_FREQ_HELP)
|
|
710
|
+
.requiredOption("--interval <n>", REPEAT_INTERVAL_HELP))).action(async (title, opts) => {
|
|
711
|
+
const todos = opts["todo"];
|
|
712
|
+
const area = containerRef(opts["area"]);
|
|
713
|
+
await runWrite(opts, (c) => c.write.createRepeatingProject({
|
|
714
|
+
title,
|
|
715
|
+
...(opts["notes"] !== undefined && { notes: opts["notes"] }),
|
|
716
|
+
...(area !== undefined && { area }),
|
|
717
|
+
...(opts["deadline"] !== undefined && { deadline: opts["deadline"] }),
|
|
718
|
+
...(todos.length > 0 && { todos }),
|
|
719
|
+
frequency: opts["frequency"],
|
|
720
|
+
interval: Number(opts["interval"]),
|
|
721
|
+
}, writeOptionsFrom(opts)));
|
|
722
|
+
});
|
|
560
723
|
addWriteFlags(project
|
|
561
724
|
.command("add <title>")
|
|
562
725
|
.description("Create a project; its uuid is printed on success.")
|
|
@@ -577,11 +740,12 @@ export function registerWriteCommands(program) {
|
|
|
577
740
|
}, writeOptionsFrom(opts)));
|
|
578
741
|
});
|
|
579
742
|
addWriteFlags(project
|
|
580
|
-
.command("update <
|
|
581
|
-
.description("Update a project's title/notes/when/deadline/reminder.
|
|
582
|
-
"
|
|
583
|
-
"--
|
|
584
|
-
"reminder
|
|
743
|
+
.command("update <ref>")
|
|
744
|
+
.description("Update a project's title/notes/when/deadline/reminder. Target by uuid or unique " +
|
|
745
|
+
"name — a duplicated project name is refused, listing the candidates to pick from " +
|
|
746
|
+
"by uuid. --append-notes/--prepend-notes join with a newline (exclusive with " +
|
|
747
|
+
"--notes). --reminder needs --when today|evening|YYYY-MM-DD; when re-scheduling " +
|
|
748
|
+
"WITHOUT --reminder an existing reminder is auto-preserved."))
|
|
585
749
|
.option("--title <text>", "new title")
|
|
586
750
|
.option("--notes <text>", "replace notes")
|
|
587
751
|
.option("--append-notes <text>", "append to existing notes (newline-joined)")
|
|
@@ -594,13 +758,11 @@ export function registerWriteCommands(program) {
|
|
|
594
758
|
.action(async (uuid, opts) => {
|
|
595
759
|
const notesModes = ["notes", "appendNotes", "prependNotes"].filter((k) => opts[k] !== undefined);
|
|
596
760
|
if (notesModes.length > 1) {
|
|
597
|
-
|
|
598
|
-
process.exitCode = ExitCode.Usage;
|
|
761
|
+
usageError(opts, "--notes, --append-notes, --prepend-notes are exclusive");
|
|
599
762
|
return;
|
|
600
763
|
}
|
|
601
764
|
if (opts["reminder"] !== undefined && opts["clearReminder"] === true) {
|
|
602
|
-
|
|
603
|
-
process.exitCode = ExitCode.Usage;
|
|
765
|
+
usageError(opts, "pass at most one of --reminder / --clear-reminder");
|
|
604
766
|
return;
|
|
605
767
|
}
|
|
606
768
|
if (!whenSugarOk(opts))
|
|
@@ -621,33 +783,33 @@ export function registerWriteCommands(program) {
|
|
|
621
783
|
...(opts["clearDeadline"] === true && { deadline: null }),
|
|
622
784
|
}, writeOptionsFrom(opts)));
|
|
623
785
|
});
|
|
624
|
-
addWriteFlags(project
|
|
625
|
-
.command("tags <
|
|
626
|
-
.description("Set or extend a project's tags
|
|
627
|
-
"clears all tags); --add merges with the current tags.
|
|
628
|
-
"
|
|
629
|
-
|
|
630
|
-
.option("--
|
|
786
|
+
addCreateTagsFlag(addWriteFlags(project
|
|
787
|
+
.command("tags <ref>")
|
|
788
|
+
.description("Set or extend a project's tags (target by uuid or unique name). --set REPLACES the " +
|
|
789
|
+
"full tag set (an empty value clears all tags); --add merges with the current tags. " +
|
|
790
|
+
"Each tag may be a name or a parent/child path, and must exist unless " +
|
|
791
|
+
"--create-tags.")
|
|
792
|
+
.option("--set <list>", "comma-separated tags: full replacement")
|
|
793
|
+
.option("--add <list>", "comma-separated tags: merge with existing"))).action(async (uuid, opts) => {
|
|
631
794
|
const set = splitCsv(opts["set"]);
|
|
632
795
|
const add = splitCsv(opts["add"]);
|
|
633
796
|
if ((set === undefined) === (add === undefined)) {
|
|
634
|
-
|
|
635
|
-
process.exitCode = ExitCode.Usage;
|
|
797
|
+
usageError(opts, "pass exactly one of --set or --add");
|
|
636
798
|
return;
|
|
637
799
|
}
|
|
638
800
|
await runWrite(opts, (c) => set !== undefined
|
|
639
|
-
? c.write.setProjectTags(uuid, set, writeOptionsFrom(opts))
|
|
640
|
-
: c.write.addProjectTags(uuid, add ?? [], writeOptionsFrom(opts)));
|
|
801
|
+
? c.write.setProjectTags(uuid, set, writeOptionsFrom(opts, createTagsExtra(opts)))
|
|
802
|
+
: c.write.addProjectTags(uuid, add ?? [], writeOptionsFrom(opts, createTagsExtra(opts))));
|
|
641
803
|
});
|
|
642
804
|
addWriteFlags(project
|
|
643
|
-
.command("move <
|
|
644
|
-
.description("Move a project to another area, or DETACH it from
|
|
645
|
-
"Status and schedule are untouched. Unknown areas are
|
|
805
|
+
.command("move <ref>")
|
|
806
|
+
.description("Move a project (target by uuid or unique name) to another area, or DETACH it from " +
|
|
807
|
+
"its current area (--detach). Status and schedule are untouched. Unknown areas are " +
|
|
808
|
+
"rejected.")
|
|
646
809
|
.option("--area <ref>", "destination area (uuid or unique name)")
|
|
647
810
|
.option("--detach", "remove the current area assignment (exclusive with --area)")).action(async (uuid, opts) => {
|
|
648
811
|
if ((opts.detach === true) === (opts.area !== undefined)) {
|
|
649
|
-
|
|
650
|
-
process.exitCode = ExitCode.Usage;
|
|
812
|
+
usageError(opts, "pass exactly one of --area / --detach");
|
|
651
813
|
return;
|
|
652
814
|
}
|
|
653
815
|
await runWrite(opts, (c) => opts.detach === true
|
|
@@ -655,15 +817,17 @@ export function registerWriteCommands(program) {
|
|
|
655
817
|
: c.write.moveProject(uuid, { uuid: opts.area, title: opts.area }, writeOptionsFrom(opts)));
|
|
656
818
|
});
|
|
657
819
|
addWriteFlags(project
|
|
658
|
-
.command("cancel <
|
|
659
|
-
.description("Cancel a project. Canceling also cancels its open
|
|
660
|
-
"--children policy is required; already-completed children
|
|
820
|
+
.command("cancel <ref>")
|
|
821
|
+
.description("Cancel a project (target by uuid or unique name). Canceling also cancels its open " +
|
|
822
|
+
"to-dos, so an explicit --children policy is required; already-completed children " +
|
|
823
|
+
"are never altered.")
|
|
661
824
|
.requiredOption("--children <policy>", "require-resolved (error if open to-dos remain) | auto-cancel (cancel them too)")).action(async (uuid, opts) => {
|
|
662
825
|
await runWrite(opts, (c) => c.write.cancelProject(uuid, { children: opts.children }, writeOptionsFrom(opts)));
|
|
663
826
|
});
|
|
664
827
|
addWriteFlags(project
|
|
665
|
-
.command("reopen <
|
|
666
|
-
.description("Reopen a completed/canceled project. Its children
|
|
828
|
+
.command("reopen <ref>")
|
|
829
|
+
.description("Reopen a completed/canceled project (target by uuid or unique name). Its children " +
|
|
830
|
+
"stay completed/canceled unless " +
|
|
667
831
|
"--restore-children also reopens the ones that were resolved together with the " +
|
|
668
832
|
"project — children resolved earlier are never touched. Exit 3 if any child " +
|
|
669
833
|
"restore fails.")
|
|
@@ -708,57 +872,86 @@ export function registerWriteCommands(program) {
|
|
|
708
872
|
}
|
|
709
873
|
});
|
|
710
874
|
addWriteFlags(project
|
|
711
|
-
.command("restore <
|
|
712
|
-
.description("Restore a TRASHED project IN PLACE: schedule, area,
|
|
713
|
-
"state. Only trashed projects qualify.")).action(async (uuid, opts) => {
|
|
875
|
+
.command("restore <ref>")
|
|
876
|
+
.description("Restore a TRASHED project IN PLACE (target by uuid or unique name): schedule, area, " +
|
|
877
|
+
"and children all keep their state. Only trashed projects qualify.")).action(async (uuid, opts) => {
|
|
714
878
|
await runWrite(opts, (c) => c.write.restoreProject(uuid, writeOptionsFrom(opts)));
|
|
715
879
|
});
|
|
716
880
|
addWriteFlags(project
|
|
717
|
-
.command("duplicate <
|
|
718
|
-
.description("Duplicate a project INCLUDING its children; the
|
|
719
|
-
"success. Not available for repeating projects.")).action(async (uuid, opts) => {
|
|
881
|
+
.command("duplicate <ref>")
|
|
882
|
+
.description("Duplicate a project (target by uuid or unique name) INCLUDING its children; the " +
|
|
883
|
+
"copy's uuid is printed on success. Not available for repeating projects.")).action(async (uuid, opts) => {
|
|
720
884
|
await runWrite(opts, (c) => c.write.duplicateProject(uuid, writeOptionsFrom(opts)));
|
|
721
885
|
});
|
|
722
886
|
addWriteFlags(project
|
|
723
|
-
.command("complete <
|
|
724
|
-
.description("Complete a project. Completing also completes its
|
|
725
|
-
"--children policy is required.")
|
|
887
|
+
.command("complete <ref>")
|
|
888
|
+
.description("Complete a project (target by uuid or unique name). Completing also completes its " +
|
|
889
|
+
"open to-dos, so an explicit --children policy is required.")
|
|
726
890
|
.requiredOption("--children <policy>", "require-resolved (error if open to-dos remain) | auto-complete (complete them too)")).action(async (uuid, opts) => {
|
|
727
891
|
await runWrite(opts, (c) => c.write.completeProject(uuid, { children: opts.children }, writeOptionsFrom(opts)));
|
|
728
892
|
});
|
|
729
893
|
addWriteFlags(project
|
|
730
|
-
.command("delete <
|
|
731
|
-
.description("Move a project to the Trash; its children go with
|
|
732
|
-
"project restore`).")).action(async (uuid, opts) => {
|
|
894
|
+
.command("delete <ref>")
|
|
895
|
+
.description("Move a project (target by uuid or unique name) to the Trash; its children go with " +
|
|
896
|
+
"it (recover with `things project restore`).")).action(async (uuid, opts) => {
|
|
733
897
|
await runWrite(opts, (c) => c.write.deleteProject(uuid, writeOptionsFrom(opts)));
|
|
734
898
|
});
|
|
735
899
|
const area = group(program, "area", "Area-scoped operations");
|
|
736
|
-
addWriteFlags(area
|
|
900
|
+
addCreateTagsFlag(addWriteFlags(area
|
|
737
901
|
.command("add <title>")
|
|
738
|
-
.description("Create an area, optionally tagged
|
|
739
|
-
|
|
902
|
+
.description("Create an area, optionally tagged. Each tag may be a name or a " +
|
|
903
|
+
"parent/child path, and must exist unless --create-tags.")
|
|
904
|
+
.option("--tags <list>", "comma-separated tags; each a name or a parent/child path (must exist unless --create-tags)"))).action(async (title, opts) => {
|
|
740
905
|
const tags = splitCsv(opts["tags"]);
|
|
741
|
-
await runWrite(opts, (c) => c.write.addArea({ title, ...(tags !== undefined && { tags }) }, writeOptionsFrom(opts)));
|
|
906
|
+
await runWrite(opts, (c) => c.write.addArea({ title, ...(tags !== undefined && { tags }) }, writeOptionsFrom(opts, createTagsExtra(opts))));
|
|
742
907
|
});
|
|
743
|
-
addWriteFlags(area
|
|
744
|
-
.command("update <
|
|
745
|
-
.description("Rename an area and/or replace its tags (the full set
|
|
746
|
-
"tags
|
|
908
|
+
addCreateTagsFlag(addWriteFlags(area
|
|
909
|
+
.command("update <ref>")
|
|
910
|
+
.description("Rename an area and/or replace its tags (the full set). Each tag may be a name " +
|
|
911
|
+
"or a parent/child path, and must exist unless --create-tags. Target by " +
|
|
912
|
+
"uuid or unique name.")
|
|
747
913
|
.option("--title <text>", "new name")
|
|
748
|
-
.option("--tags <list>", 'comma-separated
|
|
914
|
+
.option("--tags <list>", 'comma-separated tags (full replacement; "" clears all); each a name or a parent/child path'))).action(async (target, opts) => {
|
|
749
915
|
const tags = splitCsv(opts["tags"]);
|
|
750
916
|
if (opts["title"] === undefined && tags === undefined) {
|
|
751
|
-
|
|
752
|
-
process.exitCode = ExitCode.Usage;
|
|
917
|
+
usageError(opts, "pass --title and/or --tags");
|
|
753
918
|
return;
|
|
754
919
|
}
|
|
755
920
|
await runWrite(opts, (c) => c.write.updateArea(target, {
|
|
756
921
|
...(opts["title"] !== undefined && { title: opts["title"] }),
|
|
757
922
|
...(tags !== undefined && { tags }),
|
|
923
|
+
}, writeOptionsFrom(opts, createTagsExtra(opts))));
|
|
924
|
+
});
|
|
925
|
+
addDriveGuiFlag(addWriteFlags(area
|
|
926
|
+
.command("reorder <ref>")
|
|
927
|
+
.description("Move an area to a new position in the area order (target by uuid or unique name). " +
|
|
928
|
+
"Pass exactly one destination: --before/--after another area, or --first/--last. " +
|
|
929
|
+
"This visibly drives the Things app (the window comes forward and the sidebar may " +
|
|
930
|
+
"scroll); the area's projects and to-dos are untouched.")
|
|
931
|
+
.option("--before <area>", "place it immediately above this area (uuid or unique name)")
|
|
932
|
+
.option("--after <area>", "place it immediately below this area (uuid or unique name)")
|
|
933
|
+
.option("--first", "move it to the top of the area list")
|
|
934
|
+
.option("--last", "move it to the bottom of the area list"))).action(async (target, opts) => {
|
|
935
|
+
const chosen = [
|
|
936
|
+
opts["before"] !== undefined,
|
|
937
|
+
opts["after"] !== undefined,
|
|
938
|
+
opts["first"] === true,
|
|
939
|
+
opts["last"] === true,
|
|
940
|
+
].filter(Boolean).length;
|
|
941
|
+
if (chosen !== 1) {
|
|
942
|
+
usageError(opts, "pass exactly one of --before / --after / --first / --last");
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
await runWrite(opts, (c) => c.write.run("area.reorder", {
|
|
946
|
+
target,
|
|
947
|
+
...(opts["before"] !== undefined && { before: opts["before"] }),
|
|
948
|
+
...(opts["after"] !== undefined && { after: opts["after"] }),
|
|
949
|
+
...(opts["first"] === true && { position: "first" }),
|
|
950
|
+
...(opts["last"] === true && { position: "last" }),
|
|
758
951
|
}, writeOptionsFrom(opts)));
|
|
759
952
|
});
|
|
760
953
|
addWriteFlags(area
|
|
761
|
-
.command("delete <
|
|
954
|
+
.command("delete <ref>")
|
|
762
955
|
.description("Delete an area PERMANENTLY — areas do not go to the Trash, so this cannot be " +
|
|
763
956
|
"undone; requires --dangerously-permanent. The area's to-dos move to the Trash; " +
|
|
764
957
|
"its projects remain, no longer assigned to any area.")
|
|
@@ -777,7 +970,7 @@ export function registerWriteCommands(program) {
|
|
|
777
970
|
await runWrite(opts, (c) => c.write.addTag({ title: name, ...(opts["parent"] !== undefined && { parent: opts["parent"] }) }, writeOptionsFrom(opts)));
|
|
778
971
|
});
|
|
779
972
|
addWriteFlags(tag
|
|
780
|
-
.command("update <
|
|
973
|
+
.command("update <ref>")
|
|
781
974
|
.description("Rename a tag (existing assignments follow the rename), nest it under an existing " +
|
|
782
975
|
"tag, UN-NEST it to the root (--unnest; exclusive with --parent), and set or clear " +
|
|
783
976
|
"its keyboard shortcut (--shortcut / --clear-shortcut, exclusive).")
|
|
@@ -791,18 +984,15 @@ export function registerWriteCommands(program) {
|
|
|
791
984
|
opts["unnest"] === undefined &&
|
|
792
985
|
opts["shortcut"] === undefined &&
|
|
793
986
|
opts["clearShortcut"] === undefined) {
|
|
794
|
-
|
|
795
|
-
process.exitCode = ExitCode.Usage;
|
|
987
|
+
usageError(opts, "pass --title, --parent, --unnest, --shortcut, and/or --clear-shortcut");
|
|
796
988
|
return;
|
|
797
989
|
}
|
|
798
990
|
if (opts["parent"] !== undefined && opts["unnest"] === true) {
|
|
799
|
-
|
|
800
|
-
process.exitCode = ExitCode.Usage;
|
|
991
|
+
usageError(opts, "--parent and --unnest are exclusive");
|
|
801
992
|
return;
|
|
802
993
|
}
|
|
803
994
|
if (opts["shortcut"] !== undefined && opts["clearShortcut"] === true) {
|
|
804
|
-
|
|
805
|
-
process.exitCode = ExitCode.Usage;
|
|
995
|
+
usageError(opts, "--shortcut and --clear-shortcut are exclusive");
|
|
806
996
|
return;
|
|
807
997
|
}
|
|
808
998
|
await runWrite(opts, (c) => c.write.updateTag(target, {
|
|
@@ -814,7 +1004,7 @@ export function registerWriteCommands(program) {
|
|
|
814
1004
|
}, writeOptionsFrom(opts)));
|
|
815
1005
|
});
|
|
816
1006
|
addWriteFlags(tag
|
|
817
|
-
.command("delete <
|
|
1007
|
+
.command("delete <ref>")
|
|
818
1008
|
.description("Delete a tag PERMANENTLY — tags do not go to the Trash, so this cannot be " +
|
|
819
1009
|
"undone; requires --dangerously-permanent. The tag is removed from every item, " +
|
|
820
1010
|
"and ALL of its nested child tags are deleted with it — requires " +
|
|
@@ -852,8 +1042,8 @@ export function registerWriteCommands(program) {
|
|
|
852
1042
|
"options carry the confirmation flags (acknowledgeChecklistReset, " +
|
|
853
1043
|
"acknowledgeProjectReopen, dangerouslyPermanent, acknowledgeTagSubtree). " +
|
|
854
1044
|
"--dry-run plans everything without executing; --fail-fast skips the rest after " +
|
|
855
|
-
"the first failure. Exit: 0 all ok · 3 any verify-failed/invalid
|
|
856
|
-
"· 5 any drift-blocked.")
|
|
1045
|
+
"the first failure. Exit (worst failure wins): 0 all ok · 3 any verify-failed/invalid " +
|
|
1046
|
+
"· 4 any blocked · 5 any drift-blocked · 6 any unsupported.")
|
|
857
1047
|
.option("--dry-run", "plan every op; execute nothing")
|
|
858
1048
|
.option("--fail-fast", "skip remaining ops after the first failure")
|
|
859
1049
|
.option("--json", "JSONL results + summary on stdout (also the default)")
|
|
@@ -894,9 +1084,6 @@ export function registerWriteCommands(program) {
|
|
|
894
1084
|
let client = null;
|
|
895
1085
|
try {
|
|
896
1086
|
client = openThings(opts.db ? { dbPath: opts.db } : {});
|
|
897
|
-
const emit = (r) => {
|
|
898
|
-
process.stdout.write(`${JSON.stringify(r)}\n`);
|
|
899
|
-
};
|
|
900
1087
|
const results = await client.write.batch(ops, {
|
|
901
1088
|
...(opts.dryRun !== undefined && { dryRun: opts.dryRun }),
|
|
902
1089
|
...(opts["failFast"] === true && { failFast: true }),
|
|
@@ -916,15 +1103,9 @@ export function registerWriteCommands(program) {
|
|
|
916
1103
|
},
|
|
917
1104
|
};
|
|
918
1105
|
process.stdout.write(`${JSON.stringify(summary)}\n`);
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
process.exitCode =
|
|
922
|
-
? ExitCode.DriftBlocked
|
|
923
|
-
: kinds.has("blocked")
|
|
924
|
-
? ExitCode.Blocked
|
|
925
|
-
: failed.length > 0
|
|
926
|
-
? ExitCode.VerifyFailed
|
|
927
|
-
: ExitCode.Ok;
|
|
1106
|
+
// Worst failure decides the exit code, by the stable precedence
|
|
1107
|
+
// drift > blocked > unsupported > verify-failed (see aggregateExitCode).
|
|
1108
|
+
process.exitCode = aggregateExitCode(failed.map((r) => r.outcome));
|
|
928
1109
|
}
|
|
929
1110
|
finally {
|
|
930
1111
|
client?.close();
|
|
@@ -934,28 +1115,54 @@ export function registerWriteCommands(program) {
|
|
|
934
1115
|
.command("undo")
|
|
935
1116
|
.description("Undo the last N changes made through things-api, newest first — each undo applies " +
|
|
936
1117
|
"the INVERSE change (recorded as actor `undo:<actor>`, never itself an undo " +
|
|
937
|
-
"target).
|
|
938
|
-
"
|
|
939
|
-
"
|
|
940
|
-
"
|
|
941
|
-
"
|
|
942
|
-
"
|
|
943
|
-
"
|
|
944
|
-
|
|
1118
|
+
"target). By default undo is GLOBAL — the owner's Cmd+Z — reversing the latest " +
|
|
1119
|
+
"changes whoever made them; narrow it with --by <actor> to undo only a given " +
|
|
1120
|
+
"author's changes (e.g. `--by mcp` to clean up after an agent), or --txn <token> " +
|
|
1121
|
+
"to undo one exact change by the `undoToken` its result returned (immune to any " +
|
|
1122
|
+
"changes made in between). Changes made directly in the Things app cannot be " +
|
|
1123
|
+
"undone here. IRREVERSIBLE changes are reported, not guessed: permanent deletes " +
|
|
1124
|
+
"and changes whose prior state is unknown. Partial restores carry notes (e.g. a " +
|
|
1125
|
+
"delete-undo lands in the Inbox de-scheduled). --dry-run shows every inverse plan " +
|
|
1126
|
+
"without executing. Undoing a CREATED area/tag deletes it permanently — requires " +
|
|
1127
|
+
"--dangerously-permanent. An undo is refused when the item changed outside things-api " +
|
|
1128
|
+
"since (its list/project, status, schedule, trashed state, or a field like the title " +
|
|
1129
|
+
"moved) — pass --acknowledge-out-of-band-changes to overwrite it anyway. Unwinding " +
|
|
1130
|
+
"stops at the first failed inverse. " +
|
|
1131
|
+
"Exit: 0 all ok · 3 any failed/partial · 2 nothing matched or bad flags.")
|
|
1132
|
+
.option("--last <n>", "how many trailing mutations to undo (default 1)")
|
|
1133
|
+
.option("--by <actor>", "undo only changes recorded under this author — an exact actor name (`mike`, `mcp`) " +
|
|
1134
|
+
"or `*` for all; matches exactly, so `--by mcp` never touches an `undo:mcp` " +
|
|
1135
|
+
"record. This SELECTS which changes to undo; --actor names who the undo is " +
|
|
1136
|
+
"recorded as. Not combinable with --txn.")
|
|
1137
|
+
.option("--txn <token>", "undo exactly the one change with this undo token (the `undoToken` field from its " +
|
|
1138
|
+
"result); immune to interleaving. Not combinable with --last/--by.")
|
|
945
1139
|
.option("--dry-run", "show the inverse plans; execute nothing")
|
|
946
1140
|
.option("--dangerously-permanent", "allow inverses that delete areas/tags permanently")
|
|
1141
|
+
.option("--acknowledge-out-of-band-changes", "proceed even when the item changed outside things-api since (in the Things app or by " +
|
|
1142
|
+
"another tool) — overwrites whatever the out-of-band change left")
|
|
947
1143
|
.option("--json", "JSONL per-item results + summary on stdout (also the default)")
|
|
948
1144
|
.option("--db <path>", "explicit database path")
|
|
949
1145
|
.option("--verify-timeout <ms>", "how long to wait for each inverse change to take effect")
|
|
950
|
-
.option("--actor <name>", "author name
|
|
1146
|
+
.option("--actor <name>", "author name RECORDED for the undo (as undo:<name>); see --by")
|
|
951
1147
|
.action(async (opts) => {
|
|
1148
|
+
// --txn selects one exact record; --last/--by select a set. Mixing them
|
|
1149
|
+
// is a usage error (house style).
|
|
1150
|
+
if (opts["txn"] !== undefined && (opts["last"] !== undefined || opts["by"] !== undefined)) {
|
|
1151
|
+
usageError(opts, "--txn cannot be combined with --last or --by");
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
952
1154
|
let client = null;
|
|
953
1155
|
try {
|
|
954
1156
|
client = openThings(opts.db ? { dbPath: opts.db } : {});
|
|
955
1157
|
const items = await client.write.undo({
|
|
956
|
-
last: Number(opts["last"]
|
|
1158
|
+
...(opts["last"] !== undefined && { last: Number(opts["last"]) }),
|
|
1159
|
+
...(opts["by"] !== undefined && { by: String(opts["by"]) }),
|
|
1160
|
+
...(opts["txn"] !== undefined && { txn: String(opts["txn"]) }),
|
|
957
1161
|
...(opts.dryRun !== undefined && { dryRun: opts.dryRun }),
|
|
958
1162
|
...(opts["dangerouslyPermanent"] === true && { dangerouslyPermanent: true }),
|
|
1163
|
+
...(opts["acknowledgeOutOfBandChanges"] === true && {
|
|
1164
|
+
acknowledgeOutOfBandChanges: true,
|
|
1165
|
+
}),
|
|
959
1166
|
...(opts.verifyTimeout !== undefined && {
|
|
960
1167
|
verifyTimeoutMs: Number(opts.verifyTimeout),
|
|
961
1168
|
}),
|
|
@@ -980,7 +1187,22 @@ export function registerWriteCommands(program) {
|
|
|
980
1187
|
? ExitCode.Usage
|
|
981
1188
|
: ExitCode.Ok;
|
|
982
1189
|
if (items.length === 0) {
|
|
983
|
-
|
|
1190
|
+
// The JSONL summary line (targets: 0) already conveys this as data on
|
|
1191
|
+
// stdout; the prose note is a human-only affordance on stderr.
|
|
1192
|
+
if (opts.json !== true) {
|
|
1193
|
+
const scope = opts["by"] !== undefined ? ` for actor ${String(opts["by"])}` : "";
|
|
1194
|
+
process.stderr.write(`error: no undoable mutations found in the audit trail${scope}\n`);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
catch (err) {
|
|
1199
|
+
// runUndo throws RangeError for a --txn token that names no undoable
|
|
1200
|
+
// mutation or one already undone — a usage error (exit 2).
|
|
1201
|
+
if (err instanceof RangeError) {
|
|
1202
|
+
usageError(opts, err.message);
|
|
1203
|
+
}
|
|
1204
|
+
else {
|
|
1205
|
+
throw err;
|
|
984
1206
|
}
|
|
985
1207
|
}
|
|
986
1208
|
finally {
|
|
@@ -1034,6 +1256,10 @@ export function registerWriteCommands(program) {
|
|
|
1034
1256
|
for (const entry of data) {
|
|
1035
1257
|
process.stdout.write(`${entry.op}\n`);
|
|
1036
1258
|
process.stdout.write(` undo: ${entry.undo.class}${entry.undo.ack !== undefined ? ` (ack: ${entry.undo.ack})` : ""} — ${entry.undo.note}\n`);
|
|
1259
|
+
if (entry.certification !== undefined) {
|
|
1260
|
+
process.stdout.write(` certification: ${entry.certification.status}` +
|
|
1261
|
+
`${entry.certification.evidence.length > 0 ? ` (${entry.certification.evidence.join(", ")})` : ""}\n`);
|
|
1262
|
+
}
|
|
1037
1263
|
for (const v of entry.vectors) {
|
|
1038
1264
|
const s = v;
|
|
1039
1265
|
process.stdout.write(` ${v.vector}: ${s.support}${s.disruption !== undefined ? ` (tier ${s.disruption}, ${s.validation})` : ""}${s.notes !== undefined ? ` — ${s.notes}` : ""}\n`);
|
|
@@ -1066,7 +1292,7 @@ export function registerWriteCommands(program) {
|
|
|
1066
1292
|
config
|
|
1067
1293
|
.command("set <key> <value>")
|
|
1068
1294
|
.description("Persist a config key: profile | maxDisruption | actor | auditEnabled | " +
|
|
1069
|
-
"accepted-fingerprint | allow-experimental")
|
|
1295
|
+
"accepted-fingerprint | allow-experimental | ui-enabled")
|
|
1070
1296
|
.action((key, value) => {
|
|
1071
1297
|
const map = {
|
|
1072
1298
|
profile: "profile",
|
|
@@ -1075,6 +1301,7 @@ export function registerWriteCommands(program) {
|
|
|
1075
1301
|
auditEnabled: "auditEnabled",
|
|
1076
1302
|
"accepted-fingerprint": "acceptedFingerprint",
|
|
1077
1303
|
"allow-experimental": "allowExperimental",
|
|
1304
|
+
"ui-enabled": "uiEnabled",
|
|
1078
1305
|
};
|
|
1079
1306
|
const target = map[key];
|
|
1080
1307
|
if (target === undefined) {
|
|
@@ -1084,7 +1311,7 @@ export function registerWriteCommands(program) {
|
|
|
1084
1311
|
}
|
|
1085
1312
|
const parsed = target === "maxDisruption"
|
|
1086
1313
|
? Number(value)
|
|
1087
|
-
: target === "auditEnabled" || target === "allowExperimental"
|
|
1314
|
+
: target === "auditEnabled" || target === "allowExperimental" || target === "uiEnabled"
|
|
1088
1315
|
? value === "true"
|
|
1089
1316
|
: value;
|
|
1090
1317
|
saveConfigKey(target, parsed);
|