shepherd-onboard 0.1.17 → 0.1.19

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.
@@ -824,6 +824,8 @@ Domain-wide delegation OAuth Client ID: ${payload.googleWorkspaceDelegation.clie
824
824
  Scopes:
825
825
  ${payload.googleWorkspaceDelegation.scopes.join("\n")}
826
826
 
827
+ The setup command copies those scopes to the clipboard as one comma-separated string on macOS. Tell the user they can paste directly into the OAuth scopes field. If clipboard copy is unavailable, use the scopes printed above.
828
+
827
829
  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.
828
830
  Shepherd must still enforce selected users and groups internally before polling or impersonating any employee email.
829
831
 
@@ -918,15 +920,34 @@ function googleWorkspaceDelegationSetup(setup) {
918
920
 
919
921
  function printGoogleWorkspaceDelegationSetup(setup) {
920
922
  const resolved = googleWorkspaceDelegationSetup(setup);
923
+ const copiedScopes = copyTextToClipboard(resolved.scopes.join(","));
921
924
  console.log(`App name: ${resolved.appName}`);
922
925
  console.log(`Service account email: ${resolved.serviceAccountEmail}`);
923
926
  console.log(`Domain-wide delegation OAuth Client ID: ${resolved.clientId}`);
924
927
  console.log("Customer action: in Google Admin Console, add the Client ID above and paste these scopes.");
925
928
  console.log("Customers do not create a service account or upload service account JSON; Shepherd stores its private service account JSON server-side.");
929
+ if (copiedScopes) {
930
+ console.log("Copied comma-separated scopes to the clipboard.");
931
+ } else if (platform() === "darwin") {
932
+ console.log("Could not copy scopes to the clipboard; copy the scopes below instead.");
933
+ }
926
934
  console.log("\nScopes:");
927
935
  for (const scope of resolved.scopes) console.log(scope);
928
936
  }
929
937
 
938
+ function copyTextToClipboard(text) {
939
+ if (platform() !== "darwin") return false;
940
+ try {
941
+ execFileSync("pbcopy", [], {
942
+ input: text,
943
+ stdio: ["pipe", "ignore", "ignore"],
944
+ });
945
+ return true;
946
+ } catch {
947
+ return false;
948
+ }
949
+ }
950
+
930
951
  function authenticatedEmail(authenticated) {
931
952
  return authenticated?.workosUser?.email ?? authenticated?.account?.email ?? null;
932
953
  }
@@ -1238,12 +1259,20 @@ async function selectChatsInBrowser(chats, opts = {}) {
1238
1259
  const token = Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
1239
1260
  let settled = false;
1240
1261
  let server;
1262
+ const sockets = new Set();
1241
1263
 
1242
1264
  return new Promise((resolve, reject) => {
1265
+ const closeSelectorServer = () => {
1266
+ server?.close();
1267
+ server?.closeIdleConnections?.();
1268
+ for (const socket of sockets) socket.destroy();
1269
+ sockets.clear();
1270
+ };
1271
+
1243
1272
  const timeout = setTimeout(() => {
1244
1273
  if (settled) return;
1245
1274
  settled = true;
1246
- server?.close();
1275
+ closeSelectorServer();
1247
1276
  reject(new Error("Messages chat selection timed out."));
1248
1277
  }, 20 * 60 * 1000);
1249
1278
 
@@ -1271,11 +1300,11 @@ async function selectChatsInBrowser(chats, opts = {}) {
1271
1300
  return;
1272
1301
  }
1273
1302
 
1303
+ if (!settled) res.once("finish", closeSelectorServer);
1274
1304
  sendHtml(res, renderMessagesDonePage(`${selected.length} chat${selected.length === 1 ? "" : "s"} selected.`));
1275
1305
  if (!settled) {
1276
1306
  settled = true;
1277
1307
  clearTimeout(timeout);
1278
- setTimeout(() => server.close(), 100);
1279
1308
  resolve(selected);
1280
1309
  }
1281
1310
  return;
@@ -1286,16 +1315,22 @@ async function selectChatsInBrowser(chats, opts = {}) {
1286
1315
  if (!settled) {
1287
1316
  settled = true;
1288
1317
  clearTimeout(timeout);
1289
- server?.close();
1318
+ closeSelectorServer();
1290
1319
  reject(err);
1291
1320
  }
1292
1321
  }
1293
1322
  });
1294
1323
 
1324
+ server.on("connection", (socket) => {
1325
+ sockets.add(socket);
1326
+ socket.once("close", () => sockets.delete(socket));
1327
+ });
1328
+
1295
1329
  server.on("error", (err) => {
1296
1330
  if (settled) return;
1297
1331
  settled = true;
1298
1332
  clearTimeout(timeout);
1333
+ closeSelectorServer();
1299
1334
  reject(err);
1300
1335
  });
1301
1336
 
@@ -1305,7 +1340,7 @@ async function selectChatsInBrowser(chats, opts = {}) {
1305
1340
  if (!port) {
1306
1341
  settled = true;
1307
1342
  clearTimeout(timeout);
1308
- server.close();
1343
+ closeSelectorServer();
1309
1344
  reject(new Error("Could not start local Messages selector."));
1310
1345
  return;
1311
1346
  }
@@ -1621,6 +1656,7 @@ function renderMessagesSelectorPage(chats, token, error = "") {
1621
1656
  const search = document.getElementById("search");
1622
1657
  const empty = document.getElementById("empty");
1623
1658
  const selected = document.getElementById("selection-count");
1659
+ const form = document.querySelector("form");
1624
1660
  const checks = Array.from(document.querySelectorAll('input[name="chatId"]'));
1625
1661
 
1626
1662
  function updateRows() {
@@ -1642,6 +1678,11 @@ function renderMessagesSelectorPage(chats, token, error = "") {
1642
1678
  }
1643
1679
 
1644
1680
  search.addEventListener("input", updateRows);
1681
+ document.addEventListener("keydown", (event) => {
1682
+ if (event.key !== "Enter") return;
1683
+ event.preventDefault();
1684
+ form.requestSubmit();
1685
+ });
1645
1686
  for (const check of checks) check.addEventListener("change", updateSelected);
1646
1687
  updateRows();
1647
1688
  updateSelected();
@@ -1651,6 +1692,7 @@ function renderMessagesSelectorPage(chats, token, error = "") {
1651
1692
  }
1652
1693
 
1653
1694
  function renderMessagesDonePage(message, isError = false) {
1695
+ const closeScript = isError ? "" : "<script>setTimeout(() => window.close(), 150);</script>";
1654
1696
  return `<!doctype html>
1655
1697
  <html lang="en">
1656
1698
  <head>
@@ -1671,8 +1713,9 @@ function renderMessagesDonePage(message, isError = false) {
1671
1713
  <main>
1672
1714
  <div class="mark">${isError ? "!" : "OK"}</div>
1673
1715
  <h1>${html(message)}</h1>
1674
- <p>${isError ? "Return to the terminal and retry." : "You can close this tab and return to the terminal."}</p>
1716
+ <p>${isError ? "Return to the terminal and retry." : "Returning to the terminal."}</p>
1675
1717
  </main>
1718
+ ${closeScript}
1676
1719
  </body>
1677
1720
  </html>`;
1678
1721
  }
@@ -1739,6 +1782,7 @@ function sendHtml(res, body, status = 200) {
1739
1782
  res.writeHead(status, {
1740
1783
  "Content-Type": "text/html; charset=utf-8",
1741
1784
  "Cache-Control": "no-store",
1785
+ "Connection": "close",
1742
1786
  });
1743
1787
  res.end(body);
1744
1788
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shepherd-onboard",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Customer-facing Shepherd raw sync onboarding CLI",
5
5
  "type": "module",
6
6
  "bin": {