shepherd-onboard 0.1.18 → 0.1.20

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.
@@ -19,6 +19,7 @@ const DEFAULT_MESSAGE_CHAT_SEARCH_LIMIT = 200;
19
19
  const INITIAL_MESSAGE_CHAT_ROWS = 20;
20
20
  const SHEPHERD_LOGO_PATH = join(PACKAGE_DIR, "assets", "shepherd_G_vector_136033.png");
21
21
  const GRANOLA_API_KEYS_PATH = "/settings/integrations/api-keys";
22
+ const GOOGLE_WORKSPACE_DELEGATION_ADMIN_URL = "https://admin.google.com/ac/owl/domainwidedelegation";
22
23
  const GOOGLE_WORKSPACE_DELEGATION_APP_NAME = "Shepherd";
23
24
  const GOOGLE_WORKSPACE_DELEGATION_SERVICE_ACCOUNT_EMAIL =
24
25
  "gigabrain-delegation@shepherd-gigabrain.iam.gserviceaccount.com";
@@ -148,6 +149,7 @@ async function runOnboarding() {
148
149
  if (sources.google) {
149
150
  console.log("\nGoogle Workspace domain-wide delegation");
150
151
  printGoogleWorkspaceDelegationSetup(session.googleWorkspaceDelegation);
152
+ await openGoogleWorkspaceDelegationAdmin({ noOpen });
151
153
  await waitForEnter("After the Google Workspace super admin authorizes Shepherd in Admin Console, press Enter.");
152
154
  }
153
155
 
@@ -297,6 +299,16 @@ async function runAgentOnboarding() {
297
299
  });
298
300
 
299
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
+ }
300
312
  for (const [provider, url] of Object.entries(session.authUrls ?? {})) {
301
313
  if (typeof url !== "string") continue;
302
314
  if (provider === "google") continue;
@@ -315,6 +327,7 @@ async function runAgentOnboarding() {
315
327
  account: publicAgentAccount(session.account),
316
328
  opened,
317
329
  googleWorkspaceDelegation: sources.google ? googleWorkspaceDelegationSetup(session.googleWorkspaceDelegation) : undefined,
330
+ googleAdminConsole,
318
331
  granolaApiKeyPage,
319
332
  statePath,
320
333
  messagesChatsCommand: sources.messages ? `${agentCommand()} messages-chats` : undefined,
@@ -334,10 +347,14 @@ async function runAgentOnboarding() {
334
347
  console.log(`Opened browser authorization: ${opened.join(", ")}`);
335
348
  }
336
349
  if (sources.google) {
350
+ if (googleAdminConsole?.opened) {
351
+ console.log(`Opened Google Admin Console: ${googleAdminConsole.url}`);
352
+ }
337
353
  console.log("\nGoogle Workspace domain-wide delegation setup:");
338
354
  printGoogleWorkspaceDelegationSetup(session.googleWorkspaceDelegation);
339
355
  }
340
356
  if (noOpen) {
357
+ if (sources.google) console.log(`Google Admin Console domain-wide delegation URL: ${GOOGLE_WORKSPACE_DELEGATION_ADMIN_URL}`);
341
358
  for (const [provider, url] of Object.entries(session.authUrls ?? {})) {
342
359
  if (provider === "google") continue;
343
360
  console.log(`${provider} auth URL: ${url}`);
@@ -813,9 +830,9 @@ Add skip flags for sources the user did not select:
813
830
  - --no-granola
814
831
  - --no-messages
815
832
 
816
- 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.
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.
817
834
 
818
- If Google Workspace is selected, show this Admin Console setup to the user and have their Google Workspace super admin authorize it:
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:
819
836
 
820
837
  App name: ${payload.googleWorkspaceDelegation.appName}
821
838
  Service account email: ${payload.googleWorkspaceDelegation.serviceAccountEmail}
@@ -1072,6 +1089,15 @@ async function openOrPrint(url, opts) {
1072
1089
  });
1073
1090
  }
1074
1091
 
1092
+ async function openGoogleWorkspaceDelegationAdmin(opts = {}) {
1093
+ if (opts.noOpen) {
1094
+ console.log(`Google Admin Console domain-wide delegation URL: ${GOOGLE_WORKSPACE_DELEGATION_ADMIN_URL}`);
1095
+ return { opened: false, url: GOOGLE_WORKSPACE_DELEGATION_ADMIN_URL };
1096
+ }
1097
+ await openOrPrint(GOOGLE_WORKSPACE_DELEGATION_ADMIN_URL, { noOpen: false });
1098
+ return { opened: true, url: GOOGLE_WORKSPACE_DELEGATION_ADMIN_URL };
1099
+ }
1100
+
1075
1101
  async function openGranolaApiKeys(opts = {}) {
1076
1102
  const deepLink = granolaApiKeysDeepLink();
1077
1103
  if (opts.noOpen) {
@@ -1259,12 +1285,20 @@ async function selectChatsInBrowser(chats, opts = {}) {
1259
1285
  const token = Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
1260
1286
  let settled = false;
1261
1287
  let server;
1288
+ const sockets = new Set();
1262
1289
 
1263
1290
  return new Promise((resolve, reject) => {
1291
+ const closeSelectorServer = () => {
1292
+ server?.close();
1293
+ server?.closeIdleConnections?.();
1294
+ for (const socket of sockets) socket.destroy();
1295
+ sockets.clear();
1296
+ };
1297
+
1264
1298
  const timeout = setTimeout(() => {
1265
1299
  if (settled) return;
1266
1300
  settled = true;
1267
- server?.close();
1301
+ closeSelectorServer();
1268
1302
  reject(new Error("Messages chat selection timed out."));
1269
1303
  }, 20 * 60 * 1000);
1270
1304
 
@@ -1292,11 +1326,11 @@ async function selectChatsInBrowser(chats, opts = {}) {
1292
1326
  return;
1293
1327
  }
1294
1328
 
1329
+ if (!settled) res.once("finish", closeSelectorServer);
1295
1330
  sendHtml(res, renderMessagesDonePage(`${selected.length} chat${selected.length === 1 ? "" : "s"} selected.`));
1296
1331
  if (!settled) {
1297
1332
  settled = true;
1298
1333
  clearTimeout(timeout);
1299
- setTimeout(() => server.close(), 100);
1300
1334
  resolve(selected);
1301
1335
  }
1302
1336
  return;
@@ -1307,16 +1341,22 @@ async function selectChatsInBrowser(chats, opts = {}) {
1307
1341
  if (!settled) {
1308
1342
  settled = true;
1309
1343
  clearTimeout(timeout);
1310
- server?.close();
1344
+ closeSelectorServer();
1311
1345
  reject(err);
1312
1346
  }
1313
1347
  }
1314
1348
  });
1315
1349
 
1350
+ server.on("connection", (socket) => {
1351
+ sockets.add(socket);
1352
+ socket.once("close", () => sockets.delete(socket));
1353
+ });
1354
+
1316
1355
  server.on("error", (err) => {
1317
1356
  if (settled) return;
1318
1357
  settled = true;
1319
1358
  clearTimeout(timeout);
1359
+ closeSelectorServer();
1320
1360
  reject(err);
1321
1361
  });
1322
1362
 
@@ -1326,7 +1366,7 @@ async function selectChatsInBrowser(chats, opts = {}) {
1326
1366
  if (!port) {
1327
1367
  settled = true;
1328
1368
  clearTimeout(timeout);
1329
- server.close();
1369
+ closeSelectorServer();
1330
1370
  reject(new Error("Could not start local Messages selector."));
1331
1371
  return;
1332
1372
  }
@@ -1642,6 +1682,7 @@ function renderMessagesSelectorPage(chats, token, error = "") {
1642
1682
  const search = document.getElementById("search");
1643
1683
  const empty = document.getElementById("empty");
1644
1684
  const selected = document.getElementById("selection-count");
1685
+ const form = document.querySelector("form");
1645
1686
  const checks = Array.from(document.querySelectorAll('input[name="chatId"]'));
1646
1687
 
1647
1688
  function updateRows() {
@@ -1663,6 +1704,11 @@ function renderMessagesSelectorPage(chats, token, error = "") {
1663
1704
  }
1664
1705
 
1665
1706
  search.addEventListener("input", updateRows);
1707
+ document.addEventListener("keydown", (event) => {
1708
+ if (event.key !== "Enter") return;
1709
+ event.preventDefault();
1710
+ form.requestSubmit();
1711
+ });
1666
1712
  for (const check of checks) check.addEventListener("change", updateSelected);
1667
1713
  updateRows();
1668
1714
  updateSelected();
@@ -1672,6 +1718,7 @@ function renderMessagesSelectorPage(chats, token, error = "") {
1672
1718
  }
1673
1719
 
1674
1720
  function renderMessagesDonePage(message, isError = false) {
1721
+ const closeScript = isError ? "" : "<script>setTimeout(() => window.close(), 150);</script>";
1675
1722
  return `<!doctype html>
1676
1723
  <html lang="en">
1677
1724
  <head>
@@ -1692,8 +1739,9 @@ function renderMessagesDonePage(message, isError = false) {
1692
1739
  <main>
1693
1740
  <div class="mark">${isError ? "!" : "OK"}</div>
1694
1741
  <h1>${html(message)}</h1>
1695
- <p>${isError ? "Return to the terminal and retry." : "You can close this tab and return to the terminal."}</p>
1742
+ <p>${isError ? "Return to the terminal and retry." : "Returning to the terminal."}</p>
1696
1743
  </main>
1744
+ ${closeScript}
1697
1745
  </body>
1698
1746
  </html>`;
1699
1747
  }
@@ -1760,6 +1808,7 @@ function sendHtml(res, body, status = 200) {
1760
1808
  res.writeHead(status, {
1761
1809
  "Content-Type": "text/html; charset=utf-8",
1762
1810
  "Cache-Control": "no-store",
1811
+ "Connection": "close",
1763
1812
  });
1764
1813
  res.end(body);
1765
1814
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shepherd-onboard",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "Customer-facing Shepherd raw sync onboarding CLI",
5
5
  "type": "module",
6
6
  "bin": {