@harborppm/cli 0.72.0 → 0.74.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 +13 -5
- package/dist/app.mjs +767 -919
- package/lib/cli.mjs +74 -11
- package/lib/format.mjs +3 -1
- package/package.json +1 -1
package/lib/cli.mjs
CHANGED
|
@@ -112,7 +112,8 @@ export const HELP = `harbor: Harbor from the terminal
|
|
|
112
112
|
harbor hill [--program P] where each project is on its hill
|
|
113
113
|
harbor hill PROJECT --at 0-100|top|done move it: up to 50 is figuring it
|
|
114
114
|
out, past 50 is getting it done
|
|
115
|
-
harbor mine what's on you, late first
|
|
115
|
+
harbor mine what's on you, late first, and
|
|
116
|
+
what you're also on
|
|
116
117
|
harbor catchup [--since DATE] while you were away: handed to
|
|
117
118
|
you, decided, what moved
|
|
118
119
|
harbor clock [NAME] [--zone ZONE|here|none] [--hours 9-17]
|
|
@@ -123,8 +124,11 @@ export const HELP = `harbor: Harbor from the terminal
|
|
|
123
124
|
the first 20 (--all: every one);
|
|
124
125
|
done ones only with --status done
|
|
125
126
|
harbor show TASK one task (TASK: a key like RR-42),
|
|
126
|
-
|
|
127
|
-
suggests
|
|
127
|
+
who's on it, its files, and the
|
|
128
|
+
date its owner's queue suggests
|
|
129
|
+
harbor files PROJECT [--kind image|doc|link]
|
|
130
|
+
every file on a project's tasks,
|
|
131
|
+
newest first
|
|
128
132
|
harbor open [TASK|PROJECT] open it on the website (or Today)
|
|
129
133
|
harbor invite EMAIL [--viewer] invite someone to your harbor
|
|
130
134
|
harbor prompt [--shell SHELL] [--refresh] due today and late, for your
|
|
@@ -157,7 +161,8 @@ export const HELP = `harbor: Harbor from the terminal
|
|
|
157
161
|
--from: plan it from a finished
|
|
158
162
|
project as that one really ran
|
|
159
163
|
harbor add "TITLE" [--project P] [--assignee me|none|NAME]
|
|
160
|
-
[--
|
|
164
|
+
[--also "NAME, NAME"] [--due DATE|pilot]
|
|
165
|
+
[--priority low|medium|high|urgent]
|
|
161
166
|
[--status S] [--comment TEXT] [--over-limit] [--dry-run]
|
|
162
167
|
with no TITLE: one title a line
|
|
163
168
|
from stdin; --due pilot takes the
|
|
@@ -167,7 +172,7 @@ export const HELP = `harbor: Harbor from the terminal
|
|
|
167
172
|
move a task to In play, In review
|
|
168
173
|
or Done
|
|
169
174
|
harbor edit TASK [--title T] [--assignee me|none|NAME]
|
|
170
|
-
[--due DATE|pilot|none]
|
|
175
|
+
[--also "NAME, NAME"|none] [--due DATE|pilot|none]
|
|
171
176
|
[--priority P] [--status S] [--notes TEXT]
|
|
172
177
|
[--repeat weekly|biweekly|monthly|never]
|
|
173
178
|
[--waits-on TASK,TASK|none] [--confirm] [--over-limit]
|
|
@@ -608,7 +613,7 @@ export function hint(message, project) {
|
|
|
608
613
|
)
|
|
609
614
|
.replace(
|
|
610
615
|
/^Nothing to change: pass a field to update\.$/,
|
|
611
|
-
"Nothing to change. Say what with --title, --assignee, --due, --priority, --status, --notes, --repeat or --waits-on.",
|
|
616
|
+
"Nothing to change. Say what with --title, --assignee, --also, --due, --priority, --status, --notes, --repeat or --waits-on.",
|
|
612
617
|
)
|
|
613
618
|
.replace(
|
|
614
619
|
/^This connection was allowed to read only\..*$/s,
|
|
@@ -642,6 +647,24 @@ export function voyageForTerminal(r, project = r.project) {
|
|
|
642
647
|
};
|
|
643
648
|
}
|
|
644
649
|
|
|
650
|
+
/** --also "Sam, Aya" as the names; none (or nobody) leaves only the owner. */
|
|
651
|
+
export function alsoList(value) {
|
|
652
|
+
const v = value.trim();
|
|
653
|
+
if (/^(none|nobody)$/i.test(v)) return [];
|
|
654
|
+
const names = v
|
|
655
|
+
.split(",")
|
|
656
|
+
.map((x) => x.trim())
|
|
657
|
+
.filter(Boolean);
|
|
658
|
+
if (!names.length) throw new HarborError('Say who: --also "NAME, NAME", or --also none.');
|
|
659
|
+
return names;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/** One file: its name and kind, a link's address, who added it and when. */
|
|
663
|
+
export function fileText(f) {
|
|
664
|
+
const kind = { image: "image", doc: "document", link: "link" }[f.kind] ?? f.kind;
|
|
665
|
+
return `${f.name} (${kind}${f.url ? `, ${f.url}` : ""}) · ${f.addedBy ?? "someone"}, ${f.added}`;
|
|
666
|
+
}
|
|
667
|
+
|
|
645
668
|
/** Commands whose forms do different things: one line each, saying which read and which write. */
|
|
646
669
|
const DETAIL = {
|
|
647
670
|
add: [
|
|
@@ -654,7 +677,8 @@ const DETAIL = {
|
|
|
654
677
|
" --project can be left out when the harbor has one open project (First",
|
|
655
678
|
" voyage aside); the task goes there.",
|
|
656
679
|
"",
|
|
657
|
-
" --assignee me|none|NAME, --
|
|
680
|
+
" --assignee me|none|NAME, --also \"NAME, NAME\" (others on it with the",
|
|
681
|
+
" owner), --due DATE|pilot, --priority low|medium|high|urgent,",
|
|
658
682
|
" --status S, --comment TEXT and --over-limit apply to every task added, so",
|
|
659
683
|
" ten lines with --comment add the same comment to all ten. More than 100",
|
|
660
684
|
" lines is harbor import's job.",
|
|
@@ -961,8 +985,9 @@ const DETAIL = {
|
|
|
961
985
|
],
|
|
962
986
|
watch: [
|
|
963
987
|
" harbor watch reads, live: each change to the harbor",
|
|
964
|
-
" as it lands, one line with
|
|
965
|
-
" cards moving, comments,
|
|
988
|
+
" as it lands, one line with its time",
|
|
989
|
+
" and zone: cards moving, comments,",
|
|
990
|
+
" health changes",
|
|
966
991
|
" --project P one project's changes only",
|
|
967
992
|
" --bell ring the terminal's bell when something of yours changes",
|
|
968
993
|
" --since TIME start with every change since TIME (14:30 today, or",
|
|
@@ -2088,6 +2113,25 @@ export async function run(argv, io = {}) {
|
|
|
2088
2113
|
out(asJson ? jsonText(result) : plain(taskList(result, opts)));
|
|
2089
2114
|
return 0;
|
|
2090
2115
|
}
|
|
2116
|
+
case "files": {
|
|
2117
|
+
const { positionals, values, harbor } = parse(spec([], ["kind"]));
|
|
2118
|
+
const project = text(positionals, "the project");
|
|
2119
|
+
const kind = values.kind?.trim().toLowerCase().replace(/s$/, "");
|
|
2120
|
+
if (kind !== undefined && !["image", "doc", "link"].includes(kind))
|
|
2121
|
+
throw new HarborError("--kind is image, doc or link.");
|
|
2122
|
+
return call("list_files", { harbor, project, kind, limit: 200 }, (r) =>
|
|
2123
|
+
r.files.length
|
|
2124
|
+
? [
|
|
2125
|
+
`${r.project}: ${r.total} file${r.total === 1 ? "" : "s"}${kind ? ` (${kind}s)` : ""}`,
|
|
2126
|
+
"",
|
|
2127
|
+
...r.files.map(
|
|
2128
|
+
(f) => `${fileText(f)} on ${f.task.key ?? f.task.id} ${f.task.title}`,
|
|
2129
|
+
),
|
|
2130
|
+
...(r.total > r.files.length ? [`…and ${r.total - r.files.length} more.`] : []),
|
|
2131
|
+
].join("\n")
|
|
2132
|
+
: `No ${kind ? `${kind}s` : "files"} on ${r.project}'s tasks yet. Attach them to a task on the website.`,
|
|
2133
|
+
);
|
|
2134
|
+
}
|
|
2091
2135
|
case "show": {
|
|
2092
2136
|
const { positionals, harbor } = parse(spec());
|
|
2093
2137
|
return call("get_task", { harbor, task: one(positionals, "task key, like RR-42") }, (t) => {
|
|
@@ -2097,6 +2141,7 @@ export async function run(argv, io = {}) {
|
|
|
2097
2141
|
{ ...opts, width: undefined },
|
|
2098
2142
|
),
|
|
2099
2143
|
];
|
|
2144
|
+
if (t.alsoOn?.length) lines.push(`Also on it: ${t.alsoOn.join(", ")}.`);
|
|
2100
2145
|
if (t.repeats) lines.push(`Repeats ${REPEAT_WORD[t.repeats] ?? t.repeats}.`);
|
|
2101
2146
|
if (t.pilotDue?.fits) lines.push(`${t.pilotDue.says}, so the due date holds.`);
|
|
2102
2147
|
else if (t.pilotDue) lines.push(...pilotLines({ pilot: t.pilotDue }, t));
|
|
@@ -2119,6 +2164,7 @@ export async function run(argv, io = {}) {
|
|
|
2119
2164
|
(w) => ` ${w.key ?? w.id} ${w.title} (${STATUS[w.status] ?? w.status})`,
|
|
2120
2165
|
),
|
|
2121
2166
|
);
|
|
2167
|
+
if (t.files?.length) lines.push("", "Files:", ...t.files.map((f) => ` ${fileText(f)}`));
|
|
2122
2168
|
if (t.comments.length)
|
|
2123
2169
|
lines.push(
|
|
2124
2170
|
"",
|
|
@@ -2337,7 +2383,7 @@ export async function run(argv, io = {}) {
|
|
|
2337
2383
|
const { positionals, values, harbor } = parse(
|
|
2338
2384
|
spec(
|
|
2339
2385
|
["over-limit", "dry-run"],
|
|
2340
|
-
["project", "assignee", "due", "priority", "status", "comment"],
|
|
2386
|
+
["project", "assignee", "also", "due", "priority", "status", "comment"],
|
|
2341
2387
|
),
|
|
2342
2388
|
);
|
|
2343
2389
|
// No --project: the harbor's one open project, if that's all there is.
|
|
@@ -2413,6 +2459,7 @@ export async function run(argv, io = {}) {
|
|
|
2413
2459
|
title,
|
|
2414
2460
|
// Yours unless you say otherwise, as with n in the app.
|
|
2415
2461
|
assignee: values.assignee ?? "me",
|
|
2462
|
+
also: values.also === undefined ? undefined : alsoList(values.also),
|
|
2416
2463
|
due: duePilot(values.due) ? undefined : dueDate(values.due),
|
|
2417
2464
|
suggestedDue: duePilot(values.due),
|
|
2418
2465
|
priority: values.priority,
|
|
@@ -2526,7 +2573,17 @@ export async function run(argv, io = {}) {
|
|
|
2526
2573
|
const { positionals, values, harbor } = parse(
|
|
2527
2574
|
spec(
|
|
2528
2575
|
["confirm", "over-limit"],
|
|
2529
|
-
[
|
|
2576
|
+
[
|
|
2577
|
+
"title",
|
|
2578
|
+
"assignee",
|
|
2579
|
+
"also",
|
|
2580
|
+
"due",
|
|
2581
|
+
"priority",
|
|
2582
|
+
"status",
|
|
2583
|
+
"notes",
|
|
2584
|
+
"repeat",
|
|
2585
|
+
"waits-on",
|
|
2586
|
+
],
|
|
2530
2587
|
),
|
|
2531
2588
|
);
|
|
2532
2589
|
const waits = values["waits-on"];
|
|
@@ -2537,6 +2594,7 @@ export async function run(argv, io = {}) {
|
|
|
2537
2594
|
task: one(positionals, "task key, like RR-42"),
|
|
2538
2595
|
title: values.title,
|
|
2539
2596
|
assignee: values.assignee,
|
|
2597
|
+
also: values.also === undefined ? undefined : alsoList(values.also),
|
|
2540
2598
|
due:
|
|
2541
2599
|
values.due === undefined || duePilot(values.due)
|
|
2542
2600
|
? undefined
|
|
@@ -2561,6 +2619,11 @@ export async function run(argv, io = {}) {
|
|
|
2561
2619
|
(r) =>
|
|
2562
2620
|
[
|
|
2563
2621
|
`Updated: ${taskLine(r.updated, opts)}`,
|
|
2622
|
+
values.also !== undefined
|
|
2623
|
+
? r.updated.alsoOn?.length
|
|
2624
|
+
? `Also on it: ${r.updated.alsoOn.join(", ")}.`
|
|
2625
|
+
: "Only the owner is on it now."
|
|
2626
|
+
: null,
|
|
2564
2627
|
r.updated.waitsOn ? `Waits on ${r.updated.waitsOn.join(", ")}.` : null,
|
|
2565
2628
|
r.updated.repeat ? `Repeats ${REPEAT_WORD[r.updated.repeat]}.` : null,
|
|
2566
2629
|
...(values.due && !duePilot(values.due) ? pilotLines(r, r.updated) : []),
|
package/lib/format.mjs
CHANGED
|
@@ -39,7 +39,7 @@ export function taskLine(t, { today, mine = false, width } = {}) {
|
|
|
39
39
|
STATUS[t.status] ?? t.status,
|
|
40
40
|
t.due ? (late ? `late, due ${t.due}` : soon ? `due ${soon}` : `due ${t.due}`) : null,
|
|
41
41
|
t.priority && t.priority !== "medium" ? t.priority : null,
|
|
42
|
-
mine ? null :
|
|
42
|
+
mine ? null : t.assignee ? `${t.assignee}${t.alsoOn?.length ? ` +${t.alsoOn.length}` : ""}` : null,
|
|
43
43
|
t.project ?? null,
|
|
44
44
|
t.waitingOn ? `waits on ${t.waitingOn}` : null,
|
|
45
45
|
].filter(Boolean);
|
|
@@ -72,6 +72,8 @@ export function myWork(w, opts) {
|
|
|
72
72
|
if (!w.you) return w.note ?? "Nothing is yours in this harbor yet.";
|
|
73
73
|
const out = [`${w.you}: ${w.openTasks} open${w.late ? `, ${w.late} late` : ""}`];
|
|
74
74
|
if (w.tasks?.length) out.push("", ...w.tasks.map((t) => taskLine(t, { ...opts, mine: true })));
|
|
75
|
+
if (w.alsoOn?.length)
|
|
76
|
+
out.push("", "Also on (others own these):", ...w.alsoOn.map((t) => ` ${taskLine(t, opts)}`));
|
|
75
77
|
if (w.waitingOnYou?.length) {
|
|
76
78
|
out.push("", "Waiting on you:");
|
|
77
79
|
for (const x of w.waitingOnYou)
|