@harborppm/cli 0.14.0 → 0.15.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 CHANGED
@@ -55,13 +55,13 @@ Your harbor is the same one at [harborppm.com](https://harborppm.com/today?utm_s
55
55
  - **GitHub**: a pull request that names a task's key (RR-42) shows on the task and moves it along.
56
56
  - **Claude and other AI apps**, connected over MCP from [harborppm.com/ai](https://harborppm.com/ai?utm_source=npm&utm_medium=readme&utm_campaign=cli).
57
57
 
58
- `harbor open RR-42` opens a task on the website (in the right harbor, even when the browser had another one open), `harbor open Ridge` a project, and `harbor open` on its own opens Today. `harbor invite sam@example.com` sends Sam the same invite letter the People page does (`--viewer` to let them look but not change anything); only a harbor's owner can invite.
58
+ `harbor open RR-42` opens a task on the website (in the right harbor, even when the browser had another one open), `harbor open Ridge` a project, and `harbor open` on its own opens Today. `harbor invite sam@example.com` sends Sam the same invite letter the People page does (`--viewer` to let them look but not change anything); only a harbor's owner can invite. `harbor calendar` prints your private calendar link, to add to Google, Apple or Outlook calendar: your due dates, milestones and sign-offs beside your meetings, kept up to date (`--program NAME` for everything dated in one program, `--new` to swap the link for a fresh one, `--off` to turn it off).
59
59
 
60
60
  The website has the commands too: turn on **Command line at the bottom** in your menu, press `/`, and type `mine`, `add "Call the mill" #ridge @sam --due fri` or `done RR-12`.
61
61
 
62
62
  ## In your shell prompt
63
63
 
64
- `harbor prompt` prints a few characters for your prompt: `⚓ 2 today · 1 late`, with late work in coral, and nothing at all when you're clear. It never waits on the network: it shows the counts it kept and fetches new ones in the background when they're more than five minutes old, or after you change a task. `harbor prompt --init zsh` (or `bash`, `fish`, `starship`) prints the lines to add to your shell's setup:
64
+ `harbor prompt` prints a few characters for your prompt: `⚓ 2 today · 1 late`, with late work in coral, and nothing at all when you're clear. It never waits on the network: it shows the counts it kept and fetches new ones in the background when they're more than five minutes old, or after you change a task. `harbor prompt --init zsh` (or `bash`, `fish`, `starship`) prints the lines to add to your shell's setup, so `harbor prompt --init starship >> ~/.config/starship.toml` does it in one go:
65
65
 
66
66
  ```zsh
67
67
  # ~/.zshrc
@@ -71,7 +71,7 @@ RPROMPT='$(harbor prompt --shell zsh)'
71
71
 
72
72
  ## Staying up to date
73
73
 
74
- npm doesn't update a global install by itself, so once a day `harbor` asks npm whether a newer version is out, alongside whatever command you ran, and says so in one line when there is (`npm install -g @harborppm/cli@latest` updates it). It says nothing in scripts, pipes or CI; set `HARBOR_NO_UPDATE_CHECK=1` (or `NO_UPDATE_NOTIFIER=1`) to turn it off.
74
+ npm doesn't update a global install by itself, so once a day `harbor` asks npm whether a newer version is out, alongside whatever command you ran, and says so in one line when there is (`npm install -g @harborppm/cli@latest --prefer-online` updates it). It says nothing in scripts, pipes or CI; set `HARBOR_NO_UPDATE_CHECK=1` (or `NO_UPDATE_NOTIFIER=1`) to turn it off.
75
75
 
76
76
  ## Text from others
77
77
 
@@ -95,6 +95,7 @@ harbor tasks [words] [--project P] [--assignee me|none|NAME] [--late] [--status
95
95
  harbor show TASK one task in full, with its web link
96
96
  harbor open [TASK|PROJECT] open it on the website (or Today)
97
97
  harbor invite EMAIL [--viewer] invite someone to your harbor by email
98
+ harbor calendar [--program P] [--new|--off] your private calendar link (.ics)
98
99
 
99
100
  harbor program "NAME" [--portfolio P] [--lead me|NAME] [--starts DATE]
100
101
  [--ends DATE]
package/lib/cli.mjs CHANGED
@@ -66,6 +66,7 @@ export const HELP = `harbor: Harbor from the terminal
66
66
  shell prompt (never waits)
67
67
  harbor prompt --init SHELL how to add it: zsh, bash, fish
68
68
  or starship
69
+ harbor calendar [--program P] [--new|--off] your private calendar link (.ics)
69
70
 
70
71
  harbor program "NAME" [--portfolio P] [--lead me|NAME] [--starts DATE]
71
72
  [--ends DATE]
@@ -523,7 +524,8 @@ export async function run(argv, io = {}) {
523
524
  const snippet = initSnippet(values.init);
524
525
  if (!snippet) throw new HarborError(`Pick ${SHELLS.join(", ")} (not "${values.init}").`);
525
526
  out(
526
- `${snippet}\n\nYour prompt shows what's due today and late once Harbor has answered,\na moment after the next prompt. harbor prompt --refresh checks now.`,
527
+ // Comments, so the whole of it can be appended straight to the file.
528
+ `${snippet}\n# Shows what's due today and late, from the prompt after next;\n# nothing when you're clear. harbor prompt --refresh checks now.`,
527
529
  );
528
530
  return 0;
529
531
  }
@@ -533,12 +535,14 @@ export async function run(argv, io = {}) {
533
535
  const creds = await readCredentials(env);
534
536
  const harbor = values.harbor ?? creds?.harbor ?? null;
535
537
  const today = todayLocal(io.now ? new Date(io.now()) : new Date());
536
- const show = (counts, at) =>
537
- out(
538
- values.json
539
- ? jsonText({ ...(counts ?? { today: null, late: null }), at })
540
- : segment(counts ?? {}, shell),
541
- );
538
+ // A prompt with nothing to say gets nothing, not an empty line; a
539
+ // --refresh run by hand says what it found.
540
+ const show = (counts, at, byHand = false) => {
541
+ if (values.json) return out(jsonText({ ...(counts ?? { today: null, late: null }), at }));
542
+ const text = segment(counts ?? {}, shell);
543
+ if (text) out(text);
544
+ else if (byHand) out("Nothing due today or late.");
545
+ };
542
546
  if (!creds) {
543
547
  if (values.refresh) throw new HarborError("Not signed in. Run harbor login.");
544
548
  show(null, null);
@@ -553,7 +557,7 @@ export async function run(argv, io = {}) {
553
557
  const counts = promptCounts(work, today);
554
558
  const at = io.now?.() ?? Date.now();
555
559
  await writePromptCache({ at, day: today, harbor, counts }, env);
556
- show(counts, at);
560
+ show(counts, at, !shell);
557
561
  return 0;
558
562
  }
559
563
  // The prompt itself: what's kept, at once, and fresh counts fetched for next time.
@@ -818,6 +822,34 @@ export async function run(argv, io = {}) {
818
822
  );
819
823
  return 0;
820
824
  }
825
+ case "calendar": {
826
+ const { values, harbor } = parse(spec(["new", "off"], ["program"]));
827
+ if (values.new && values.off) throw new HarborError("Say --new or --off, not both.");
828
+ if (!hasFullAccess(creds)) {
829
+ throw new HarborError("Calendar links need full access. Run harbor login, then try again.");
830
+ }
831
+ const r = await client.app({
832
+ op: "calendar",
833
+ ...(harbor ? { harbor } : {}),
834
+ ...(values.program ? { program: values.program } : {}),
835
+ action: values.off ? "off" : values.new ? "fresh" : "show",
836
+ });
837
+ if (asJson) {
838
+ out(jsonText(r));
839
+ return 0;
840
+ }
841
+ const what = r.program ? `everything dated in ${r.program}` : `your work in ${r.harbor}`;
842
+ if (!r.url) {
843
+ out(plain(`Turned off the calendar link for ${what}. Calendars using it stop updating.`));
844
+ return 0;
845
+ }
846
+ out(
847
+ plain(
848
+ `${values.new ? "A new calendar link" : "Your calendar link"} for ${what}${values.new ? " (the old one has stopped working)" : ""}:\n\n ${r.url}\n\nAdd it to Google, Apple or Outlook calendar as a calendar from a URL. Anyone with the link can see these dates; harbor calendar --new swaps it for a fresh one.`,
849
+ ),
850
+ );
851
+ return 0;
852
+ }
821
853
  case "add": {
822
854
  const { positionals, values, harbor } = parse(
823
855
  spec(["over-limit"], ["project", "assignee", "due", "priority", "status"]),
package/lib/update.mjs CHANGED
@@ -68,6 +68,6 @@ export async function updateNotice({
68
68
  }
69
69
  }
70
70
  return latest && isNewer(latest, version)
71
- ? `Harbor CLI ${latest} is out (you have ${version}): npm install -g @harborppm/cli@latest`
71
+ ? `Harbor CLI ${latest} is out (you have ${version}): npm install -g @harborppm/cli@latest --prefer-online`
72
72
  : null;
73
73
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harborppm/cli",
3
- "version": "0.14.0",
3
+ "version": "0.15.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",