shepherd-onboard 0.1.15 → 0.1.17

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.
@@ -1356,16 +1356,15 @@ function renderMessagesSelectorPage(chats, token, error = "") {
1356
1356
  <title>Select Messages Chats</title>
1357
1357
  <style>
1358
1358
  :root {
1359
- --bg: #fbfbfa;
1360
- --panel: #ffffff;
1359
+ --bg: #ffffff;
1361
1360
  --fg: #141614;
1362
- --muted: #767d78;
1363
- --faint: #9aa09b;
1364
- --line: #dedfdd;
1361
+ --muted: #615d59;
1362
+ --faint: #a39e98;
1363
+ --line: #e6e6e6;
1365
1364
  --green: #1f5c2e;
1366
1365
  --green-hover: #246836;
1367
1366
  --radius: 8px;
1368
- --grid: 20px minmax(0, 1fr) 84px 112px;
1367
+ --grid: 20px minmax(0, 1fr) 76px 108px;
1369
1368
  }
1370
1369
  * { box-sizing: border-box; }
1371
1370
  body {
@@ -1378,32 +1377,30 @@ function renderMessagesSelectorPage(chats, token, error = "") {
1378
1377
  -webkit-font-smoothing: antialiased;
1379
1378
  }
1380
1379
  main {
1381
- width: min(720px, calc(100vw - 32px));
1380
+ width: min(640px, calc(100vw - 32px));
1382
1381
  margin: 0 auto;
1383
- padding: 30px 0 28px;
1382
+ padding: 56px 0 28px;
1384
1383
  }
1385
1384
  .header {
1386
- display: flex;
1387
- justify-content: space-between;
1388
- align-items: center;
1389
- gap: 16px;
1390
- padding: 0 0 18px;
1385
+ display: grid;
1386
+ justify-items: center;
1387
+ text-align: center;
1388
+ padding: 0 0 28px;
1391
1389
  }
1392
1390
  .brand {
1393
- display: flex;
1394
- align-items: center;
1395
- gap: 10px;
1391
+ display: grid;
1392
+ justify-items: center;
1393
+ gap: 16px;
1396
1394
  min-width: 0;
1397
1395
  }
1398
1396
  .logo {
1399
- width: 28px;
1400
- height: 28px;
1397
+ width: 30px;
1398
+ height: 30px;
1401
1399
  object-fit: contain;
1402
- flex: none;
1403
1400
  }
1404
1401
  .logo-fallback {
1405
- width: 28px;
1406
- height: 28px;
1402
+ width: 30px;
1403
+ height: 30px;
1407
1404
  border-radius: 6px;
1408
1405
  display: grid;
1409
1406
  place-items: center;
@@ -1415,36 +1412,32 @@ function renderMessagesSelectorPage(chats, token, error = "") {
1415
1412
  }
1416
1413
  h1 {
1417
1414
  margin: 0;
1418
- font-size: 19px;
1415
+ font-size: 30px;
1419
1416
  line-height: 1.1;
1420
1417
  font-weight: 700;
1421
1418
  letter-spacing: 0;
1422
1419
  }
1423
1420
  .subtitle {
1424
- margin: 4px 0 0;
1425
- font-size: 13px;
1421
+ margin: 8px 0 0;
1422
+ font-size: 14px;
1426
1423
  line-height: 1.4;
1427
1424
  color: var(--muted);
1428
1425
  }
1429
1426
  .panel {
1430
- background: var(--panel);
1431
- border: 1px solid var(--line);
1432
- border-radius: var(--radius);
1433
- overflow: hidden;
1427
+ background: transparent;
1434
1428
  }
1435
1429
  .panel-head {
1436
- padding: 12px;
1437
- border-bottom: 1px solid var(--line);
1430
+ padding: 0 0 18px;
1438
1431
  }
1439
1432
  .search {
1440
1433
  width: 100%;
1441
- border: 1px solid #d7d9d6;
1442
- border-radius: 6px;
1434
+ border: 1px solid var(--line);
1435
+ border-radius: 8px;
1443
1436
  background: #ffffff;
1444
1437
  color: var(--fg);
1445
- padding: 9px 10px;
1438
+ padding: 12px 13px;
1446
1439
  font: inherit;
1447
- font-size: 13px;
1440
+ font-size: 15px;
1448
1441
  outline: none;
1449
1442
  }
1450
1443
  .search:focus {
@@ -1454,7 +1447,7 @@ function renderMessagesSelectorPage(chats, token, error = "") {
1454
1447
  }
1455
1448
  .search::placeholder { color: var(--faint); }
1456
1449
  .error {
1457
- margin: 10px 12px 0;
1450
+ margin: 0 0 12px;
1458
1451
  color: #9B1C1C;
1459
1452
  font-size: 13px;
1460
1453
  }
@@ -1463,12 +1456,14 @@ function renderMessagesSelectorPage(chats, token, error = "") {
1463
1456
  grid-template-columns: var(--grid);
1464
1457
  gap: 12px;
1465
1458
  align-items: center;
1466
- padding: 9px 12px;
1459
+ padding: 9px 0;
1460
+ border-top: 1px solid var(--line);
1467
1461
  border-bottom: 1px solid var(--line);
1468
1462
  color: var(--faint);
1469
1463
  font-size: 11px;
1470
1464
  font-weight: 600;
1471
1465
  text-transform: uppercase;
1466
+ letter-spacing: 0.02em;
1472
1467
  }
1473
1468
  .list-head .right { text-align: left; }
1474
1469
  .chat-list { display: block; }
@@ -1477,13 +1472,12 @@ function renderMessagesSelectorPage(chats, token, error = "") {
1477
1472
  grid-template-columns: var(--grid);
1478
1473
  gap: 12px;
1479
1474
  align-items: center;
1480
- padding: 11px 12px;
1475
+ padding: 11px 0;
1481
1476
  border-bottom: 1px solid var(--line);
1482
1477
  cursor: pointer;
1483
1478
  transition: background 120ms ease;
1484
1479
  }
1485
- .chat-row:last-child { border-bottom: 0; }
1486
- .chat-row:hover { background: #f6f7f5; }
1480
+ .chat-row:hover { background: #f6f5f4; }
1487
1481
  input[type="checkbox"] {
1488
1482
  position: absolute;
1489
1483
  opacity: 0;
@@ -1521,7 +1515,7 @@ function renderMessagesSelectorPage(chats, token, error = "") {
1521
1515
  text-overflow: ellipsis;
1522
1516
  white-space: nowrap;
1523
1517
  font-size: 15px;
1524
- font-weight: 550;
1518
+ font-weight: 500;
1525
1519
  }
1526
1520
  .chat-people {
1527
1521
  color: var(--muted);
@@ -1535,9 +1529,9 @@ function renderMessagesSelectorPage(chats, token, error = "") {
1535
1529
  justify-self: start;
1536
1530
  color: var(--muted);
1537
1531
  font-size: 12.5px;
1538
- font-weight: 500;
1532
+ font-weight: 400;
1539
1533
  }
1540
- .chat-kind--group { color: var(--green); }
1534
+ .chat-kind--group { color: var(--fg); }
1541
1535
  .chat-meta {
1542
1536
  color: var(--muted);
1543
1537
  font-size: 12.5px;
@@ -1558,8 +1552,8 @@ function renderMessagesSelectorPage(chats, token, error = "") {
1558
1552
  gap: 12px;
1559
1553
  position: sticky;
1560
1554
  bottom: 0;
1561
- margin-top: 12px;
1562
- padding: 10px 0 0;
1555
+ margin-top: 16px;
1556
+ padding: 12px 0 0;
1563
1557
  background: var(--bg);
1564
1558
  }
1565
1559
  .selection-count {
@@ -1570,7 +1564,7 @@ function renderMessagesSelectorPage(chats, token, error = "") {
1570
1564
  button {
1571
1565
  appearance: none;
1572
1566
  border: 0;
1573
- border-radius: 6px;
1567
+ border-radius: 8px;
1574
1568
  background: var(--green);
1575
1569
  color: #FFFFFF;
1576
1570
  padding: 9px 13px;
@@ -1595,7 +1589,7 @@ function renderMessagesSelectorPage(chats, token, error = "") {
1595
1589
  ${logo ? `<img class="logo" src="${htmlAttr(logo)}" alt="">` : `<span class="logo-fallback" aria-hidden="true">S</span>`}
1596
1590
  <div>
1597
1591
  <h1>Select chats</h1>
1598
- <p class="subtitle">Choose the local Messages chats Shepherd should sync.</p>
1592
+ <p class="subtitle">Choose which local Messages chats Shepherd should sync.</p>
1599
1593
  </div>
1600
1594
  </div>
1601
1595
  </header>
@@ -1607,7 +1601,6 @@ function renderMessagesSelectorPage(chats, token, error = "") {
1607
1601
  </div>
1608
1602
  ${error ? `<p class="error">${html(error)}</p>` : ""}
1609
1603
  <div class="list-head">
1610
- <span></span>
1611
1604
  <span></span>
1612
1605
  <span>Name</span>
1613
1606
  <span>Type</span>
@@ -1692,7 +1685,9 @@ function renderChatPeople(chat) {
1692
1685
 
1693
1686
  function chatPeopleLine(chat) {
1694
1687
  if (chat.kind !== "group") return "";
1695
- const names = chat.participants?.map((participant) => participant.name ?? participant.handle).filter(Boolean) ?? [];
1688
+ const names = chat.participants
1689
+ ?.map((participant) => participant.name)
1690
+ .filter((name) => name && !looksLikeHandleList(name)) ?? [];
1696
1691
  const line = names.slice(0, 6).join(", ");
1697
1692
  if (!line || normalizeDisplayText(line) === normalizeDisplayText(chat.label)) return "";
1698
1693
  return line;
@@ -2029,8 +2024,8 @@ function createMessageSerializer(kit, contactLookup = emptyContactLookup()) {
2029
2024
  };
2030
2025
  }
2031
2026
 
2032
- function buildContactLookup() {
2033
- const contacts = loadContacts();
2027
+ function buildContactLookup(opts = {}) {
2028
+ const contacts = opts.loadAll === false ? [] : loadContacts();
2034
2029
  const myCard = loadMyCard();
2035
2030
  const handleToName = new Map();
2036
2031
  const selfHandles = new Set();
@@ -2077,6 +2072,9 @@ function emptyContactLookup() {
2077
2072
 
2078
2073
  function loadContacts() {
2079
2074
  if (platform() !== "darwin") return [];
2075
+ const sqliteContacts = loadContactsFromAddressBookDb();
2076
+ if (sqliteContacts.length > 0) return sqliteContacts;
2077
+
2080
2078
  const script = `
2081
2079
  set output to ""
2082
2080
  tell application "Contacts"
@@ -2100,14 +2098,71 @@ return output`;
2100
2098
  try {
2101
2099
  const raw = execFileSync("osascript", ["-e", script], {
2102
2100
  encoding: "utf8",
2103
- timeout: 30_000,
2101
+ timeout: 120_000,
2104
2102
  });
2105
2103
  return parseContacts(raw);
2106
- } catch {
2104
+ } catch (err) {
2105
+ if (args.debug === true) console.error("Could not load Contacts:", safeError(err));
2107
2106
  return [];
2108
2107
  }
2109
2108
  }
2110
2109
 
2110
+ function loadContactsFromAddressBookDb() {
2111
+ const contacts = new Map();
2112
+ for (const dbPath of addressBookDatabasePaths()) {
2113
+ const query = `
2114
+ select r.Z_PK,
2115
+ coalesce(nullif(r.ZNAME, ''), nullif(trim(coalesce(r.ZFIRSTNAME, '') || ' ' || coalesce(r.ZLASTNAME, '')), ''), nullif(r.ZORGANIZATION, ''), '') as display_name,
2116
+ coalesce(p.ZFULLNUMBER, '') as phone,
2117
+ '' as email
2118
+ from ZABCDRECORD r
2119
+ join ZABCDPHONENUMBER p on p.ZOWNER = r.Z_PK
2120
+ where p.ZFULLNUMBER is not null and p.ZFULLNUMBER != ''
2121
+ union all
2122
+ select r.Z_PK,
2123
+ coalesce(nullif(r.ZNAME, ''), nullif(trim(coalesce(r.ZFIRSTNAME, '') || ' ' || coalesce(r.ZLASTNAME, '')), ''), nullif(r.ZORGANIZATION, ''), '') as display_name,
2124
+ '' as phone,
2125
+ coalesce(e.ZADDRESS, '') as email
2126
+ from ZABCDRECORD r
2127
+ join ZABCDEMAILADDRESS e on e.ZOWNER = r.Z_PK
2128
+ where e.ZADDRESS is not null and e.ZADDRESS != '';`;
2129
+
2130
+ try {
2131
+ const raw = execFileSync("sqlite3", ["-separator", "\t", dbPath, query], {
2132
+ encoding: "utf8",
2133
+ timeout: 10_000,
2134
+ });
2135
+ for (const line of raw.split("\n").filter(Boolean)) {
2136
+ const [id, rawName, phone, email] = line.split("\t");
2137
+ const name = rawName?.trim();
2138
+ if (!id || !name) continue;
2139
+ const key = `${dbPath}:${id}`;
2140
+ const current = contacts.get(key) ?? { name, phones: [], emails: [] };
2141
+ if (phone) current.phones.push(phone.trim());
2142
+ if (email) current.emails.push(email.trim());
2143
+ contacts.set(key, current);
2144
+ }
2145
+ } catch (err) {
2146
+ if (args.debug === true) console.error(`Could not read Contacts DB ${dbPath}:`, safeError(err));
2147
+ }
2148
+ }
2149
+
2150
+ return [...contacts.values()].filter((contact) => contact.name);
2151
+ }
2152
+
2153
+ function addressBookDatabasePaths() {
2154
+ const addressBookDir = join(homedir(), "Library", "Application Support", "AddressBook");
2155
+ try {
2156
+ const raw = execFileSync("find", [addressBookDir, "-maxdepth", "4", "-name", "AddressBook-v22.abcddb"], {
2157
+ encoding: "utf8",
2158
+ timeout: 5_000,
2159
+ });
2160
+ return [...new Set(raw.split("\n").map((path) => path.trim()).filter(Boolean))];
2161
+ } catch {
2162
+ return [join(addressBookDir, "AddressBook-v22.abcddb")];
2163
+ }
2164
+ }
2165
+
2111
2166
  function loadMyCard() {
2112
2167
  if (platform() !== "darwin") return null;
2113
2168
  const script = `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shepherd-onboard",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Customer-facing Shepherd raw sync onboarding CLI",
5
5
  "type": "module",
6
6
  "bin": {