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.
@@ -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 opened = [];
302
- let googleAdminConsole = null;
303
- if (sources.google) {
304
- googleAdminConsole = { url: GOOGLE_WORKSPACE_DELEGATION_ADMIN_URL };
305
- if (!noOpen) {
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
- googleAdminConsole,
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, opened),
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
- console.log("\nCoding agent next steps:");
365
- let step = 1;
366
- if (sources.google) {
367
- 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.`);
368
- }
369
- if (sources.slack) console.log(`${step++}. Ask the user to finish the opened Slack browser authorization.`);
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
- console.log("\nAfter the user completes missing auth/details, rerun:");
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
- "For Google Workspace, have a super admin authorize the Shepherd Client ID and scopes in Google Admin Console.",
754
- "Complete the opened Slack authorization.",
755
- "Create/copy the Granola API key from the opened Granola Mac app API key screen, if they want Granola connected.",
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 the Google Admin Console domain-wide delegation page if Google Workspace is selected, prints Google Workspace setup values, opens Slack browser auth if selected, and saves local state.
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 selected, 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:
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 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.
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 selected, it also opens the Granola desktop app. If your local app automation can navigate it, go to:
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 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:
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 agentNeedsUserAction(sources, opened) {
977
- const actions = [];
978
- if (sources.google) actions.push("Have the customer's Google Workspace super admin authorize Shepherd's domain-wide delegation Client ID and scopes in Google Admin Console.");
979
- if (sources.slack && opened.includes("slack")) actions.push("Complete Slack browser authorization.");
980
- if (sources.granola) actions.push("Create/copy a Granola API key from the Granola Mac app.");
981
- if (sources.messages) actions.push("Run messages-chats, have the user select local Messages contacts/groups in the browser, then pass the printed chat IDs with the Messages handle.");
982
- return actions;
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() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shepherd-onboard",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "description": "Customer-facing Shepherd raw sync onboarding CLI",
5
5
  "type": "module",
6
6
  "bin": {