shepherd-onboard 0.1.20 → 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 +141 -71
- package/package.json +1 -1
package/bin/shepherd-onboard.js
CHANGED
|
@@ -17,6 +17,7 @@ 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";
|
|
22
23
|
const GOOGLE_WORKSPACE_DELEGATION_ADMIN_URL = "https://admin.google.com/ac/owl/domainwidedelegation";
|
|
@@ -298,41 +299,23 @@ async function runAgentOnboarding() {
|
|
|
298
299
|
createdAt: new Date().toISOString(),
|
|
299
300
|
});
|
|
300
301
|
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
await openGoogleWorkspaceDelegationAdmin({ noOpen: false });
|
|
307
|
-
googleAdminConsole.opened = true;
|
|
308
|
-
} else {
|
|
309
|
-
googleAdminConsole.opened = false;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
for (const [provider, url] of Object.entries(session.authUrls ?? {})) {
|
|
313
|
-
if (typeof url !== "string") continue;
|
|
314
|
-
if (provider === "google") continue;
|
|
315
|
-
if (!noOpen) await openOrPrint(url, { noOpen: false });
|
|
316
|
-
opened.push(provider);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
let granolaApiKeyPage = null;
|
|
320
|
-
if (sources.granola && !args["no-open-granola"]) {
|
|
321
|
-
granolaApiKeyPage = await openGranolaApiKeys({ noOpen });
|
|
322
|
-
}
|
|
302
|
+
const currentAction = await openNextAgentModality({
|
|
303
|
+
sources,
|
|
304
|
+
authUrls: session.authUrls ?? {},
|
|
305
|
+
noOpen,
|
|
306
|
+
});
|
|
323
307
|
|
|
324
308
|
if (args.json) {
|
|
325
309
|
console.log(JSON.stringify({
|
|
326
310
|
status: "auth_required",
|
|
327
311
|
account: publicAgentAccount(session.account),
|
|
328
|
-
opened,
|
|
312
|
+
opened: currentAction?.opened ? [currentAction.source] : [],
|
|
329
313
|
googleWorkspaceDelegation: sources.google ? googleWorkspaceDelegationSetup(session.googleWorkspaceDelegation) : undefined,
|
|
330
|
-
|
|
331
|
-
granolaApiKeyPage,
|
|
314
|
+
currentAction,
|
|
332
315
|
statePath,
|
|
333
316
|
messagesChatsCommand: sources.messages ? `${agentCommand()} messages-chats` : undefined,
|
|
334
317
|
nextCommand: `${agentCommand()} agent --continue --messages-handle "<phone_or_apple_id>" --messages-chat-ids "<comma_separated_chat_ids>" --granola-api-key "<granola_key>"`,
|
|
335
|
-
needsUserAction: agentNeedsUserAction(sources,
|
|
318
|
+
needsUserAction: agentNeedsUserAction(sources, currentAction),
|
|
336
319
|
}, null, 2));
|
|
337
320
|
return;
|
|
338
321
|
}
|
|
@@ -343,35 +326,13 @@ async function runAgentOnboarding() {
|
|
|
343
326
|
if (session.account.organizationMatch?.type && session.account.organizationMatch.type !== "created") {
|
|
344
327
|
console.log(`Matched existing organization by ${session.account.organizationMatch.type}.`);
|
|
345
328
|
}
|
|
346
|
-
if (opened.length) {
|
|
347
|
-
console.log(`Opened browser authorization: ${opened.join(", ")}`);
|
|
348
|
-
}
|
|
349
|
-
if (sources.google) {
|
|
350
|
-
if (googleAdminConsole?.opened) {
|
|
351
|
-
console.log(`Opened Google Admin Console: ${googleAdminConsole.url}`);
|
|
352
|
-
}
|
|
353
|
-
console.log("\nGoogle Workspace domain-wide delegation setup:");
|
|
354
|
-
printGoogleWorkspaceDelegationSetup(session.googleWorkspaceDelegation);
|
|
355
|
-
}
|
|
356
|
-
if (noOpen) {
|
|
357
|
-
if (sources.google) console.log(`Google Admin Console domain-wide delegation URL: ${GOOGLE_WORKSPACE_DELEGATION_ADMIN_URL}`);
|
|
358
|
-
for (const [provider, url] of Object.entries(session.authUrls ?? {})) {
|
|
359
|
-
if (provider === "google") continue;
|
|
360
|
-
console.log(`${provider} auth URL: ${url}`);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
329
|
console.log(`State saved: ${statePath}`);
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (sources.granola) console.log(`${step++}. Ask the user for their Granola API key from the Granola Mac app.`);
|
|
371
|
-
if (sources.messages) {
|
|
372
|
-
console.log(`${step++}. Run ${agentCommand()} messages-chats, have the user select chats in the browser page, and keep the printed chat IDs.`);
|
|
373
|
-
}
|
|
374
|
-
console.log(`${step++}. Run:`);
|
|
330
|
+
|
|
331
|
+
printAgentCurrentAction(currentAction, {
|
|
332
|
+
googleWorkspaceDelegation: session.googleWorkspaceDelegation,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
console.log("\nAfter that modality is complete, run:");
|
|
375
336
|
console.log(` ${agentCommand()} agent --continue --messages-handle "<phone_or_apple_id>" --messages-chat-ids "<comma_separated_chat_ids>" --granola-api-key "<granola_key>"`);
|
|
376
337
|
console.log(" Omit either optional flag if that source is not being connected.");
|
|
377
338
|
}
|
|
@@ -494,11 +455,20 @@ async function continueAgentOnboarding() {
|
|
|
494
455
|
}
|
|
495
456
|
|
|
496
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;
|
|
497
466
|
if (args.json) {
|
|
498
467
|
console.log(JSON.stringify({
|
|
499
468
|
status: errors ? "waiting" : "completed",
|
|
500
469
|
connected: Object.keys(finalized.connected ?? {}),
|
|
501
470
|
errors: errors ? safeErrorRecord(errors) : undefined,
|
|
471
|
+
currentAction,
|
|
502
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,
|
|
503
473
|
}, null, 2));
|
|
504
474
|
return;
|
|
@@ -509,7 +479,12 @@ async function continueAgentOnboarding() {
|
|
|
509
479
|
for (const [source, message] of Object.entries(errors)) {
|
|
510
480
|
console.log(`- ${source}: ${safeError(message)}`);
|
|
511
481
|
}
|
|
512
|
-
|
|
482
|
+
|
|
483
|
+
printAgentCurrentAction(currentAction, {
|
|
484
|
+
googleWorkspaceDelegation: state.googleWorkspaceDelegation,
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
console.log("\nAfter that modality is complete, rerun:");
|
|
513
488
|
console.log(` ${agentCommand()} agent --continue --messages-handle "<phone_or_apple_id>" --messages-chat-ids "<comma_separated_chat_ids>" --granola-api-key "<granola_key>"`);
|
|
514
489
|
console.log(" Omit either optional flag if that source is not being connected.");
|
|
515
490
|
return;
|
|
@@ -750,9 +725,9 @@ function printAgentContract() {
|
|
|
750
725
|
"Selected local Messages chats from the browser selector, if they want local Messages connected",
|
|
751
726
|
],
|
|
752
727
|
afterStartCommand: [
|
|
753
|
-
"
|
|
754
|
-
"
|
|
755
|
-
"
|
|
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.",
|
|
756
731
|
],
|
|
757
732
|
doNotDo: [
|
|
758
733
|
"Do not run wiki generation.",
|
|
@@ -830,9 +805,9 @@ Add skip flags for sources the user did not select:
|
|
|
830
805
|
- --no-granola
|
|
831
806
|
- --no-messages
|
|
832
807
|
|
|
833
|
-
That command creates/reuses the customer user and org, opens
|
|
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.
|
|
834
809
|
|
|
835
|
-
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:
|
|
836
811
|
|
|
837
812
|
App name: ${payload.googleWorkspaceDelegation.appName}
|
|
838
813
|
Service account email: ${payload.googleWorkspaceDelegation.serviceAccountEmail}
|
|
@@ -846,17 +821,17 @@ The setup command copies those scopes to the clipboard as one comma-separated st
|
|
|
846
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.
|
|
847
822
|
Shepherd must still enforce selected users and groups internally before polling or impersonating any employee email.
|
|
848
823
|
|
|
849
|
-
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.
|
|
850
825
|
|
|
851
|
-
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:
|
|
852
827
|
Settings -> Connectors -> API keys
|
|
853
828
|
Then have the user create/copy the API key.
|
|
854
829
|
|
|
855
|
-
If Granola did not come forward, run:
|
|
830
|
+
If Granola is the current modality and did not come forward, run:
|
|
856
831
|
${payload.granolaApiKeyCommand}
|
|
857
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.
|
|
858
833
|
|
|
859
|
-
After
|
|
834
|
+
After the current modality is complete, run:
|
|
860
835
|
${payload.continueCommand} --messages-handle "<phone_or_apple_id>" --messages-chat-ids "<comma_separated_chat_ids>" --granola-api-key "<granola_key>"
|
|
861
836
|
|
|
862
837
|
Omit either optional flag if that source is not being connected.
|
|
@@ -973,13 +948,108 @@ function authenticatedName(authenticated) {
|
|
|
973
948
|
return authenticated?.workosUser?.name ?? authenticated?.account?.name ?? null;
|
|
974
949
|
}
|
|
975
950
|
|
|
976
|
-
function
|
|
977
|
-
const
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
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 [];
|
|
983
1053
|
}
|
|
984
1054
|
|
|
985
1055
|
function agentCommand() {
|