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 +11 -3
- package/bin/shepherd-onboard.js +66 -22
- package/package.json +1 -1
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
|
|
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
|
|
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
|
+
```
|
package/bin/shepherd-onboard.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
852
|
+
const deepLink = "granola://settings/integrations";
|
|
842
853
|
if (opts.noOpen) {
|
|
843
|
-
console.log(
|
|
844
|
-
return { opened: false, target:
|
|
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
|
|
854
|
-
await sleep(
|
|
855
|
-
|
|
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 (
|
|
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:
|
|
867
|
-
|
|
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",
|