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
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the bundled agent skill (`skills/things-cli/`): where the
|
|
3
|
+
* package copy lives, where an installed copy lands, and how to read the
|
|
4
|
+
* version stamp both carry. Used by `things install-skill` (distribution) and
|
|
5
|
+
* the passive CLI drift notice.
|
|
6
|
+
*
|
|
7
|
+
* Pure CLI-surface support — node builtins only, no library internals — so it
|
|
8
|
+
* respects the consumer air gap (AGENTS.md, docs/design/architecture.md).
|
|
9
|
+
*/
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
14
|
+
/** The skill's canonical name — its directory basename in every install root. */
|
|
15
|
+
export const SKILL_NAME = "things-cli";
|
|
16
|
+
/**
|
|
17
|
+
* The bundled skill directory inside THIS package. Resolved from the module's
|
|
18
|
+
* own location (three levels above src/cli/ AND dist/cli/), so it works under
|
|
19
|
+
* every package-manager layout — npm, pnpm, bun, npx — with no PATH heuristics.
|
|
20
|
+
*/
|
|
21
|
+
export function bundledSkillDir() {
|
|
22
|
+
return fileURLToPath(new URL(`../../skills/${SKILL_NAME}`, import.meta.url));
|
|
23
|
+
}
|
|
24
|
+
/** The home directory to resolve `~/.agents` etc. against (env override wins). */
|
|
25
|
+
export function resolveHome(env = process.env) {
|
|
26
|
+
const h = env["HOME"];
|
|
27
|
+
return h !== undefined && h !== "" ? h : homedir();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The well-known install locations we can place and read without the `skills`
|
|
31
|
+
* CLI's harness detection. The canonical `~/.agents` root comes first — agent
|
|
32
|
+
* dirs may be symlinks to it, and it is where the drift check looks before the
|
|
33
|
+
* agent-specific fallbacks.
|
|
34
|
+
*/
|
|
35
|
+
export function skillLocations(home) {
|
|
36
|
+
return [
|
|
37
|
+
{
|
|
38
|
+
label: "canonical (~/.agents)",
|
|
39
|
+
dir: join(home, ".agents", "skills", SKILL_NAME),
|
|
40
|
+
canonical: true,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
label: "Claude (~/.claude)",
|
|
44
|
+
dir: join(home, ".claude", "skills", SKILL_NAME),
|
|
45
|
+
canonical: false,
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Parse the `version:` stamp from a SKILL.md's YAML frontmatter. Returns the
|
|
51
|
+
* trimmed value, or null when there is no frontmatter or no version key.
|
|
52
|
+
* Deliberately tiny (no YAML dependency): the frontmatter is a flat key list.
|
|
53
|
+
*/
|
|
54
|
+
export function parseSkillVersion(md) {
|
|
55
|
+
if (!md.startsWith("---"))
|
|
56
|
+
return null;
|
|
57
|
+
const end = md.indexOf("\n---", 3);
|
|
58
|
+
if (end === -1)
|
|
59
|
+
return null;
|
|
60
|
+
const front = md.slice(0, end);
|
|
61
|
+
for (const line of front.split("\n")) {
|
|
62
|
+
const m = /^version:\s*(.+?)\s*$/.exec(line);
|
|
63
|
+
if (m && m[1] !== undefined)
|
|
64
|
+
return m[1].replace(/^["']|["']$/g, "");
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Rewrite the `version:` line in a SKILL.md's frontmatter to `version`. Pure
|
|
70
|
+
* string transform used by the publish-time stamp (scripts/stamp-skill.mjs).
|
|
71
|
+
* Throws when there is no frontmatter or no `version:` key — the source file is
|
|
72
|
+
* expected to carry the `0.0.0-dev` slot.
|
|
73
|
+
*/
|
|
74
|
+
export function stampSkillVersion(md, version) {
|
|
75
|
+
const end = md.indexOf("\n---", 3);
|
|
76
|
+
if (!md.startsWith("---") || end === -1)
|
|
77
|
+
throw new Error("SKILL.md has no frontmatter");
|
|
78
|
+
const front = md.slice(0, end);
|
|
79
|
+
const rest = md.slice(end);
|
|
80
|
+
if (!/^version:\s*.+$/m.test(front))
|
|
81
|
+
throw new Error("SKILL.md frontmatter has no 'version:' key");
|
|
82
|
+
return front.replace(/^version:\s*.+$/m, `version: ${version}`) + rest;
|
|
83
|
+
}
|
|
84
|
+
/** Read the version stamp from a SKILL.md file, or null if absent/unreadable. */
|
|
85
|
+
export function readSkillVersion(skillMdPath) {
|
|
86
|
+
try {
|
|
87
|
+
return parseSkillVersion(readFileSync(skillMdPath, "utf8"));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** The bundled SKILL.md's version stamp (what a fresh install would carry). */
|
|
94
|
+
export function bundledSkillVersion() {
|
|
95
|
+
return readSkillVersion(join(bundledSkillDir(), "SKILL.md"));
|
|
96
|
+
}
|
|
97
|
+
/** The stamp of the installed skill at `dir`, or null when nothing is there. */
|
|
98
|
+
export function installedSkillVersion(dir) {
|
|
99
|
+
const md = join(dir, "SKILL.md");
|
|
100
|
+
if (!existsSync(md))
|
|
101
|
+
return null;
|
|
102
|
+
return readSkillVersion(md);
|
|
103
|
+
}
|
|
104
|
+
/** Parse `X.Y.Z` (suffix tolerated), or null when it is not a `X.Y.Z` stamp. */
|
|
105
|
+
export function parseSemver(v) {
|
|
106
|
+
if (v === null)
|
|
107
|
+
return null;
|
|
108
|
+
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
|
|
109
|
+
if (!m)
|
|
110
|
+
return null;
|
|
111
|
+
return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]) };
|
|
112
|
+
}
|
|
113
|
+
/** -1 / 0 / +1 for a<b / a==b / a>b; null when either is not a `X.Y.Z` stamp. */
|
|
114
|
+
export function compareSemver(a, b) {
|
|
115
|
+
const pa = parseSemver(a);
|
|
116
|
+
const pb = parseSemver(b);
|
|
117
|
+
if (pa === null || pb === null)
|
|
118
|
+
return null;
|
|
119
|
+
if (pa.major !== pb.major)
|
|
120
|
+
return pa.major < pb.major ? -1 : 1;
|
|
121
|
+
if (pa.minor !== pb.minor)
|
|
122
|
+
return pa.minor < pb.minor ? -1 : 1;
|
|
123
|
+
if (pa.patch !== pb.patch)
|
|
124
|
+
return pa.patch < pb.patch ? -1 : 1;
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* True when `installed` is a MINOR version or more behind `bundled` — i.e. their
|
|
129
|
+
* `(major, minor)` differs and bundled is the newer pair. Patch-only drift is
|
|
130
|
+
* NOT behind (too small to nudge). False when either is not a `X.Y.Z` stamp, so
|
|
131
|
+
* a dev build (`0.0.0-dev` → 0.0.0) never flags anything as behind.
|
|
132
|
+
*/
|
|
133
|
+
export function isMinorBehind(installed, bundled) {
|
|
134
|
+
const pi = parseSemver(installed);
|
|
135
|
+
const pb = parseSemver(bundled);
|
|
136
|
+
if (pi === null || pb === null)
|
|
137
|
+
return false;
|
|
138
|
+
if (pb.major !== pi.major)
|
|
139
|
+
return pb.major > pi.major;
|
|
140
|
+
return pb.minor > pi.minor;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=skill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill.js","sourceRoot":"","sources":["../../src/cli/skill.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three universal tag-filter flags (`--tag`, `--exact-tag`, `--untagged`)
|
|
3
|
+
* shared by every read command that accepts them — the flat list views
|
|
4
|
+
* (reads.ts) and the container views (project.ts, area.ts, show.ts). One place
|
|
5
|
+
* owns the help copy, the repeatable-collect, the mutual-exclusivity guard, and
|
|
6
|
+
* the mapping to a {@link ViewFilter}.
|
|
7
|
+
*
|
|
8
|
+
* Two axes live in `--tag`: (A) CONTAINER inheritance (an item inherits its
|
|
9
|
+
* project/area/heading tags) and (B) TAG-HIERARCHY descendant expansion
|
|
10
|
+
* (filtering `Parent` matches items tagged with a descendant). `--exact-tag`
|
|
11
|
+
* drops only (B). Axis (A) is view-dependent: FLAT views keep it (an
|
|
12
|
+
* inheritance-inclusive `--tag`); CONTAINER views (`project show`, `area show`,
|
|
13
|
+
* the `projects` list) drop it, since every child inherits the container's tags
|
|
14
|
+
* — there a `--tag` matches a DIRECT assignment (still descendant-expanded).
|
|
15
|
+
*/
|
|
16
|
+
import type { Command } from "commander";
|
|
17
|
+
import { hasTagPresence as hasTagPresenceShared, type TagPresence, type ViewFilter } from "../index.ts";
|
|
18
|
+
/** Commander repeatable-collect: accumulate each `--tag` value. */
|
|
19
|
+
export declare const collectRef: (value: string, previous: string[]) => string[];
|
|
20
|
+
/**
|
|
21
|
+
* Help copy for the tag-filter flags (shared verbatim across every view). Kept
|
|
22
|
+
* to a one-liner: the full inheritance/descendant model — the two axes and how
|
|
23
|
+
* they differ between flat and container views — lives in `things help filters`.
|
|
24
|
+
*/
|
|
25
|
+
export declare const TAG_DESC: string;
|
|
26
|
+
export declare const EXACT_TAG_DESC = "match the named tag(s) only \u2014 exclude hierarchy descendants";
|
|
27
|
+
export declare const UNTAGGED_DESC = "only items with no tag \u2014 the app's \"No Tag\" filter; see `things help filters`";
|
|
28
|
+
/**
|
|
29
|
+
* The `--help` footer appended to the container views (`project show`,
|
|
30
|
+
* `area show`, the `projects` list) explaining their `--tag` semantics: the
|
|
31
|
+
* filter matches a tag carried directly on the child, ignoring tags inherited
|
|
32
|
+
* from the container (every child inherits those, so an inheritance-inclusive
|
|
33
|
+
* match would be vacuous).
|
|
34
|
+
*/
|
|
35
|
+
export declare const CONTAINER_TAG_HINT: string;
|
|
36
|
+
/**
|
|
37
|
+
* The parsed shape of the three tag-filter flags on any tag-accepting view.
|
|
38
|
+
* A structural subtype of the shared {@link TagPresence}, so the CLI's parsed
|
|
39
|
+
* commander options feed the shared predicates without adaptation.
|
|
40
|
+
*/
|
|
41
|
+
export interface TagFlags extends TagPresence {
|
|
42
|
+
tag?: string[];
|
|
43
|
+
exactTag?: boolean;
|
|
44
|
+
untagged?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** Register the three universal tag-filter flags on a command, in help order. */
|
|
47
|
+
export declare function addTagFilterOptions(cmd: Command): Command;
|
|
48
|
+
/** True when any tag-PRESENCE flag was passed (a positive filter, not a negation). */
|
|
49
|
+
export declare const hasTagPresence: typeof hasTagPresenceShared;
|
|
50
|
+
/**
|
|
51
|
+
* Shared usage guard for the tag-filter flags. The negation (`--untagged`)
|
|
52
|
+
* inverts a tag presence, so it does not combine with a tag-presence flag.
|
|
53
|
+
* Emits the usage error (the view's conflict style) and returns true when it
|
|
54
|
+
* fires. The predicate is the shared {@link tagFlagConflictShared}; only the
|
|
55
|
+
* CLI-voiced copy and the `--json`-aware emit stay here.
|
|
56
|
+
*/
|
|
57
|
+
export declare function tagFlagConflict(opts: TagFlags & {
|
|
58
|
+
json?: boolean;
|
|
59
|
+
}): boolean;
|
|
60
|
+
/** The ViewFilter tag fields built from the parsed flags (empty keys omitted). */
|
|
61
|
+
export declare function tagFilterFields(opts: TagFlags): ViewFilter;
|
|
62
|
+
/** The invocation-echo fragments for the tag flags (one `--tag <ref>` per ref). */
|
|
63
|
+
export declare function tagInvocationParts(opts: TagFlags): Array<string | false>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { hasTagPresence as hasTagPresenceShared, tagFilterFields as tagFilterFieldsShared, tagFlagConflict as tagFlagConflictShared, } from "../index.js";
|
|
2
|
+
import { shellQuote, usageError } from "./read-driver.js";
|
|
3
|
+
/** Commander repeatable-collect: accumulate each `--tag` value. */
|
|
4
|
+
export const collectRef = (value, previous) => [...previous, value];
|
|
5
|
+
/**
|
|
6
|
+
* Help copy for the tag-filter flags (shared verbatim across every view). Kept
|
|
7
|
+
* to a one-liner: the full inheritance/descendant model — the two axes and how
|
|
8
|
+
* they differ between flat and container views — lives in `things help filters`.
|
|
9
|
+
*/
|
|
10
|
+
export const TAG_DESC = "filter by tag (uuid or unique name), repeatable — several AND together; " +
|
|
11
|
+
"see `things help filters`";
|
|
12
|
+
export const EXACT_TAG_DESC = "match the named tag(s) only — exclude hierarchy descendants";
|
|
13
|
+
export const UNTAGGED_DESC = 'only items with no tag — the app\'s "No Tag" filter; see `things help filters`';
|
|
14
|
+
/**
|
|
15
|
+
* The `--help` footer appended to the container views (`project show`,
|
|
16
|
+
* `area show`, the `projects` list) explaining their `--tag` semantics: the
|
|
17
|
+
* filter matches a tag carried directly on the child, ignoring tags inherited
|
|
18
|
+
* from the container (every child inherits those, so an inheritance-inclusive
|
|
19
|
+
* match would be vacuous).
|
|
20
|
+
*/
|
|
21
|
+
export const CONTAINER_TAG_HINT = "\n--tag / --untagged match a tag carried directly on the item, ignoring tags\n" +
|
|
22
|
+
"inherited from this container (every child inherits them). --exact-tag still\n" +
|
|
23
|
+
"drops hierarchy descendants.";
|
|
24
|
+
/** Register the three universal tag-filter flags on a command, in help order. */
|
|
25
|
+
export function addTagFilterOptions(cmd) {
|
|
26
|
+
return cmd
|
|
27
|
+
.option("--tag <ref>", TAG_DESC, collectRef, [])
|
|
28
|
+
.option("--exact-tag", EXACT_TAG_DESC)
|
|
29
|
+
.option("--untagged", UNTAGGED_DESC);
|
|
30
|
+
}
|
|
31
|
+
/** True when any tag-PRESENCE flag was passed (a positive filter, not a negation). */
|
|
32
|
+
export const hasTagPresence = hasTagPresenceShared;
|
|
33
|
+
/**
|
|
34
|
+
* Shared usage guard for the tag-filter flags. The negation (`--untagged`)
|
|
35
|
+
* inverts a tag presence, so it does not combine with a tag-presence flag.
|
|
36
|
+
* Emits the usage error (the view's conflict style) and returns true when it
|
|
37
|
+
* fires. The predicate is the shared {@link tagFlagConflictShared}; only the
|
|
38
|
+
* CLI-voiced copy and the `--json`-aware emit stay here.
|
|
39
|
+
*/
|
|
40
|
+
export function tagFlagConflict(opts) {
|
|
41
|
+
if (tagFlagConflictShared(opts)) {
|
|
42
|
+
usageError(opts, "--untagged does not combine with --tag/--exact-tag");
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
/** The ViewFilter tag fields built from the parsed flags (empty keys omitted). */
|
|
48
|
+
export function tagFilterFields(opts) {
|
|
49
|
+
return tagFilterFieldsShared(opts);
|
|
50
|
+
}
|
|
51
|
+
/** The invocation-echo fragments for the tag flags (one `--tag <ref>` per ref). */
|
|
52
|
+
export function tagInvocationParts(opts) {
|
|
53
|
+
return [
|
|
54
|
+
...(opts.tag ?? []).map((t) => `--tag ${shellQuote(t)}`),
|
|
55
|
+
opts.exactTag === true && "--exact-tag",
|
|
56
|
+
opts.untagged === true && "--untagged",
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=tag-filters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tag-filters.js","sourceRoot":"","sources":["../../src/cli/tag-filters.ts"],"names":[],"mappings":"AAiBA,OAAO,EACL,cAAc,IAAI,oBAAoB,EACtC,eAAe,IAAI,qBAAqB,EACxC,eAAe,IAAI,qBAAqB,GAGzC,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE1D,mEAAmE;AACnE,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAa,EAAE,QAAkB,EAAY,EAAE,CAAC,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,CAAC;AAEhG;;;;GAIG;AACH,MAAM,CAAC,MAAM,QAAQ,GACnB,0EAA0E;IAC1E,2BAA2B,CAAC;AAC9B,MAAM,CAAC,MAAM,cAAc,GAAG,6DAA6D,CAAC;AAC5F,MAAM,CAAC,MAAM,aAAa,GACxB,gFAAgF,CAAC;AAEnF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAC7B,gFAAgF;IAChF,gFAAgF;IAChF,8BAA8B,CAAC;AAajC,iFAAiF;AACjF,MAAM,UAAU,mBAAmB,CAAC,GAAY;IAC9C,OAAO,GAAG;SACP,MAAM,CAAC,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,CAAC;SAC/C,MAAM,CAAC,aAAa,EAAE,cAAc,CAAC;SACrC,MAAM,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;AACzC,CAAC;AAED,sFAAsF;AACtF,MAAM,CAAC,MAAM,cAAc,GAAG,oBAAoB,CAAC;AAEnD;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,IAAmC;IACjE,IAAI,qBAAqB,CAAC,IAAI,CAAC,EAAE,CAAC;QAChC,UAAU,CAAC,IAAI,EAAE,oDAAoD,CAAC,CAAC;QACvE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,eAAe,CAAC,IAAc;IAC5C,OAAO,qBAAqB,CAAC,IAAI,CAAC,CAAC;AACrC,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,kBAAkB,CAAC,IAAc;IAC/C,OAAO;QACL,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,IAAI,CAAC,QAAQ,KAAK,IAAI,IAAI,aAAa;QACvC,IAAI,CAAC,QAAQ,KAAK,IAAI,IAAI,YAAY;KACvC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bare-mutation-verb handler (docs/design/cli-grammar.md). A top-level
|
|
3
|
+
* `things <verb> …` whose first token is a write verb (update, add, delete, …)
|
|
4
|
+
* used to fall into the show-sugar and emit a confusing `things show` usage
|
|
5
|
+
* error. Instead the resolver flags it `verb-hint` and this handler suggests
|
|
6
|
+
* the namespaced write command:
|
|
7
|
+
*
|
|
8
|
+
* - a following ref that uniquely resolves as an area/project/to-do → one
|
|
9
|
+
* CONCRETE suggestion echoing the remaining args
|
|
10
|
+
* (`did you mean: things area update health --tags test`). The mutation is
|
|
11
|
+
* NEVER auto-run — the suggestion is the deliberate keystroke.
|
|
12
|
+
* - `add`/`create` with a title-like arg → `things todo add "<arg>"` first
|
|
13
|
+
* (the common intent), then the other namespaces.
|
|
14
|
+
* - no ref, an unresolvable ref, or an ambiguous ref → a hint listing the
|
|
15
|
+
* namespaced forms and pointing at `things help writes`.
|
|
16
|
+
*
|
|
17
|
+
* Exit class is Usage (exit 2), consistent with the other resolver errors;
|
|
18
|
+
* under `--json` the suggestions ride `error.details.suggestions`.
|
|
19
|
+
*/
|
|
20
|
+
import type { Command } from "commander";
|
|
21
|
+
/**
|
|
22
|
+
* Run the bare-verb hint: emit the suggestion(s) and set the Usage exit code.
|
|
23
|
+
* Never opens the write pipeline — this only reads to classify the ref.
|
|
24
|
+
*/
|
|
25
|
+
export declare function runVerbHint(program: Command, args: string[]): void;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { errorEnvelope, ExitCode, openThings } from "../index.js";
|
|
2
|
+
import { indexPastLeadingFlags, subcommandsOf, WRITE_GROUP_ORDER } from "./resolve-invocation.js";
|
|
3
|
+
import { shellQuote } from "./shell-quote.js";
|
|
4
|
+
/** The type-group a resolved reference maps to a write namespace by. */
|
|
5
|
+
const KIND_GROUP = { "to-do": "todo", project: "project", area: "area" };
|
|
6
|
+
/** Split the raw argv into verb, ref, echo-tail, and the global read opts. */
|
|
7
|
+
function parse(args) {
|
|
8
|
+
const at = indexPastLeadingFlags(args) ?? 0;
|
|
9
|
+
const verb = args[at] ?? "";
|
|
10
|
+
const after = args.slice(at + 1);
|
|
11
|
+
let json = false;
|
|
12
|
+
let db;
|
|
13
|
+
let ref = null;
|
|
14
|
+
const tail = [];
|
|
15
|
+
for (let i = 0; i < after.length; i++) {
|
|
16
|
+
const tok = after[i] ?? "";
|
|
17
|
+
if (tok === "--json") {
|
|
18
|
+
json = true;
|
|
19
|
+
}
|
|
20
|
+
else if (tok === "--db") {
|
|
21
|
+
db = after[++i];
|
|
22
|
+
}
|
|
23
|
+
else if (tok.startsWith("--db=")) {
|
|
24
|
+
db = tok.slice("--db=".length);
|
|
25
|
+
}
|
|
26
|
+
else if (!tok.startsWith("-") && ref === null) {
|
|
27
|
+
ref = tok; // the first positional is the write target
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
tail.push(tok);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// A leading --json/--db (before the verb) still governs output/target.
|
|
34
|
+
for (let i = 0; i < at; i++) {
|
|
35
|
+
const tok = args[i] ?? "";
|
|
36
|
+
if (tok === "--json")
|
|
37
|
+
json = true;
|
|
38
|
+
else if (tok === "--db")
|
|
39
|
+
db = args[++i];
|
|
40
|
+
else if (tok.startsWith("--db="))
|
|
41
|
+
db = tok.slice("--db=".length);
|
|
42
|
+
}
|
|
43
|
+
return { verb, ref, tail, json, db };
|
|
44
|
+
}
|
|
45
|
+
/** Build `things <group> <verb> <ref?> <trailing flags…>` for the suggestion echo. */
|
|
46
|
+
function typedForm(group, verb, ref, tail) {
|
|
47
|
+
const parts = ["things", group, verb, ...(ref !== null ? [shellQuote(ref)] : []), ...tail];
|
|
48
|
+
return parts.join(" ");
|
|
49
|
+
}
|
|
50
|
+
/** The write groups (in a stable order) whose registered verbs include `verb`. */
|
|
51
|
+
function groupsWithVerb(program, verb) {
|
|
52
|
+
return WRITE_GROUP_ORDER.filter((g) => subcommandsOf(program, g).has(verb));
|
|
53
|
+
}
|
|
54
|
+
/** Container flags a `move` accepts — its destination is never a positional. */
|
|
55
|
+
const MOVE_CONTAINER_FLAGS = new Set(["--area", "--project", "--heading", "--inbox"]);
|
|
56
|
+
/**
|
|
57
|
+
* `move` takes its destination as a FLAG (--area/--project/--heading), never as
|
|
58
|
+
* a second positional. Left alone, the generic echo turns `things move X Home`
|
|
59
|
+
* into the suggestion `things todo move X Home` — which is itself a usage error.
|
|
60
|
+
* When the echo tail carries a stray positional destination and no container
|
|
61
|
+
* flag, rewrite it behind the flag matching the destination's kind (falling back
|
|
62
|
+
* to --project when it does not resolve) so the suggestion is a command that
|
|
63
|
+
* actually runs. A tail that already uses a container flag is echoed unchanged.
|
|
64
|
+
*/
|
|
65
|
+
function moveEchoTail(p) {
|
|
66
|
+
if (p.tail.some((t) => MOVE_CONTAINER_FLAGS.has(t)))
|
|
67
|
+
return p.tail;
|
|
68
|
+
const destIdx = p.tail.findIndex((t) => !t.startsWith("-"));
|
|
69
|
+
if (destIdx < 0)
|
|
70
|
+
return p.tail;
|
|
71
|
+
const dest = p.tail[destIdx] ?? "";
|
|
72
|
+
const rest = p.tail.filter((_, i) => i !== destIdx);
|
|
73
|
+
const flag = resolvedGroup(p.db, dest) === "area" ? "--area" : "--project";
|
|
74
|
+
return [flag, shellQuote(dest), ...rest];
|
|
75
|
+
}
|
|
76
|
+
/** Classify the ref's type with the read client; null when it does not uniquely resolve. */
|
|
77
|
+
function resolvedGroup(db, ref) {
|
|
78
|
+
let client = null;
|
|
79
|
+
try {
|
|
80
|
+
client = openThings(db ? { dbPath: db } : {});
|
|
81
|
+
const target = client.read.showTarget(ref);
|
|
82
|
+
return KIND_GROUP[target.kind] ?? null;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Ambiguous, unresolvable, or an unreadable db all fall to the generic hint.
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
client?.close();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Compose the suggestion set for a bare verb. The first line is the best guess;
|
|
94
|
+
* the rest are the other namespaced forms worth offering.
|
|
95
|
+
*/
|
|
96
|
+
function suggestionsFor(program, p) {
|
|
97
|
+
const verbLower = p.verb.toLowerCase();
|
|
98
|
+
// add/create: the target is a NEW title, so a name never "resolves" — offer
|
|
99
|
+
// to-do first (the common intent), then the container namespaces.
|
|
100
|
+
if (verbLower === "add" || verbLower === "create") {
|
|
101
|
+
if (p.ref === null)
|
|
102
|
+
return [];
|
|
103
|
+
return [
|
|
104
|
+
typedForm("todo", "add", p.ref, p.tail),
|
|
105
|
+
typedForm("project", "add", p.ref, p.tail),
|
|
106
|
+
typedForm("area", "add", p.ref, p.tail),
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
const applicable = groupsWithVerb(program, verbLower);
|
|
110
|
+
// `move`'s destination is a flag, not a positional — rewrite a stray
|
|
111
|
+
// positional destination so every suggestion below is a runnable command.
|
|
112
|
+
const echoTail = verbLower === "move" ? moveEchoTail(p) : p.tail;
|
|
113
|
+
// A ref that uniquely resolves to an area/project/to-do gets ONE concrete
|
|
114
|
+
// suggestion — but only when that type actually offers the verb.
|
|
115
|
+
if (p.ref !== null) {
|
|
116
|
+
const group = resolvedGroup(p.db, p.ref);
|
|
117
|
+
if (group !== null && applicable.includes(group)) {
|
|
118
|
+
return [typedForm(group, p.verb, p.ref, echoTail)];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Generic: every namespace that offers the verb (fall back to all write
|
|
122
|
+
// groups if the verb is a synonym none register directly).
|
|
123
|
+
const groups = applicable.length > 0 ? applicable : WRITE_GROUP_ORDER;
|
|
124
|
+
return groups.map((g) => typedForm(g, p.verb, p.ref, echoTail));
|
|
125
|
+
}
|
|
126
|
+
/** Human render: the error line, the suggestion(s), and the writes signpost. */
|
|
127
|
+
function renderHuman(verb, suggestions) {
|
|
128
|
+
const lines = [];
|
|
129
|
+
if (suggestions.length === 0) {
|
|
130
|
+
lines.push(`error: \`things ${verb}\` is a write verb but names no target — writes are namespaced.`);
|
|
131
|
+
}
|
|
132
|
+
else if (suggestions.length === 1) {
|
|
133
|
+
lines.push(`error: \`things ${verb} …\` is not a command — did you mean:`);
|
|
134
|
+
lines.push(` ${suggestions[0]}`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
lines.push(`error: \`things ${verb} …\` is not a command — writes are namespaced. Try:`);
|
|
138
|
+
for (const s of suggestions)
|
|
139
|
+
lines.push(` ${s}`);
|
|
140
|
+
}
|
|
141
|
+
lines.push("", "See `things help writes` for the full write grammar.");
|
|
142
|
+
return lines.join("\n");
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Run the bare-verb hint: emit the suggestion(s) and set the Usage exit code.
|
|
146
|
+
* Never opens the write pipeline — this only reads to classify the ref.
|
|
147
|
+
*/
|
|
148
|
+
export function runVerbHint(program, args) {
|
|
149
|
+
const p = parse(args);
|
|
150
|
+
const suggestions = suggestionsFor(program, p);
|
|
151
|
+
if (p.json) {
|
|
152
|
+
const meta = { dbVersion: null, fingerprint: "unknown", elapsedMs: 0 };
|
|
153
|
+
const message = suggestions.length === 1
|
|
154
|
+
? `\`things ${p.verb} …\` is not a command — did you mean \`${suggestions[0]}\``
|
|
155
|
+
: `\`things ${p.verb} …\` is not a command — writes are namespaced (see \`things help writes\`)`;
|
|
156
|
+
process.stdout.write(`${JSON.stringify(errorEnvelope({ code: "usage", message, details: { suggestions } }, meta))}\n`);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
process.stderr.write(`${renderHuman(p.verb, suggestions)}\n`);
|
|
160
|
+
}
|
|
161
|
+
process.exitCode = ExitCode.Usage;
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=verb-hint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verb-hint.js","sourceRoot":"","sources":["../../src/cli/verb-hint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** `${baseVersion}-dev` when running live TS source, else the plain version. */
|
|
2
|
+
export declare function resolveCliVersion(moduleUrl: string, baseVersion: string): string;
|
|
3
|
+
/** The version `things --version` and the `--help` footer report. */
|
|
4
|
+
export declare const CLI_VERSION: string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CLI version string, marked `-dev` when running from a source checkout.
|
|
3
|
+
*
|
|
4
|
+
* `bin/things.js` loads live TS source (`src/cli/main.ts`) in a dev checkout
|
|
5
|
+
* (npm link / `node bin/things.js` in the repo) and the built `dist/cli/main.js`
|
|
6
|
+
* in a published install. So inside these modules `import.meta.url` ends in
|
|
7
|
+
* `.ts` when live source is running and `.js` when the published build is —
|
|
8
|
+
* which is exactly the signal that tells the two apart. Published installs are
|
|
9
|
+
* never affected: the suffix appears only when the running module is TS.
|
|
10
|
+
*/
|
|
11
|
+
import { PKG_VERSION } from "../index.js";
|
|
12
|
+
/** `${baseVersion}-dev` when running live TS source, else the plain version. */
|
|
13
|
+
export function resolveCliVersion(moduleUrl, baseVersion) {
|
|
14
|
+
return moduleUrl.endsWith(".ts") ? `${baseVersion}-dev` : baseVersion;
|
|
15
|
+
}
|
|
16
|
+
/** The version `things --version` and the `--help` footer report. */
|
|
17
|
+
export const CLI_VERSION = resolveCliVersion(import.meta.url, PKG_VERSION);
|
|
18
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/cli/version.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,gFAAgF;AAChF,MAAM,UAAU,iBAAiB,CAAC,SAAiB,EAAE,WAAmB;IACtE,OAAO,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC;AACxE,CAAC;AAED,qEAAqE;AACrE,MAAM,CAAC,MAAM,WAAW,GAAG,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Width-aware TTY row fitting (docs/design/width-aware-tty.md). Terminal list
|
|
3
|
+
* rows never wrap: like the Things GUI, an over-long row truncates its title
|
|
4
|
+
* with a trailing `…` and folds its tags, keeping the always-present metadata
|
|
5
|
+
* (uuid, box, chips, tail markers, the full deadline token) intact. Everything
|
|
6
|
+
* here is pure and takes the target width explicitly, so it is unit-testable
|
|
7
|
+
* without a TTY and byte-stable off one: fitting engages ONLY when the driver
|
|
8
|
+
* resolves a positive width (setFitWidth) — the default is null (no fitting),
|
|
9
|
+
* so pipes, grep, and `--json` are untouched by construction.
|
|
10
|
+
*
|
|
11
|
+
* No runtime dependency (guest e2e bundles ship node + dist + commander only),
|
|
12
|
+
* so the display-width math is a small vendored wcwidth: SGR escapes strip to
|
|
13
|
+
* zero, East-Asian-Wide + emoji-presentation codepoints count two cells, the
|
|
14
|
+
* rest count one. ZWJ-sequence emoji can be off by a cell (accepted — worst
|
|
15
|
+
* case a row runs a hair short); the glyph vocabulary (★ ⏾ ⚑ ↻ ⍾ ≡ ◷) is all
|
|
16
|
+
* ambiguous/narrow class and counts one, so the layout math matches the render.
|
|
17
|
+
*/
|
|
18
|
+
/** Drop every SGR (color/dim/bold/…) escape, leaving the visible characters. */
|
|
19
|
+
export declare function stripSgr(s: string): string;
|
|
20
|
+
/** Terminal cells a single codepoint occupies: 0 (combining/zero-width), 2 (wide), else 1. */
|
|
21
|
+
export declare function charWidth(cp: number): number;
|
|
22
|
+
/** Visible terminal width of a string: SGR-stripped, summed per codepoint. */
|
|
23
|
+
export declare function visibleWidth(s: string): number;
|
|
24
|
+
/**
|
|
25
|
+
* Truncate PLAIN text (no SGR) to at most `cols` terminal cells, never splitting
|
|
26
|
+
* a wide codepoint across the boundary. Used to clip a raw title before its
|
|
27
|
+
* styling wrappers are re-applied, so the ellipsis/clip boundary lands OUTSIDE
|
|
28
|
+
* the SGR runs (the fitter never cuts an escape sequence).
|
|
29
|
+
*/
|
|
30
|
+
export declare function clipPlain(s: string, cols: number): string;
|
|
31
|
+
/**
|
|
32
|
+
* The title's protected minimum, in columns. NOT a per-row clamp: it is the
|
|
33
|
+
* input to the derived floors (render.ts FULL_FIT_FLOOR / COMPACT_FIT_FLOOR =
|
|
34
|
+
* worst-case furniture + TITLE_MIN) and the threshold the sacrifice order
|
|
35
|
+
* protects the title down to. Because the driver fits every row to at least the
|
|
36
|
+
* compact floor (max(width, COMPACT_FIT_FLOOR)),
|
|
37
|
+
* the worst-furniture row's title lands at exactly TITLE_MIN and every lighter
|
|
38
|
+
* row's title is automatically wider — so this floor is always satisfiable and
|
|
39
|
+
* there is no sub-floor clip (below the floor the terminal wraps, losing nothing).
|
|
40
|
+
*/
|
|
41
|
+
export declare const TITLE_MIN = 16;
|
|
42
|
+
/**
|
|
43
|
+
* The named, ordered stages of a row's collapse — the ratified sacrifice order
|
|
44
|
+
* (docs/design/width-aware-tty.md, GUI-measured oracle + the CLI-only container
|
|
45
|
+
* rule). Reordering the sacrifice is editing this list plus {@link fitRow}.
|
|
46
|
+
*/
|
|
47
|
+
export type FitStage = "full" | "both-shrink" | "container-drop" | "tags-bare";
|
|
48
|
+
/**
|
|
49
|
+
* The segments of a row the fitter composes. Fixed parts are pre-styled strings
|
|
50
|
+
* (measured, never altered); the two collapsible parts arrive as raw material
|
|
51
|
+
* plus their styling closures so truncation happens on plain text and the SGR
|
|
52
|
+
* wraps the result:
|
|
53
|
+
* - `left` uuid column + box + meta chips (measured; the space before the
|
|
54
|
+
* title is added by the fitter)
|
|
55
|
+
* - `rawTitle` + `styleTitle` — the plain title text and its wrapper
|
|
56
|
+
* - `tail` trailing markers (count chip, ◷ ≡ ≔) — pre-styled incl. its
|
|
57
|
+
* leading space, or ""
|
|
58
|
+
* - `tagNames` raw tag names (no `#`) + `styleTags` wrapping a `#a #b` form
|
|
59
|
+
* (incl. the leading space) — the fitter folds the list
|
|
60
|
+
* - `context` the ` (container)` suffix — pre-styled incl. leading space, or ""
|
|
61
|
+
* - `deadline` the full ⚑ token — pre-styled incl. leading space, or ""
|
|
62
|
+
* - `full` the fully-composed row (the byte-identical no-fit output)
|
|
63
|
+
*/
|
|
64
|
+
export interface RowSegments {
|
|
65
|
+
left: string;
|
|
66
|
+
rawTitle: string;
|
|
67
|
+
styleTitle: (text: string) => string;
|
|
68
|
+
tail: string;
|
|
69
|
+
tagNames: string[];
|
|
70
|
+
styleTags: (form: string) => string;
|
|
71
|
+
context: string;
|
|
72
|
+
deadline: string;
|
|
73
|
+
full: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Fit one row into `width` terminal cells per the ratified collapse order. The
|
|
77
|
+
* always-present parts (uuid, box, meta chips, tail markers, full deadline)
|
|
78
|
+
* never shrink; title and tags divvy up what remains LAZILY — tags fold to the
|
|
79
|
+
* widest progressive level that fits and the 4:1 ratio arbitrates only when both
|
|
80
|
+
* genuinely contend (slack an under-share side leaves flows to the other). Then
|
|
81
|
+
* the CLI-only container is sacrificed WHOLE (before the last tag folds), then
|
|
82
|
+
* the last tag folds to bare `#…`, and the title bottoms at TITLE_MIN. `width` is
|
|
83
|
+
* the caller's effective width (already max'd with COMPACT_FIT_FLOOR), so the final
|
|
84
|
+
* stage always satisfies the floor; a sub-floor width would simply let the
|
|
85
|
+
* terminal wrap (the title is kept whole rather than clipped). A row that already
|
|
86
|
+
* fits keeps its full content; the only always-applied transform is the
|
|
87
|
+
* experimental deadline gutter ({@link alignDeadline}), which right-pins the `⚑`.
|
|
88
|
+
*/
|
|
89
|
+
export declare function fitRow(seg: RowSegments, width: number): string;
|
|
90
|
+
/**
|
|
91
|
+
* The resolved fit for a view: the effective width every row is fitted to, plus
|
|
92
|
+
* whether deadline tokens render in the COMPACT form (the Things iOS narrow-
|
|
93
|
+
* width oracle — `8/12`, `58d ago` — docs/design/width-aware-tty.md § Compact
|
|
94
|
+
* deadline forms). `compact` is decided ONCE from the effective width against
|
|
95
|
+
* the two derived floors, never per row, so a view's right-pinned gutter never
|
|
96
|
+
* mixes `Aug 12` and `8/12`.
|
|
97
|
+
*/
|
|
98
|
+
export interface EffectiveFit {
|
|
99
|
+
/** Width to fit each row to — the raw fit width clamped up to the compact floor. */
|
|
100
|
+
width: number;
|
|
101
|
+
/** True when the effective width sits below the full-form floor. */
|
|
102
|
+
compact: boolean;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Resolve the effective fit from the once-resolved fit width and the two
|
|
106
|
+
* glyph-derived floors (`render.ts`: FULL_FIT_FLOOR / COMPACT_FIT_FLOOR). This
|
|
107
|
+
* is the SINGLE decision point for the full-vs-compact deadline form:
|
|
108
|
+
* - `width ≥ full` → full forms, fit to width (today's behavior);
|
|
109
|
+
* - `compact ≤ width < full` → all deadlines compact, fit to width;
|
|
110
|
+
* - `width < compact` → clamp to the compact floor and wrap (sub-floor
|
|
111
|
+
* rule at the lower floor), still compact.
|
|
112
|
+
* The floors live in render.ts because their derivation needs the glyph
|
|
113
|
+
* inventory; the branching lives here so it is pure and unit-testable.
|
|
114
|
+
*/
|
|
115
|
+
export declare function resolveFit(fitWidth: number, fullFloor: number, compactFloor: number): EffectiveFit;
|
|
116
|
+
/**
|
|
117
|
+
* Set the process-wide fit width (columns), resolved ONCE by the CLI driver at
|
|
118
|
+
* startup. `null` disables fitting (the default — every non-TTY path stays
|
|
119
|
+
* byte-stable). Tests set this explicitly and reset it afterward.
|
|
120
|
+
*/
|
|
121
|
+
export declare function setFitWidth(width: number | null): void;
|
|
122
|
+
/** The current fit width, or null when fitting is disabled. */
|
|
123
|
+
export declare function getFitWidth(): number | null;
|
|
124
|
+
/**
|
|
125
|
+
* Resolve the fit width from the environment and stdout: `THINGS_WIDTH` wins
|
|
126
|
+
* when set (a positive integer forces that width; `0` disables fitting), else
|
|
127
|
+
* the terminal's column count when stdout is a TTY, else null (pipes, grep,
|
|
128
|
+
* captured output — no fitting, byte-stable). A malformed `THINGS_WIDTH` is
|
|
129
|
+
* ignored (falls through to the TTY/null resolution).
|
|
130
|
+
*/
|
|
131
|
+
export declare function resolveWidth(opts: {
|
|
132
|
+
env: Record<string, string | undefined>;
|
|
133
|
+
columns: number | undefined;
|
|
134
|
+
isTTY: boolean;
|
|
135
|
+
}): number | null;
|