shepherd-onboard 0.1.5 → 0.1.7

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
@@ -25,9 +25,9 @@ The command:
25
25
  - asks for name, organization, and an optional local Messages handle
26
26
  - creates or reuses the Shepherd customer account from the WorkOS-authenticated email
27
27
  - creates or reuses the organization, including case-insensitive and close-name matches
28
- - opens Google authorization for Gmail, Docs, and Calendar consent
28
+ - opens Google Workspace authorization for Gmail, Drive, Docs, and Calendar consent
29
29
  - opens Slack authorization
30
- - opens Granola's API key screen with `open 'granola://settings/connectors/api-keys'`
30
+ - opens the Granola desktop app to Settings -> Connectors -> API keys
31
31
  - collects the Granola API key after opening the Granola screen when Granola is enabled
32
32
  - sets up local macOS Messages raw sync with a background LaunchAgent
33
33
  - starts raw polling/backfill for connected sources
@@ -44,10 +44,18 @@ The command does not expose Railway, database, Redis, or internal service detail
44
44
  --granola-api-key <key> Granola API key
45
45
  --messages-handle <value> Messages phone number or Apple ID email
46
46
  --messages-backfill-days Local Messages backfill window, default 30
47
- --no-google Skip Google/Gmail/Docs/Calendar
47
+ --no-google Skip Google Workspace (Gmail/Drive/Docs/Calendar)
48
48
  --no-slack Skip Slack
49
49
  --no-granola Skip Granola
50
50
  --no-open-granola Do not open the Granola API key screen
51
51
  --no-messages Skip local Messages
52
52
  --no-open Print auth URLs instead of opening the browser
53
53
  ```
54
+
55
+ ## Granola API Keys
56
+
57
+ If Granola does not land on the API keys page, run:
58
+
59
+ ```sh
60
+ npx -y shepherd-onboard@latest granola-api-keys
61
+ ```
@@ -32,6 +32,8 @@ async function dispatch() {
32
32
  await runOnboarding();
33
33
  } else if (command === "agent") {
34
34
  await runAgentOnboarding();
35
+ } else if (command === "granola-api-keys") {
36
+ await openGranolaApiKeys({ noOpen: Boolean(args["no-open"]) });
35
37
  } else if (command === "messages-agent") {
36
38
  await runMessagesAgent();
37
39
  } else {
@@ -80,9 +82,9 @@ async function runOnboarding() {
80
82
  }
81
83
 
82
84
  if (session.authUrls?.google) {
83
- console.log("\nGoogle, Gmail, Docs, and Calendar authorization");
85
+ console.log("\nGoogle Workspace authorization");
84
86
  await openOrPrint(session.authUrls.google, { noOpen });
85
- await waitForEnter("Complete Google authorization in the browser, then press Enter.");
87
+ await waitForEnter("Complete Google Workspace authorization in the browser, then press Enter.");
86
88
  }
87
89
 
88
90
  if (session.authUrls?.slack) {
@@ -489,6 +491,7 @@ Usage:
489
491
  npx -y ${PACKAGE_NAME}@latest agent --name <name> --org <organization>
490
492
  npx -y ${PACKAGE_NAME}@latest agent --continue --granola-api-key <key> --messages-handle <value>
491
493
  npx -y ${PACKAGE_NAME}@latest agent --status
494
+ npx -y ${PACKAGE_NAME}@latest granola-api-keys
492
495
 
493
496
  Agent mode is non-interactive. It prints the user prompt and exact commands a coding agent should run.
494
497
  Always run --login first. WorkOS login/signup creates or relinks the Shepherd account before source setup.
@@ -516,6 +519,7 @@ Options:
516
519
  Usage:
517
520
  npx -y ${PACKAGE_NAME}@latest
518
521
  npx -y ${PACKAGE_NAME}@latest agent
522
+ npx -y ${PACKAGE_NAME}@latest granola-api-keys
519
523
 
520
524
  Options:
521
525
  --email <email> Advanced: must match the WorkOS-authenticated email.
@@ -525,7 +529,7 @@ Options:
525
529
  --messages-handle <value> Messages phone number or Apple ID email.
526
530
  --messages-backfill-days <days>
527
531
  Local Messages backfill window. Defaults to 30.
528
- --no-google Skip Google/Gmail/Docs/Calendar.
532
+ --no-google Skip Google Workspace (Gmail/Drive/Docs/Calendar).
529
533
  --no-slack Skip Slack.
530
534
  --no-granola Skip Granola.
531
535
  --no-open-granola Do not open the Granola API key screen.
@@ -561,7 +565,7 @@ function printAgentContract() {
561
565
  {
562
566
  label: "Sources",
563
567
  prompt: "Which sources should Shepherd connect for raw sync?",
564
- options: ["Google/Gmail/Docs/Calendar", "Slack", "Granola", "Messages"],
568
+ options: ["Google Workspace (Gmail/Drive/Docs/Calendar)", "Slack", "Granola", "Messages"],
565
569
  multiSelect: true,
566
570
  },
567
571
  {
@@ -576,7 +580,7 @@ function printAgentContract() {
576
580
  "Messages phone number or Apple ID email, if they want local Messages connected",
577
581
  ],
578
582
  afterStartCommand: [
579
- "Complete the opened Google authorization for Gmail, Docs, and Calendar.",
583
+ "Complete the opened Google Workspace authorization for Gmail, Drive, Docs, and Calendar.",
580
584
  "Complete the opened Slack authorization.",
581
585
  "Create/copy the Granola API key from the opened Granola Mac app API key screen, if they want Granola connected.",
582
586
  ],
@@ -597,7 +601,8 @@ function printAgentContract() {
597
601
  ],
598
602
  statusCommand: `${command} agent --status`,
599
603
  expectedResult: "Cloud sources start raw polling/backfill in the customer-facing Shepherd environment. Local Messages starts via a macOS LaunchAgent when run on macOS. Downstream wiki, memory, and summary compilers remain outside this onboarding flow.",
600
- granolaApiKeyCommand: "open 'granola://settings/connectors/api-keys'",
604
+ granolaApiKeyCommand: `${command} granola-api-keys`,
605
+ granolaApiKeyPath: "Granola desktop app -> Settings -> Connectors -> API keys",
601
606
  };
602
607
 
603
608
  if (args.json) {
@@ -615,7 +620,7 @@ Ask with short interactive prompts, not as one pasted checklist.
615
620
 
616
621
  Start with selection questions to determine intent:
617
622
  1. Organization: Join existing org, or Create new org.
618
- 2. Sources: Google/Gmail/Docs/Calendar, Slack, Granola, Messages. Allow multi-select if your interface supports it.
623
+ 2. Sources: Google Workspace (Gmail/Drive/Docs/Calendar), Slack, Granola, Messages. Allow multi-select if your interface supports it.
619
624
  3. Messages, if selected: Skip Messages, or Provide handle.
620
625
 
621
626
  Before source setup, always run:
@@ -641,12 +646,18 @@ Add skip flags for sources the user did not select:
641
646
  - --no-granola
642
647
  - --no-messages
643
648
 
644
- That command creates/reuses the customer user and org, opens Google/Slack browser auth, and saves local state.
645
- It also runs:
646
- open 'granola://settings/connectors/api-keys'
647
- and activates Granola so the user can create/copy the API key.
649
+ That command creates/reuses the customer user and org, opens Google Workspace/Slack browser auth, and saves local state.
650
+ If your browser automation can complete those auth screens, do it. If it cannot click through OAuth screens, leave the opened browser tabs for the user and ask them to complete Google Workspace and Slack auth.
648
651
 
649
- After Google/Gmail/Docs/Calendar and Slack browser auth is complete, and after the user has copied a Granola API key from the opened Granola screen if they want Granola, run:
652
+ If Granola is selected, it also opens the Granola desktop app. If your local app automation can navigate it, go to:
653
+ Settings -> Connectors -> API keys
654
+ Then have the user create/copy the API key.
655
+
656
+ If Granola did not come forward, run:
657
+ ${payload.granolaApiKeyCommand}
658
+ That command opens Granola and tries to navigate to Settings -> Connectors -> API keys. If your tool cannot click inside Granola, leave Granola open and ask the user to go to that screen.
659
+
660
+ After Google Workspace and Slack browser auth is complete, and after the user has copied a Granola API key from the opened Granola screen if they want Granola, run:
650
661
  ${payload.continueCommand} --messages-handle "<phone_or_apple_id>" --granola-api-key "<granola_key>"
651
662
 
652
663
  Omit either optional flag if that source is not being connected.
@@ -723,7 +734,7 @@ function authenticatedName(authenticated) {
723
734
 
724
735
  function agentNeedsUserAction(sources, opened) {
725
736
  const actions = [];
726
- if (sources.google && opened.includes("google")) actions.push("Complete Google browser authorization for Gmail, Docs, and Calendar consent.");
737
+ if (sources.google && opened.includes("google")) actions.push("Complete Google Workspace browser authorization for Gmail, Drive, Docs, and Calendar consent.");
727
738
  if (sources.slack && opened.includes("slack")) actions.push("Complete Slack browser authorization.");
728
739
  if (sources.granola) actions.push("Create/copy a Granola API key from the Granola Mac app.");
729
740
  if (sources.messages) actions.push("Pass the Messages phone number or Apple ID email collected before starting onboarding.");
@@ -838,10 +849,10 @@ async function openOrPrint(url, opts) {
838
849
  }
839
850
 
840
851
  async function openGranolaApiKeys(opts = {}) {
841
- const deepLink = "granola://settings/connectors/api-keys";
852
+ const deepLink = "granola://settings/integrations";
842
853
  if (opts.noOpen) {
843
- console.log(`Granola API keys: ${deepLink}`);
844
- return { opened: false, target: deepLink };
854
+ console.log("Granola API keys: open the Granola desktop app -> Settings -> Connectors -> API keys");
855
+ return { opened: false, target: "Granola Settings -> Connectors -> API keys" };
845
856
  }
846
857
 
847
858
  if (platform() !== "darwin") {
@@ -850,24 +861,57 @@ async function openGranolaApiKeys(opts = {}) {
850
861
  }
851
862
 
852
863
  console.log("\nOpening Granola API keys");
853
- const deepLinkResult = await execFileQuiet("open", [deepLink], { ignoreError: true, captureError: true });
854
- await sleep(700);
855
- const activateResult = await execFileQuiet("osascript", [
864
+ const bundleResult = await execFileQuiet("open", ["-b", "com.granola.app"], { ignoreError: true, captureError: true });
865
+ await sleep(500);
866
+ await execFileQuiet("open", [deepLink], { ignoreError: true, captureError: true });
867
+ await sleep(500);
868
+ const activateByBundleResult = await execFileQuiet("osascript", [
856
869
  "-e",
857
870
  'tell application id "com.granola.app" to activate',
858
871
  ], { ignoreError: true, captureError: true });
872
+ const activateByNameResult = await execFileQuiet("open", ["-a", "Granola"], { ignoreError: true, captureError: true });
859
873
 
860
- if (deepLinkResult.error || activateResult.error) {
874
+ if (bundleResult.error && activateByBundleResult.error && activateByNameResult.error) {
861
875
  await execFileQuiet("open", ["-a", "Granola"], { ignoreError: true });
862
876
  }
877
+ await navigateGranolaApiKeysWithAppleScript();
863
878
 
864
879
  return {
865
880
  opened: true,
866
- target: deepLink,
867
- fallback: "If Granola does not land on the API key screen, open Settings -> Connectors -> API keys in Granola.",
881
+ target: "Granola Settings -> Connectors -> API keys",
882
+ attemptedDeepLink: deepLink,
883
+ fallback: "If local app automation cannot click inside Granola, ask the user to open Settings -> Connectors -> API keys.",
868
884
  };
869
885
  }
870
886
 
887
+ async function navigateGranolaApiKeysWithAppleScript() {
888
+ const script = `
889
+ tell application id "com.granola.app" to activate
890
+ delay 0.7
891
+ tell application "System Events"
892
+ if not (exists process "Granola") then return
893
+ tell process "Granola"
894
+ set frontmost to true
895
+ try
896
+ set {x, y} to position of window 1
897
+ set {w, h} to size of window 1
898
+ click at {x + w - 55, y + 665}
899
+ return
900
+ end try
901
+ try
902
+ keystroke "," using command down
903
+ delay 0.5
904
+ set {x, y} to position of window 1
905
+ set {w, h} to size of window 1
906
+ click at {x + w - 55, y + 665}
907
+ return
908
+ end try
909
+ end tell
910
+ end tell
911
+ `;
912
+ await execFileQuiet("osascript", ["-e", script], { ignoreError: true });
913
+ }
914
+
871
915
  async function postJson(url, body, opts = {}) {
872
916
  const res = await fetch(url, {
873
917
  method: "POST",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shepherd-onboard",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Customer-facing Shepherd raw sync onboarding CLI",
5
5
  "type": "module",
6
6
  "bin": {