shepherd-onboard 0.1.10 → 0.1.11
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 +19 -3
- package/bin/shepherd-onboard.js +37 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Give this to a coding agent:
|
|
|
10
10
|
npx -y shepherd-onboard@latest agent
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
The command prints the exact prompt the agent should follow, then the exact follow-up commands to open Shepherd WorkOS login/signup, open source auth, open Granola's API key page, finalize, start cloud raw polling/backfills, and install local Messages sync.
|
|
13
|
+
The command prints the exact prompt the agent should follow, then the exact follow-up commands to open Shepherd WorkOS login/signup, guide Google Workspace Admin Console delegation, open source auth for non-Google sources, open Granola's API key page, finalize, start cloud raw polling/backfills, and install local Messages sync.
|
|
14
14
|
The agent prompt tells coding agents to ask short selection questions first: existing/new org, sources to connect, and Messages skip/provide-handle. If Messages is selected, the agent runs `messages-chats --json`, shows the 20 most recent local Messages chats with contact/group names, and asks which chats to sync. Account creation/relinking always starts with Shepherd WorkOS auth.
|
|
15
15
|
Existing-organization joins are verified from Shepherd login and company email domain; the typed org name is not trusted by itself.
|
|
16
16
|
|
|
@@ -27,7 +27,7 @@ The command:
|
|
|
27
27
|
- creates or reuses the Shepherd customer account from the WorkOS-authenticated email
|
|
28
28
|
- creates or reuses the organization, including case-insensitive and close-name matches
|
|
29
29
|
- only reuses an existing organization when the authenticated account is allowed to join it
|
|
30
|
-
-
|
|
30
|
+
- prints Google Workspace domain-wide delegation setup for a Workspace super admin
|
|
31
31
|
- opens Slack authorization
|
|
32
32
|
- opens the Granola desktop app to Settings -> Connectors -> API keys
|
|
33
33
|
- collects the Granola API key after opening the Granola screen when Granola is enabled
|
|
@@ -38,6 +38,22 @@ The command:
|
|
|
38
38
|
|
|
39
39
|
The command does not expose Railway, database, Redis, or internal service details to the user.
|
|
40
40
|
|
|
41
|
+
## Google Workspace Domain-wide Delegation
|
|
42
|
+
|
|
43
|
+
Business customers connect Google Workspace once as an admin. Employees do not create service accounts and do not each complete Google OAuth for Gmail, Calendar, Drive, Docs, Sheets, Slides, Tasks, or Contacts.
|
|
44
|
+
|
|
45
|
+
Customer-side setup in Google Admin Console:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
App name: Shepherd
|
|
49
|
+
Service account email: gigabrain-delegation@shepherd-gigabrain.iam.gserviceaccount.com
|
|
50
|
+
Domain-wide delegation OAuth Client ID: 118363960386741325727
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The customer super admin authorizes that Client ID with the scopes printed by the CLI. The customer does not upload service account JSON; Shepherd stores its private service account JSON server-side in `GOOGLE_WORKSPACE_SERVICE_ACCOUNT_KEY_JSON` and requests delegated tokens with `sub=<allowed_employee_email>`.
|
|
54
|
+
|
|
55
|
+
Shepherd must still enforce selected users and groups internally before impersonating or polling employee emails.
|
|
56
|
+
|
|
41
57
|
## Options
|
|
42
58
|
|
|
43
59
|
```sh
|
|
@@ -48,7 +64,7 @@ The command does not expose Railway, database, Redis, or internal service detail
|
|
|
48
64
|
--messages-handle <value> Messages phone number or Apple ID email
|
|
49
65
|
--messages-chat-ids <ids> Comma-separated chat IDs selected from messages-chats
|
|
50
66
|
--messages-backfill-days Local Messages backfill window, default 30
|
|
51
|
-
--no-google Skip Google Workspace (Gmail/Drive/Docs/Calendar)
|
|
67
|
+
--no-google Skip Google Workspace (Gmail/Drive/Docs/Calendar/Sheets/Slides/Tasks/Contacts)
|
|
52
68
|
--no-slack Skip Slack
|
|
53
69
|
--no-granola Skip Granola
|
|
54
70
|
--no-open-granola Do not open the Granola API key screen
|
package/bin/shepherd-onboard.js
CHANGED
|
@@ -13,6 +13,7 @@ const DEFAULT_AGENT_STATE_PATH = join(homedir(), ".shepherd", "raw-onboarding-ag
|
|
|
13
13
|
const MAX_BATCH_SIZE = 50;
|
|
14
14
|
const MAX_QUEUE_MESSAGES = 10_000;
|
|
15
15
|
const DEFAULT_RECENT_MESSAGE_CHATS = 20;
|
|
16
|
+
const GRANOLA_API_KEYS_PATH = "/settings/integrations/api-keys";
|
|
16
17
|
const GOOGLE_WORKSPACE_DELEGATION_APP_NAME = "Shepherd";
|
|
17
18
|
const GOOGLE_WORKSPACE_DELEGATION_SERVICE_ACCOUNT_EMAIL =
|
|
18
19
|
"gigabrain-delegation@shepherd-gigabrain.iam.gserviceaccount.com";
|
|
@@ -283,7 +284,9 @@ async function runAgentOnboarding() {
|
|
|
283
284
|
account: session.account,
|
|
284
285
|
sources,
|
|
285
286
|
authUrls: session.authUrls ?? {},
|
|
286
|
-
googleWorkspaceDelegation:
|
|
287
|
+
googleWorkspaceDelegation: sources.google
|
|
288
|
+
? googleWorkspaceDelegationSetup(session.googleWorkspaceDelegation)
|
|
289
|
+
: undefined,
|
|
287
290
|
workosAuth,
|
|
288
291
|
createdAt: new Date().toISOString(),
|
|
289
292
|
});
|
|
@@ -337,15 +340,16 @@ async function runAgentOnboarding() {
|
|
|
337
340
|
}
|
|
338
341
|
console.log(`State saved: ${statePath}`);
|
|
339
342
|
console.log("\nCoding agent next steps:");
|
|
343
|
+
let step = 1;
|
|
340
344
|
if (sources.google) {
|
|
341
|
-
console.log(
|
|
345
|
+
console.log(`${step++}. Ask the user's Google Workspace super admin to authorize Shepherd in Google Admin Console with the Client ID and scopes above.`);
|
|
342
346
|
}
|
|
343
|
-
if (sources.slack) console.log(
|
|
344
|
-
if (sources.granola) console.log(
|
|
347
|
+
if (sources.slack) console.log(`${step++}. Ask the user to finish the opened Slack browser authorization.`);
|
|
348
|
+
if (sources.granola) console.log(`${step++}. Ask the user for their Granola API key from the Granola Mac app.`);
|
|
345
349
|
if (sources.messages) {
|
|
346
|
-
console.log(
|
|
350
|
+
console.log(`${step++}. Run ${agentCommand()} messages-chats --json, ask the user which recent chats to sync, and keep those chat IDs.`);
|
|
347
351
|
}
|
|
348
|
-
console.log(
|
|
352
|
+
console.log(`${step++}. Run:`);
|
|
349
353
|
console.log(` ${agentCommand()} agent --continue --messages-handle "<phone_or_apple_id>" --messages-chat-ids "<comma_separated_chat_ids>" --granola-api-key "<granola_key>"`);
|
|
350
354
|
console.log(" Omit either optional flag if that source is not being connected.");
|
|
351
355
|
}
|
|
@@ -758,7 +762,7 @@ Ask with short interactive prompts, not as one pasted checklist.
|
|
|
758
762
|
|
|
759
763
|
Start with selection questions to determine intent:
|
|
760
764
|
1. Organization: Join existing org, or Create new org.
|
|
761
|
-
2. Sources: Google Workspace (Gmail/Drive/Docs/Calendar), Slack, Granola, Messages. Allow multi-select if your interface supports it.
|
|
765
|
+
2. Sources: Google Workspace (Gmail/Drive/Docs/Calendar/Sheets/Slides/Tasks/Contacts), Slack, Granola, Messages. Allow multi-select if your interface supports it.
|
|
762
766
|
3. Messages, if selected: Skip Messages, or Provide handle.
|
|
763
767
|
|
|
764
768
|
When discussing existing orgs, keep it short: Shepherd verifies the join from their Shepherd login and company email domain. The org name they type is not trusted by itself.
|
|
@@ -767,6 +771,7 @@ Before source setup, always run:
|
|
|
767
771
|
${payload.loginCommand}
|
|
768
772
|
|
|
769
773
|
That opens one WorkOS Shepherd login/signup flow and saves a local onboarding auth session. It creates or relinks the Shepherd customer account; the next setup command attaches sources to the same production cloud account rows.
|
|
774
|
+
Do not use WorkOS Auth, WorkOS Pipes, or per-user Google OAuth for Google Workspace. WorkOS login is only Shepherd account identity.
|
|
770
775
|
|
|
771
776
|
Ask the user for:
|
|
772
777
|
1. Full name
|
|
@@ -791,8 +796,21 @@ Add skip flags for sources the user did not select:
|
|
|
791
796
|
- --no-granola
|
|
792
797
|
- --no-messages
|
|
793
798
|
|
|
794
|
-
That command creates/reuses the customer user and org,
|
|
795
|
-
|
|
799
|
+
That command creates/reuses the customer user and org, prints Google Workspace domain-wide delegation setup values, opens Slack browser auth if selected, and saves local state.
|
|
800
|
+
|
|
801
|
+
If Google Workspace is selected, show this Admin Console setup to the user and have their Google Workspace super admin authorize it:
|
|
802
|
+
|
|
803
|
+
App name: ${payload.googleWorkspaceDelegation.appName}
|
|
804
|
+
Service account email: ${payload.googleWorkspaceDelegation.serviceAccountEmail}
|
|
805
|
+
Domain-wide delegation OAuth Client ID: ${payload.googleWorkspaceDelegation.clientId}
|
|
806
|
+
|
|
807
|
+
Scopes:
|
|
808
|
+
${payload.googleWorkspaceDelegation.scopes.join("\n")}
|
|
809
|
+
|
|
810
|
+
The customer does not create a service account and does not upload service account JSON in the default Shepherd-managed flow. Their super admin only authorizes the Client ID and scopes in Google Admin Console. Shepherd's backend stores and uses its private service account JSON server-side.
|
|
811
|
+
Shepherd must still enforce selected users and groups internally before polling or impersonating any employee email.
|
|
812
|
+
|
|
813
|
+
If Slack is selected and your browser automation can complete that auth screen, do it. If it cannot click through OAuth screens, leave the opened browser tab for the user and ask them to complete Slack auth.
|
|
796
814
|
|
|
797
815
|
If Granola is selected, it also opens the Granola desktop app. If your local app automation can navigate it, go to:
|
|
798
816
|
Settings -> Connectors -> API keys
|
|
@@ -802,7 +820,7 @@ If Granola did not come forward, run:
|
|
|
802
820
|
${payload.granolaApiKeyCommand}
|
|
803
821
|
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.
|
|
804
822
|
|
|
805
|
-
After Google Workspace and Slack browser auth
|
|
823
|
+
After Google Workspace Admin Console delegation and Slack browser auth are complete, and after the user has copied a Granola API key from the opened Granola screen if they want Granola, run:
|
|
806
824
|
${payload.continueCommand} --messages-handle "<phone_or_apple_id>" --messages-chat-ids "<comma_separated_chat_ids>" --granola-api-key "<granola_key>"
|
|
807
825
|
|
|
808
826
|
Omit either optional flag if that source is not being connected.
|
|
@@ -1017,7 +1035,7 @@ async function openOrPrint(url, opts) {
|
|
|
1017
1035
|
}
|
|
1018
1036
|
|
|
1019
1037
|
async function openGranolaApiKeys(opts = {}) {
|
|
1020
|
-
const deepLink =
|
|
1038
|
+
const deepLink = granolaApiKeysDeepLink();
|
|
1021
1039
|
if (opts.noOpen) {
|
|
1022
1040
|
console.log("Granola API keys: open the Granola desktop app -> Settings -> Connectors -> API keys");
|
|
1023
1041
|
return { opened: false, target: "Granola Settings -> Connectors -> API keys" };
|
|
@@ -1030,19 +1048,20 @@ async function openGranolaApiKeys(opts = {}) {
|
|
|
1030
1048
|
|
|
1031
1049
|
console.log("\nOpening Granola API keys");
|
|
1032
1050
|
const bundleResult = await execFileQuiet("open", ["-b", "com.granola.app"], { ignoreError: true, captureError: true });
|
|
1033
|
-
await sleep(
|
|
1034
|
-
await execFileQuiet("open", [deepLink], { ignoreError: true, captureError: true });
|
|
1035
|
-
await sleep(
|
|
1051
|
+
await sleep(900);
|
|
1052
|
+
const deepLinkResult = await execFileQuiet("open", ["-u", deepLink], { ignoreError: true, captureError: true });
|
|
1053
|
+
await sleep(700);
|
|
1054
|
+
const deepLinkRetryResult = await execFileQuiet("open", ["-u", deepLink], { ignoreError: true, captureError: true });
|
|
1055
|
+
await sleep(300);
|
|
1036
1056
|
const activateByBundleResult = await execFileQuiet("osascript", [
|
|
1037
1057
|
"-e",
|
|
1038
1058
|
'tell application id "com.granola.app" to activate',
|
|
1039
1059
|
], { ignoreError: true, captureError: true });
|
|
1040
1060
|
const activateByNameResult = await execFileQuiet("open", ["-a", "Granola"], { ignoreError: true, captureError: true });
|
|
1041
1061
|
|
|
1042
|
-
if (bundleResult.error && activateByBundleResult.error && activateByNameResult.error) {
|
|
1062
|
+
if (bundleResult.error && deepLinkResult.error && deepLinkRetryResult.error && activateByBundleResult.error && activateByNameResult.error) {
|
|
1043
1063
|
await execFileQuiet("open", ["-a", "Granola"], { ignoreError: true });
|
|
1044
1064
|
}
|
|
1045
|
-
await navigateGranolaApiKeysWithAppleScript();
|
|
1046
1065
|
|
|
1047
1066
|
return {
|
|
1048
1067
|
opened: true,
|
|
@@ -1052,32 +1071,8 @@ async function openGranolaApiKeys(opts = {}) {
|
|
|
1052
1071
|
};
|
|
1053
1072
|
}
|
|
1054
1073
|
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
tell application id "com.granola.app" to activate
|
|
1058
|
-
delay 0.7
|
|
1059
|
-
tell application "System Events"
|
|
1060
|
-
if not (exists process "Granola") then return
|
|
1061
|
-
tell process "Granola"
|
|
1062
|
-
set frontmost to true
|
|
1063
|
-
try
|
|
1064
|
-
set {x, y} to position of window 1
|
|
1065
|
-
set {w, h} to size of window 1
|
|
1066
|
-
click at {x + w - 55, y + 810}
|
|
1067
|
-
return
|
|
1068
|
-
end try
|
|
1069
|
-
try
|
|
1070
|
-
keystroke "," using command down
|
|
1071
|
-
delay 0.5
|
|
1072
|
-
set {x, y} to position of window 1
|
|
1073
|
-
set {w, h} to size of window 1
|
|
1074
|
-
click at {x + w - 55, y + 810}
|
|
1075
|
-
return
|
|
1076
|
-
end try
|
|
1077
|
-
end tell
|
|
1078
|
-
end tell
|
|
1079
|
-
`;
|
|
1080
|
-
await execFileQuiet("osascript", ["-e", script], { ignoreError: true });
|
|
1074
|
+
function granolaApiKeysDeepLink() {
|
|
1075
|
+
return `granola://open?path=${encodeURIComponent(GRANOLA_API_KEYS_PATH)}`;
|
|
1081
1076
|
}
|
|
1082
1077
|
|
|
1083
1078
|
async function postJson(url, body, opts = {}) {
|