@harborppm/cli 0.31.0 → 0.33.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 +6 -1
- package/dist/app.mjs +897 -92
- package/lib/cli.mjs +77 -0
- package/lib/format.mjs +27 -0
- package/package.json +1 -1
package/lib/cli.mjs
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
myWork,
|
|
28
28
|
premortemText,
|
|
29
29
|
voyageText,
|
|
30
|
+
handoverText,
|
|
30
31
|
raidList,
|
|
31
32
|
programTree,
|
|
32
33
|
projectList,
|
|
@@ -110,11 +111,16 @@ export const HELP = `harbor: Harbor from the terminal
|
|
|
110
111
|
harbor brief [--portfolio P] [--say] the weekly brief, ready to send;
|
|
111
112
|
--say reads it aloud as a
|
|
112
113
|
shipping forecast
|
|
114
|
+
harbor report PROGRAM a program's status report
|
|
113
115
|
harbor premortem PROJECT ["WHAT WENT WRONG"] [--start]
|
|
114
116
|
a project's pre-mortem: start,
|
|
115
117
|
answer or read it
|
|
116
118
|
harbor voyage PROJECT ["YOUR LINE"] a Done project's voyage log, and
|
|
117
119
|
your line in it
|
|
120
|
+
harbor handover PERSON [--to NAME] [--until DATE] [--note TEXT]
|
|
121
|
+
[--keep ID,ID] [--confirm]
|
|
122
|
+
hand someone's work to the crew
|
|
123
|
+
harbor handback PERSON give it back after time away
|
|
118
124
|
|
|
119
125
|
Every command takes --json, and --harbor NAME when you're in more than one
|
|
120
126
|
harbor (or set one with harbor use). harbor help COMMAND shows just that one.
|
|
@@ -283,6 +289,10 @@ const FLAG = {
|
|
|
283
289
|
column: "the column",
|
|
284
290
|
limit: "the limit",
|
|
285
291
|
at: "--at",
|
|
292
|
+
until: "--until",
|
|
293
|
+
to: "--to",
|
|
294
|
+
keep: "--keep",
|
|
295
|
+
person: "the person",
|
|
286
296
|
};
|
|
287
297
|
|
|
288
298
|
/** Harbor's refusals are worded for agents; say them as the commands and flags that answer them. */
|
|
@@ -376,6 +386,21 @@ const DETAIL = {
|
|
|
376
386
|
" PROJECT is a name, a key like RR, or an id. When two projects share a",
|
|
377
387
|
" name, give the key.",
|
|
378
388
|
],
|
|
389
|
+
handover: [
|
|
390
|
+
" harbor handover PERSON reads what would move, and to whom:",
|
|
391
|
+
" their open tasks, sign-offs, risks",
|
|
392
|
+
" and issues, and what they lead",
|
|
393
|
+
" harbor handover PERSON --confirm writes: hands it over as shown",
|
|
394
|
+
"",
|
|
395
|
+
" --to NAME all of it to one person, instead of who has room",
|
|
396
|
+
" --until DATE time away: they show as away, and harbor handback PERSON",
|
|
397
|
+
" (or People on the website) gives back what's still with",
|
|
398
|
+
" whoever took it",
|
|
399
|
+
' --note "TEXT" goes with each task and risk as a comment',
|
|
400
|
+
" --keep ID,ID leave these where they are (ids or keys from the list)",
|
|
401
|
+
"",
|
|
402
|
+
' PERSON is a name, an id, or "me".',
|
|
403
|
+
],
|
|
379
404
|
voyage: [
|
|
380
405
|
" harbor voyage PROJECT reads a Done project's voyage log",
|
|
381
406
|
' harbor voyage PROJECT "YOUR LINE" writes: adds or changes your line in it',
|
|
@@ -1266,6 +1291,12 @@ export async function run(argv, io = {}) {
|
|
|
1266
1291
|
(r) => raidList(r, positionals.join(" ")),
|
|
1267
1292
|
);
|
|
1268
1293
|
}
|
|
1294
|
+
case "report": {
|
|
1295
|
+
const { positionals, harbor } = parse(spec([]));
|
|
1296
|
+
const program = positionals.join(" ").trim();
|
|
1297
|
+
if (!program) throw new HarborError('Give a program: harbor report "NAME".');
|
|
1298
|
+
return call("status_report", { harbor, program }, (r) => r.markdown);
|
|
1299
|
+
}
|
|
1269
1300
|
case "premortem": {
|
|
1270
1301
|
const { positionals, values, harbor } = parse(spec(["start"]));
|
|
1271
1302
|
const [project, ...words] = positionals;
|
|
@@ -1333,6 +1364,52 @@ export async function run(argv, io = {}) {
|
|
|
1333
1364
|
}
|
|
1334
1365
|
return call("get_voyage_log", { harbor, project }, voyageText);
|
|
1335
1366
|
}
|
|
1367
|
+
case "handover": {
|
|
1368
|
+
const { positionals, values, harbor } = parse(
|
|
1369
|
+
spec(["confirm"], ["to", "until", "note", "keep"]),
|
|
1370
|
+
);
|
|
1371
|
+
const person = positionals.join(" ").trim();
|
|
1372
|
+
if (!person) throw new HarborError('Say whose work: harbor handover "NAME" (or me).');
|
|
1373
|
+
if (!values.confirm) {
|
|
1374
|
+
return call("handover_plan", { harbor, person, to: values.to }, (r) => handoverText(r));
|
|
1375
|
+
}
|
|
1376
|
+
return call(
|
|
1377
|
+
"hand_over",
|
|
1378
|
+
{
|
|
1379
|
+
harbor,
|
|
1380
|
+
person,
|
|
1381
|
+
to: values.to,
|
|
1382
|
+
until: dueDate(values.until),
|
|
1383
|
+
note: values.note,
|
|
1384
|
+
keep: values.keep
|
|
1385
|
+
? values.keep
|
|
1386
|
+
.split(",")
|
|
1387
|
+
.map((k) => k.trim())
|
|
1388
|
+
.filter(Boolean)
|
|
1389
|
+
: undefined,
|
|
1390
|
+
},
|
|
1391
|
+
(r) => {
|
|
1392
|
+
const h = r.handedOver;
|
|
1393
|
+
const names = [...new Set(h.items.map((i) => i.to))];
|
|
1394
|
+
const who =
|
|
1395
|
+
names.length > 1 ? `${names.slice(0, -1).join(", ")} and ${names.at(-1)}` : names[0];
|
|
1396
|
+
const moved = `Handed ${h.person}'s work over: ${h.moved} ${h.moved === 1 ? "thing" : "things"}${who ? ` to ${who}` : ""}.`;
|
|
1397
|
+
return h.awayUntil
|
|
1398
|
+
? `${moved} ${h.person} shows as away until ${h.awayUntil}; harbor handback "${h.person}" gives it back.`
|
|
1399
|
+
: moved;
|
|
1400
|
+
},
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
case "handback": {
|
|
1404
|
+
const { positionals, harbor } = parse(spec());
|
|
1405
|
+
const person = positionals.join(" ").trim();
|
|
1406
|
+
if (!person) throw new HarborError('Say who is back: harbor handback "NAME".');
|
|
1407
|
+
return call("hand_back", { harbor, person }, (r) =>
|
|
1408
|
+
r.handedBack.moved
|
|
1409
|
+
? `Handed ${r.handedBack.moved} back to ${r.handedBack.person}.`
|
|
1410
|
+
: `${r.handedBack.person} is back. Nothing was still waiting to go back.`,
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1336
1413
|
case "risk":
|
|
1337
1414
|
case "issue":
|
|
1338
1415
|
case "decision": {
|
package/lib/format.mjs
CHANGED
|
@@ -369,3 +369,30 @@ export function hillList(r) {
|
|
|
369
369
|
}
|
|
370
370
|
return blocks.join("\n\n");
|
|
371
371
|
}
|
|
372
|
+
|
|
373
|
+
/** handover_plan as a list grouped like the website's dialog, each line with who takes it. */
|
|
374
|
+
export function handoverText(r, { confirmHint = true } = {}) {
|
|
375
|
+
const items = r.items ?? [];
|
|
376
|
+
if (items.length === 0) return `${r.person} has nothing open to hand over.`;
|
|
377
|
+
const lines = [
|
|
378
|
+
`Hand over ${r.person}'s work${r.awayUntil ? ` (away until ${r.awayUntil})` : ""}`,
|
|
379
|
+
];
|
|
380
|
+
let group = null;
|
|
381
|
+
for (const i of items) {
|
|
382
|
+
if (i.group !== group) {
|
|
383
|
+
group = i.group;
|
|
384
|
+
lines.push("", `${group}:`);
|
|
385
|
+
}
|
|
386
|
+
lines.push(
|
|
387
|
+
` ${i.what}${i.due && i.kind !== "project" && i.kind !== "program" ? ` (${i.due})` : ""} → ${i.to ?? "nobody yet"}`,
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
const room = (r.takers ?? []).filter((t) => t.note).map((t) => `${t.who} ${t.note}`);
|
|
391
|
+
if (room.length) lines.push("", `This week: ${room.join(", ")}.`);
|
|
392
|
+
if (confirmHint)
|
|
393
|
+
lines.push(
|
|
394
|
+
"",
|
|
395
|
+
'Nothing has moved yet. Add --confirm to hand it over, --until DATE for time away, --note "…" to send a note with it, --to NAME to give it all to one person, or --keep ID,ID to leave some where it is.',
|
|
396
|
+
);
|
|
397
|
+
return lines.join("\n");
|
|
398
|
+
}
|