@harborppm/cli 0.11.2 → 0.13.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
@@ -19,6 +19,7 @@ import {
19
19
  import {
20
20
  harbors,
21
21
  myWork,
22
+ raidList,
22
23
  programTree,
23
24
  projectList,
24
25
  taskLine,
@@ -59,13 +60,18 @@ export const HELP = `harbor: Harbor from the terminal
59
60
  [--over-limit]
60
61
  harbor start TASK | review TASK | done TASK [--confirm] [--over-limit]
61
62
  harbor edit TASK [--title T] [--assignee me|none|NAME] [--due DATE|none]
62
- [--priority P] [--status S] [--confirm] [--over-limit]
63
+ [--priority P] [--status S] [--notes TEXT]
64
+ [--repeat weekly|biweekly|monthly|never]
65
+ [--waits-on TASK,TASK|none] [--confirm] [--over-limit]
63
66
  harbor comment TASK "TEXT"
64
67
  harbor checkin "WHAT YOU'RE DOING" [--on-blocker]
65
68
  harbor risk "TITLE" --project P|--program P [--impact 1-5] [--probability 1-5]
66
69
  [--owner NAME] [--due DATE]
67
70
  harbor issue "TITLE" --project P|--program P [--impact 1-5] [--owner NAME]
68
71
  [--due DATE]
72
+ harbor decision "TITLE" --project P|--program P [--owner NAME] [--due DATE]
73
+ harbor risks [words] [--kind risk,issue,decision] [--project P] [--closed]
74
+ the register, worst first
69
75
 
70
76
  Every command takes --json, and --harbor NAME when you're in more than one
71
77
  harbor (or set one with harbor use). harbor help COMMAND shows just that one.
@@ -144,6 +150,7 @@ const level = (v, name) => {
144
150
  return n;
145
151
  };
146
152
  const nullable = (v) => (v === "none" ? null : v);
153
+ const REPEAT_WORD = { weekly: "every week", biweekly: "every two weeks", monthly: "every month" };
147
154
 
148
155
  /** --due as people say it: today, tomorrow, +3, or a date. Anything else goes to Harbor to judge. */
149
156
  export function dueDate(v, today = new Date()) {
@@ -176,6 +183,9 @@ const FLAG = {
176
183
  query: "the words",
177
184
  task: "the task",
178
185
  note: "the check-in",
186
+ waitsOn: "--waits-on",
187
+ repeat: "--repeat",
188
+ description: "--notes",
179
189
  body: "the comment",
180
190
  };
181
191
 
@@ -213,7 +223,7 @@ export function hint(message) {
213
223
  .replace(/Find it with search_tasks\.$/, "Find it with harbor tasks.")
214
224
  .replace(
215
225
  /^Nothing to change: pass a field to update\.$/,
216
- "Nothing to change. Say what with --title, --assignee, --due, --priority or --status.",
226
+ "Nothing to change. Say what with --title, --assignee, --due, --priority, --status, --notes, --repeat or --waits-on.",
217
227
  )
218
228
  .replace(
219
229
  /^This connection was allowed to read only\..*$/s,
@@ -782,8 +792,12 @@ export async function run(argv, io = {}) {
782
792
  }
783
793
  case "edit": {
784
794
  const { positionals, values, harbor } = parse(
785
- spec(["confirm", "over-limit"], ["title", "assignee", "due", "priority", "status"]),
795
+ spec(
796
+ ["confirm", "over-limit"],
797
+ ["title", "assignee", "due", "priority", "status", "notes", "repeat", "waits-on"],
798
+ ),
786
799
  );
800
+ const waits = values["waits-on"];
787
801
  return call(
788
802
  "update_task",
789
803
  {
@@ -794,10 +808,28 @@ export async function run(argv, io = {}) {
794
808
  due: values.due === undefined ? undefined : nullable(dueDate(values.due)),
795
809
  priority: values.priority,
796
810
  status: values.status,
811
+ description: values.notes,
812
+ repeat: values.repeat,
813
+ waitsOn:
814
+ waits === undefined
815
+ ? undefined
816
+ : waits === "none"
817
+ ? []
818
+ : waits
819
+ .split(",")
820
+ .map((x) => x.trim())
821
+ .filter(Boolean),
797
822
  confirmed: values.confirm || undefined,
798
823
  overLimit: values["over-limit"] || undefined,
799
824
  },
800
- (r) => `Updated: ${taskLine(r.updated, opts)}`,
825
+ (r) =>
826
+ [
827
+ `Updated: ${taskLine(r.updated, opts)}`,
828
+ r.updated.waitsOn ? `Waits on ${r.updated.waitsOn.join(", ")}.` : null,
829
+ r.updated.repeat ? `Repeats ${REPEAT_WORD[r.updated.repeat]}.` : null,
830
+ ]
831
+ .filter(Boolean)
832
+ .join("\n"),
801
833
  );
802
834
  }
803
835
  case "comment": {
@@ -823,13 +855,34 @@ export async function run(argv, io = {}) {
823
855
  () => "Checked in for today.",
824
856
  );
825
857
  }
858
+ case "risks": {
859
+ const { positionals, values, harbor } = parse(spec(["closed"], ["kind", "project"]));
860
+ const kind = values.kind
861
+ ?.split(",")
862
+ .map((x) => x.trim().replace(/s$/, ""))
863
+ .filter(Boolean);
864
+ const bad = kind?.find((k) => !["risk", "issue", "decision"].includes(k));
865
+ if (bad) throw new HarborError(`--kind is risk, issue or decision (not "${bad}").`);
866
+ return call(
867
+ "list_risks",
868
+ {
869
+ harbor,
870
+ kind,
871
+ project: values.project,
872
+ includeClosed: Boolean(values.closed),
873
+ limit: 50,
874
+ },
875
+ (r) => raidList(r, positionals.join(" ")),
876
+ );
877
+ }
826
878
  case "risk":
827
- case "issue": {
879
+ case "issue":
880
+ case "decision": {
828
881
  const { positionals, values, harbor } = parse(
829
882
  spec(
830
883
  [],
831
- ["project", "program", "owner", "impact", "due"].concat(
832
- command === "risk" ? ["probability"] : [],
884
+ ["project", "program", "owner", "due"].concat(
885
+ command === "risk" ? ["impact", "probability"] : command === "issue" ? ["impact"] : [],
833
886
  ),
834
887
  ),
835
888
  );
package/lib/format.mjs CHANGED
@@ -158,3 +158,28 @@ export function todayLocal(d = new Date()) {
158
158
  const p = (n) => String(n).padStart(2, "0");
159
159
  return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
160
160
  }
161
+
162
+ /** The register, worst first: kind, title, then status, scores, owner and where it sits. */
163
+ export function raidList(r, words = "") {
164
+ const want = words.trim().toLowerCase();
165
+ const items = (r.items ?? []).filter(
166
+ (x) => !want || `${x.title} ${x.description ?? ""}`.toLowerCase().includes(want),
167
+ );
168
+ if (!items.length) return want ? "Nothing in the register matches." : "The register is clear.";
169
+ const lines = items.map((x) => {
170
+ const bits = [
171
+ x.status?.toLowerCase(),
172
+ x.probability != null ? `likelihood ${x.probability}` : null,
173
+ x.impact != null ? `impact ${x.impact}` : null,
174
+ x.owner,
175
+ x.project ?? x.program ?? x.portfolio ?? null,
176
+ x.due ? `${x.kind === "Decision" ? "decide by" : "review by"} ${x.due}` : null,
177
+ ].filter(Boolean);
178
+ return `${x.kind.toLowerCase().padEnd(8)} ${x.title} (${bits.join(" · ")})`;
179
+ });
180
+ const more =
181
+ !want && r.total > items.length
182
+ ? `\n…and ${r.total - items.length} more. Narrow it with --kind or --project.`
183
+ : "";
184
+ return lines.join("\n") + more;
185
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harborppm/cli",
3
- "version": "0.11.2",
3
+ "version": "0.13.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",