@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/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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harborppm/cli",
3
- "version": "0.31.0",
3
+ "version": "0.33.0",
4
4
  "description": "Harbor from the terminal: a full-screen app with everything, plus commands for scripts and agents.",
5
5
  "keywords": [
6
6
  "harbor",