shepherd-onboard 0.1.19 → 0.1.21
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/bin/shepherd-onboard.js +152 -56
- package/package.json +1 -1
package/bin/shepherd-onboard.js
CHANGED
|
@@ -17,8 +17,10 @@ const MAX_BATCH_SIZE = 50;
|
|
|
17
17
|
const MAX_QUEUE_MESSAGES = 10_000;
|
|
18
18
|
const DEFAULT_MESSAGE_CHAT_SEARCH_LIMIT = 200;
|
|
19
19
|
const INITIAL_MESSAGE_CHAT_ROWS = 20;
|
|
20
|
+
const AGENT_MODALITY_ORDER = ["google", "slack", "granola", "messages"];
|
|
20
21
|
const SHEPHERD_LOGO_PATH = join(PACKAGE_DIR, "assets", "shepherd_G_vector_136033.png");
|
|
21
22
|
const GRANOLA_API_KEYS_PATH = "/settings/integrations/api-keys";
|
|
23
|
+
const GOOGLE_WORKSPACE_DELEGATION_ADMIN_URL = "https://admin.google.com/ac/owl/domainwidedelegation";
|
|
22
24
|
const GOOGLE_WORKSPACE_DELEGATION_APP_NAME = "Shepherd";
|
|
23
25
|
const GOOGLE_WORKSPACE_DELEGATION_SERVICE_ACCOUNT_EMAIL =
|
|
24
26
|
"gigabrain-delegation@shepherd-gigabrain.iam.gserviceaccount.com";
|
|
@@ -148,6 +150,7 @@ async function runOnboarding() {
|
|
|
148
150
|
if (sources.google) {
|
|
149
151
|
console.log("\nGoogle Workspace domain-wide delegation");
|
|
150
152
|
printGoogleWorkspaceDelegationSetup(session.googleWorkspaceDelegation);
|
|
153
|
+
await openGoogleWorkspaceDelegationAdmin({ noOpen });
|
|
151
154
|
await waitForEnter("After the Google Workspace super admin authorizes Shepherd in Admin Console, press Enter.");
|
|
152
155
|
}
|
|
153
156
|
|
|
@@ -296,30 +299,23 @@ async function runAgentOnboarding() {
|
|
|
296
299
|
createdAt: new Date().toISOString(),
|
|
297
300
|
});
|
|
298
301
|
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
opened.push(provider);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
let granolaApiKeyPage = null;
|
|
308
|
-
if (sources.granola && !args["no-open-granola"]) {
|
|
309
|
-
granolaApiKeyPage = await openGranolaApiKeys({ noOpen });
|
|
310
|
-
}
|
|
302
|
+
const currentAction = await openNextAgentModality({
|
|
303
|
+
sources,
|
|
304
|
+
authUrls: session.authUrls ?? {},
|
|
305
|
+
noOpen,
|
|
306
|
+
});
|
|
311
307
|
|
|
312
308
|
if (args.json) {
|
|
313
309
|
console.log(JSON.stringify({
|
|
314
310
|
status: "auth_required",
|
|
315
311
|
account: publicAgentAccount(session.account),
|
|
316
|
-
opened,
|
|
312
|
+
opened: currentAction?.opened ? [currentAction.source] : [],
|
|
317
313
|
googleWorkspaceDelegation: sources.google ? googleWorkspaceDelegationSetup(session.googleWorkspaceDelegation) : undefined,
|
|
318
|
-
|
|
314
|
+
currentAction,
|
|
319
315
|
statePath,
|
|
320
316
|
messagesChatsCommand: sources.messages ? `${agentCommand()} messages-chats` : undefined,
|
|
321
317
|
nextCommand: `${agentCommand()} agent --continue --messages-handle "<phone_or_apple_id>" --messages-chat-ids "<comma_separated_chat_ids>" --granola-api-key "<granola_key>"`,
|
|
322
|
-
needsUserAction: agentNeedsUserAction(sources,
|
|
318
|
+
needsUserAction: agentNeedsUserAction(sources, currentAction),
|
|
323
319
|
}, null, 2));
|
|
324
320
|
return;
|
|
325
321
|
}
|
|
@@ -330,31 +326,13 @@ async function runAgentOnboarding() {
|
|
|
330
326
|
if (session.account.organizationMatch?.type && session.account.organizationMatch.type !== "created") {
|
|
331
327
|
console.log(`Matched existing organization by ${session.account.organizationMatch.type}.`);
|
|
332
328
|
}
|
|
333
|
-
if (opened.length) {
|
|
334
|
-
console.log(`Opened browser authorization: ${opened.join(", ")}`);
|
|
335
|
-
}
|
|
336
|
-
if (sources.google) {
|
|
337
|
-
console.log("\nGoogle Workspace domain-wide delegation setup:");
|
|
338
|
-
printGoogleWorkspaceDelegationSetup(session.googleWorkspaceDelegation);
|
|
339
|
-
}
|
|
340
|
-
if (noOpen) {
|
|
341
|
-
for (const [provider, url] of Object.entries(session.authUrls ?? {})) {
|
|
342
|
-
if (provider === "google") continue;
|
|
343
|
-
console.log(`${provider} auth URL: ${url}`);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
329
|
console.log(`State saved: ${statePath}`);
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (sources.granola) console.log(`${step++}. Ask the user for their Granola API key from the Granola Mac app.`);
|
|
354
|
-
if (sources.messages) {
|
|
355
|
-
console.log(`${step++}. Run ${agentCommand()} messages-chats, have the user select chats in the browser page, and keep the printed chat IDs.`);
|
|
356
|
-
}
|
|
357
|
-
console.log(`${step++}. Run:`);
|
|
330
|
+
|
|
331
|
+
printAgentCurrentAction(currentAction, {
|
|
332
|
+
googleWorkspaceDelegation: session.googleWorkspaceDelegation,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
console.log("\nAfter that modality is complete, run:");
|
|
358
336
|
console.log(` ${agentCommand()} agent --continue --messages-handle "<phone_or_apple_id>" --messages-chat-ids "<comma_separated_chat_ids>" --granola-api-key "<granola_key>"`);
|
|
359
337
|
console.log(" Omit either optional flag if that source is not being connected.");
|
|
360
338
|
}
|
|
@@ -477,11 +455,20 @@ async function continueAgentOnboarding() {
|
|
|
477
455
|
}
|
|
478
456
|
|
|
479
457
|
const errors = finalized.errors && Object.keys(finalized.errors).length ? finalized.errors : null;
|
|
458
|
+
const currentAction = errors
|
|
459
|
+
? await openNextAgentModality({
|
|
460
|
+
sources: state.sources,
|
|
461
|
+
authUrls: state.authUrls ?? {},
|
|
462
|
+
noOpen: Boolean(args["no-open"]),
|
|
463
|
+
pendingSources: pendingAgentSources(state.sources, errors),
|
|
464
|
+
})
|
|
465
|
+
: null;
|
|
480
466
|
if (args.json) {
|
|
481
467
|
console.log(JSON.stringify({
|
|
482
468
|
status: errors ? "waiting" : "completed",
|
|
483
469
|
connected: Object.keys(finalized.connected ?? {}),
|
|
484
470
|
errors: errors ? safeErrorRecord(errors) : undefined,
|
|
471
|
+
currentAction,
|
|
485
472
|
nextCommand: errors ? `${agentCommand()} agent --continue --messages-handle "<phone_or_apple_id>" --messages-chat-ids "<comma_separated_chat_ids>" --granola-api-key "<granola_key>"` : undefined,
|
|
486
473
|
}, null, 2));
|
|
487
474
|
return;
|
|
@@ -492,7 +479,12 @@ async function continueAgentOnboarding() {
|
|
|
492
479
|
for (const [source, message] of Object.entries(errors)) {
|
|
493
480
|
console.log(`- ${source}: ${safeError(message)}`);
|
|
494
481
|
}
|
|
495
|
-
|
|
482
|
+
|
|
483
|
+
printAgentCurrentAction(currentAction, {
|
|
484
|
+
googleWorkspaceDelegation: state.googleWorkspaceDelegation,
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
console.log("\nAfter that modality is complete, rerun:");
|
|
496
488
|
console.log(` ${agentCommand()} agent --continue --messages-handle "<phone_or_apple_id>" --messages-chat-ids "<comma_separated_chat_ids>" --granola-api-key "<granola_key>"`);
|
|
497
489
|
console.log(" Omit either optional flag if that source is not being connected.");
|
|
498
490
|
return;
|
|
@@ -733,9 +725,9 @@ function printAgentContract() {
|
|
|
733
725
|
"Selected local Messages chats from the browser selector, if they want local Messages connected",
|
|
734
726
|
],
|
|
735
727
|
afterStartCommand: [
|
|
736
|
-
"
|
|
737
|
-
"
|
|
738
|
-
"
|
|
728
|
+
"Handle only the current modality opened or printed by the command.",
|
|
729
|
+
"After the current modality is complete, run the continue command to advance to the next unresolved modality.",
|
|
730
|
+
"Do not open Slack, Granola, or Messages while Google Workspace is still waiting; do not open Granola or Messages while Slack is still waiting.",
|
|
739
731
|
],
|
|
740
732
|
doNotDo: [
|
|
741
733
|
"Do not run wiki generation.",
|
|
@@ -813,9 +805,9 @@ Add skip flags for sources the user did not select:
|
|
|
813
805
|
- --no-granola
|
|
814
806
|
- --no-messages
|
|
815
807
|
|
|
816
|
-
That command creates/reuses the customer user and org,
|
|
808
|
+
That command creates/reuses the customer user and org, saves local state, and opens at most one source setup surface. It works one modality at a time after account setup: Google Workspace, then Slack, then Granola. If Messages details are still missing, it prints the Messages selector command instead of opening another auth surface. Do not manually open later source setup surfaces until the command tells you that source is the current modality.
|
|
817
809
|
|
|
818
|
-
If Google Workspace is
|
|
810
|
+
If Google Workspace is the current modality, the setup command opens the Admin Console domain-wide delegation page. Show this setup to the user and have their Google Workspace super admin authorize it:
|
|
819
811
|
|
|
820
812
|
App name: ${payload.googleWorkspaceDelegation.appName}
|
|
821
813
|
Service account email: ${payload.googleWorkspaceDelegation.serviceAccountEmail}
|
|
@@ -829,17 +821,17 @@ The setup command copies those scopes to the clipboard as one comma-separated st
|
|
|
829
821
|
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.
|
|
830
822
|
Shepherd must still enforce selected users and groups internally before polling or impersonating any employee email.
|
|
831
823
|
|
|
832
|
-
If Slack is
|
|
824
|
+
If Slack is the current modality 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. Do not open Granola or Messages until Slack is complete and the continue command advances.
|
|
833
825
|
|
|
834
|
-
If Granola is
|
|
826
|
+
If Granola is the current modality, the command opens the Granola desktop app. If your local app automation can navigate it, go to:
|
|
835
827
|
Settings -> Connectors -> API keys
|
|
836
828
|
Then have the user create/copy the API key.
|
|
837
829
|
|
|
838
|
-
If Granola did not come forward, run:
|
|
830
|
+
If Granola is the current modality and did not come forward, run:
|
|
839
831
|
${payload.granolaApiKeyCommand}
|
|
840
832
|
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.
|
|
841
833
|
|
|
842
|
-
After
|
|
834
|
+
After the current modality is complete, run:
|
|
843
835
|
${payload.continueCommand} --messages-handle "<phone_or_apple_id>" --messages-chat-ids "<comma_separated_chat_ids>" --granola-api-key "<granola_key>"
|
|
844
836
|
|
|
845
837
|
Omit either optional flag if that source is not being connected.
|
|
@@ -956,13 +948,108 @@ function authenticatedName(authenticated) {
|
|
|
956
948
|
return authenticated?.workosUser?.name ?? authenticated?.account?.name ?? null;
|
|
957
949
|
}
|
|
958
950
|
|
|
959
|
-
function
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
951
|
+
function pendingAgentSources(sources, errors) {
|
|
952
|
+
const pending = new Set(Object.keys(errors ?? {}));
|
|
953
|
+
return AGENT_MODALITY_ORDER.filter((source) => sources?.[source] && pending.has(source));
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
async function openNextAgentModality({ sources, authUrls = {}, noOpen = false, pendingSources = null }) {
|
|
957
|
+
const pending = pendingSources ?? AGENT_MODALITY_ORDER.filter((source) => sources?.[source]);
|
|
958
|
+
for (const source of AGENT_MODALITY_ORDER) {
|
|
959
|
+
if (!sources?.[source] || !pending.includes(source)) continue;
|
|
960
|
+
|
|
961
|
+
if (source === "google") {
|
|
962
|
+
return {
|
|
963
|
+
source,
|
|
964
|
+
label: "Google Workspace",
|
|
965
|
+
...await openGoogleWorkspaceDelegationAdmin({ noOpen }),
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
if (source === "slack") {
|
|
970
|
+
const url = typeof authUrls.slack === "string" ? authUrls.slack : null;
|
|
971
|
+
if (!url) {
|
|
972
|
+
return {
|
|
973
|
+
source,
|
|
974
|
+
label: "Slack",
|
|
975
|
+
opened: false,
|
|
976
|
+
message: "Slack authorization URL was not returned by Shepherd.",
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
await openOrPrint(url, { noOpen });
|
|
980
|
+
return { source, label: "Slack", opened: !noOpen, url };
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
if (source === "granola") {
|
|
984
|
+
const result = await openGranolaApiKeys({ noOpen: noOpen || Boolean(args["no-open-granola"]) });
|
|
985
|
+
return { source, label: "Granola", ...result };
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
if (source === "messages") {
|
|
989
|
+
return {
|
|
990
|
+
source,
|
|
991
|
+
label: "Messages",
|
|
992
|
+
opened: false,
|
|
993
|
+
command: `${agentCommand()} messages-chats`,
|
|
994
|
+
message: "Run the local Messages chat selector and keep the printed chat IDs.",
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
return null;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function printAgentCurrentAction(action, opts = {}) {
|
|
1003
|
+
if (!action) {
|
|
1004
|
+
console.log("\nNo source setup page was opened.");
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
console.log(`\nCurrent modality: ${action.label}`);
|
|
1009
|
+
|
|
1010
|
+
if (action.source === "google") {
|
|
1011
|
+
if (action.opened) {
|
|
1012
|
+
console.log(`Opened Google Admin Console: ${action.url}`);
|
|
1013
|
+
} else {
|
|
1014
|
+
console.log(`Google Admin Console domain-wide delegation URL: ${action.url}`);
|
|
1015
|
+
}
|
|
1016
|
+
console.log("\nGoogle Workspace domain-wide delegation setup:");
|
|
1017
|
+
printGoogleWorkspaceDelegationSetup(opts.googleWorkspaceDelegation);
|
|
1018
|
+
console.log("\nAsk the user's Google Workspace super admin to authorize Shepherd before opening another source.");
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
if (action.source === "slack") {
|
|
1023
|
+
if (action.opened) {
|
|
1024
|
+
console.log("Opened Slack authorization in the browser.");
|
|
1025
|
+
} else if (action.url) {
|
|
1026
|
+
console.log(`Slack authorization URL: ${action.url}`);
|
|
1027
|
+
} else if (action.message) {
|
|
1028
|
+
console.log(action.message);
|
|
1029
|
+
}
|
|
1030
|
+
console.log("Ask the user to complete Slack authorization before opening another source.");
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
if (action.source === "granola") {
|
|
1035
|
+
if (action.target) console.log(`Granola target: ${action.target}`);
|
|
1036
|
+
console.log("Ask the user to create/copy the Granola API key before opening another source.");
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
if (action.source === "messages") {
|
|
1041
|
+
console.log(`Run: ${action.command}`);
|
|
1042
|
+
console.log("Have the user select specific local Messages chats; do not select all by default.");
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
function agentNeedsUserAction(sources, action) {
|
|
1047
|
+
if (!action) return [];
|
|
1048
|
+
if (action.source === "google") return ["Have the customer's Google Workspace super admin authorize Shepherd's domain-wide delegation Client ID and scopes in Google Admin Console."];
|
|
1049
|
+
if (action.source === "slack") return ["Complete Slack browser authorization."];
|
|
1050
|
+
if (action.source === "granola") return ["Create/copy a Granola API key from the Granola Mac app."];
|
|
1051
|
+
if (action.source === "messages") return ["Run messages-chats, have the user select local Messages contacts/groups in the browser, then pass the printed chat IDs with the Messages handle."];
|
|
1052
|
+
return [];
|
|
966
1053
|
}
|
|
967
1054
|
|
|
968
1055
|
function agentCommand() {
|
|
@@ -1072,6 +1159,15 @@ async function openOrPrint(url, opts) {
|
|
|
1072
1159
|
});
|
|
1073
1160
|
}
|
|
1074
1161
|
|
|
1162
|
+
async function openGoogleWorkspaceDelegationAdmin(opts = {}) {
|
|
1163
|
+
if (opts.noOpen) {
|
|
1164
|
+
console.log(`Google Admin Console domain-wide delegation URL: ${GOOGLE_WORKSPACE_DELEGATION_ADMIN_URL}`);
|
|
1165
|
+
return { opened: false, url: GOOGLE_WORKSPACE_DELEGATION_ADMIN_URL };
|
|
1166
|
+
}
|
|
1167
|
+
await openOrPrint(GOOGLE_WORKSPACE_DELEGATION_ADMIN_URL, { noOpen: false });
|
|
1168
|
+
return { opened: true, url: GOOGLE_WORKSPACE_DELEGATION_ADMIN_URL };
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1075
1171
|
async function openGranolaApiKeys(opts = {}) {
|
|
1076
1172
|
const deepLink = granolaApiKeysDeepLink();
|
|
1077
1173
|
if (opts.noOpen) {
|