@harborppm/cli 0.46.0 → 0.48.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
@@ -35,6 +35,7 @@ import {
35
35
  notesText,
36
36
  voyageText,
37
37
  handoverText,
38
+ fogText,
38
39
  agentWorkText,
39
40
  raidDetail,
40
41
  raidList,
@@ -178,6 +179,10 @@ export const HELP = `harbor: Harbor from the terminal
178
179
  [--keep ID,ID] [--confirm]
179
180
  hand someone's work to the crew
180
181
  harbor handback PERSON give it back after time away
182
+ harbor fog [--project P] [--list] tasks nobody has touched for three
183
+ weeks, one at a time: still
184
+ needed, a new date, or drop it
185
+ harbor fog keep TASK it's still needed: out of the fog
181
186
  harbor agent [--agent NAME] the tasks given to an AI agent on
182
187
  the crew, in the order to do them
183
188
  harbor agent report TASK "WHAT YOU DID" [--start] [--agent NAME]
@@ -190,7 +195,8 @@ export const HELP = `harbor: Harbor from the terminal
190
195
  "to do", "in play"; --json says doing for In play).
191
196
  DATE: YYYY-MM-DD, today, tomorrow, or +N for N days from today.
192
197
  Changes show in Harbor's History as yours, "via Harbor CLI". These commands
193
- never delete; the full-screen app can, as the website can, and asks first.
198
+ never delete; the full-screen app can, as the website can, and asks first,
199
+ and so can harbor fog's x (drop it), one task at a time.
194
200
  `;
195
201
 
196
202
  export const VERSION = JSON.parse(
@@ -628,6 +634,20 @@ const DETAIL = {
628
634
  "",
629
635
  ' PERSON is a name, an id, or "me".',
630
636
  ],
637
+ fog: [
638
+ " harbor fog [--project P] in a terminal: the tasks in fog, one",
639
+ " at a time. k still needed (clears the",
640
+ " fog), d a new date, x drop it, s skip,",
641
+ " q stop; then it saves what you chose",
642
+ " harbor fog --list reads them: open tasks nobody has",
643
+ " opened, moved, edited or commented on",
644
+ " for three weeks, longest first",
645
+ " harbor fog keep TASK writes: it's still needed, so it",
646
+ " leaves the fog",
647
+ "",
648
+ " Piped, or with --json, it lists. The one-at-a-time sweep needs full access",
649
+ " (harbor login without --read-only), since dropping a task deletes it.",
650
+ ],
631
651
  handback: [
632
652
  " harbor handback PERSON writes, straight away: gives back",
633
653
  " what was handed over for their time",
@@ -2045,6 +2065,36 @@ export async function run(argv, io = {}) {
2045
2065
  }
2046
2066
  return call("get_voyage_log", { harbor, project }, voyageText, voyageForTerminal);
2047
2067
  }
2068
+ case "fog": {
2069
+ const { positionals, values, harbor } = parse(spec(["list"], ["project"]));
2070
+ if (positionals[0] === "keep") {
2071
+ const task = one(positionals.slice(1), "task");
2072
+ return call(
2073
+ "keep_task",
2074
+ { harbor, task },
2075
+ (r) => `Kept ${r.kept.key ?? r.kept.task} out of the fog: it counts as touched today.`,
2076
+ );
2077
+ }
2078
+ none(positionals);
2079
+ const terminal = io.terminal ?? Boolean(process.stdout.isTTY && process.stdin.isTTY);
2080
+ if (terminal && !values.list && !asJson && hasFullAccess(creds)) {
2081
+ const { fogHere } = await loadApp();
2082
+ await fogHere({
2083
+ source: tokenSource({ creds, save: (c) => writeCredentials(c, env), fetcher }),
2084
+ fetcher,
2085
+ harbor,
2086
+ saved: creds.harbor ?? null,
2087
+ project: values.project,
2088
+ });
2089
+ await stalePromptCache(env);
2090
+ return 0;
2091
+ }
2092
+ return call(
2093
+ "fog",
2094
+ { harbor, project: values.project, limit: 100 },
2095
+ (r) => fogText(r),
2096
+ );
2097
+ }
2048
2098
  case "handover": {
2049
2099
  const { positionals, values, harbor } = parse(
2050
2100
  spec(["confirm"], ["to", "until", "note", "keep"]),
package/lib/format.mjs CHANGED
@@ -705,3 +705,25 @@ export function exportData(r) {
705
705
  rows: body.map((cells) => Object.fromEntries(columns.map((c, i) => [c, cells[i] ?? ""]))),
706
706
  };
707
707
  }
708
+
709
+ /** fog: the open tasks nobody has touched for three weeks, longest first. */
710
+ export function fogText(r, { sweepHint = true } = {}) {
711
+ const where = r.project ? ` in ${r.project}` : "";
712
+ if (!r.inFog) return `No fog${where}: every open task was touched in the last three weeks.`;
713
+ const lines = [
714
+ `${r.inFog} ${r.inFog === 1 ? "task" : "tasks"} in fog${where}: nobody has opened, moved or commented on ${r.inFog === 1 ? "it" : "them"} for three weeks.`,
715
+ "",
716
+ ];
717
+ for (const t of r.tasks) {
718
+ const bits = [t.untouched, r.project ? null : t.project, t.due ? `due ${t.due}` : null];
719
+ lines.push(` ${(t.key ?? "").padEnd(7)} ${t.title}`);
720
+ lines.push(` ${bits.filter(Boolean).join(" · ")}`);
721
+ }
722
+ if (r.more) lines.push("", `And ${r.inFog - r.tasks.length} more.`);
723
+ if (sweepHint)
724
+ lines.push(
725
+ "",
726
+ "In a terminal, harbor fog walks through them one at a time: k still needed, d new date, x drop it. harbor fog keep TASK keeps one.",
727
+ );
728
+ return lines.join("\n");
729
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harborppm/cli",
3
- "version": "0.46.0",
3
+ "version": "0.48.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",