@usesocial/cli 0.9.0 → 0.10.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @usesocial/cli
2
2
 
3
+ ## 0.10.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#38](https://github.com/usesocial/monorepo/pull/38) [`9f1f30d`](https://github.com/usesocial/monorepo/commit/9f1f30daeb89b5ad83bcc456a0ed71a5587e790c) Thanks [@CyrusNuevoDia](https://github.com/CyrusNuevoDia)! - Add `social x sync timeline` for syncing the authenticated X home timeline into the local SQL mirror, remove the live `social x timeline` shortcut from the public command surface, and let local SQL reads query partial mirror rows.
8
+
3
9
  ## 0.9.0
4
10
 
5
11
  ### Minor Changes
package/README.md CHANGED
@@ -48,7 +48,7 @@ social x profile
48
48
  | "Surface the ten warm contacts worth reaching out to this week." | `linkedin search people`, `linkedin reactions`, `linkedin sql` |
49
49
  | "Pull everyone who reacted to my last LinkedIn post and flag the warm leads." | `linkedin posts <target>`, `linkedin reactions`, `linkedin profile` |
50
50
  | "Read this creator's recent posts and break down what makes them land." | `linkedin posts <target>`, `x tweets <target>` |
51
- | "Find what people are saying about AI agents this week and summarize the themes." | `linkedin search posts`, `x timeline` |
51
+ | "Find what people are saying about AI agents this week and summarize the themes." | `linkedin search posts`, `x sync timeline`, `x sql` |
52
52
  | "Triage my inbox and draft replies for approval." | `sync messages`, `sql`, `linkedin message`, `x message` |
53
53
  | "Draft a reply to this thread and post it once I approve." | `linkedin comment`, `x post` |
54
54
 
@@ -161,7 +161,7 @@ profile lookup is needed.
161
161
  | `jobs <target>` | List a company's job postings. | |
162
162
  | `search people\|posts\|jobs\|companies <keywords>` | Search LinkedIn. | |
163
163
  | `sync [collection]` | Pull your own `connections`, `posts`, `messages`, or `requests` into the local mirror; costs credits. | local |
164
- | `sql [query]` | Query your synced local mirror with read-only SQL - free; bare `sql` prints compact schema metadata. | |
164
+ | `sql [query]` | Query your local mirror with read-only SQL - free; bare `sql` prints compact schema metadata. | |
165
165
  | `post` | Create a post (body from stdin). | write |
166
166
  | `comment <target>` | Comment on a post (body from stdin). | write |
167
167
  | `react <target>` | React to a post; use `--type` for non-like reactions. | write |
@@ -180,7 +180,6 @@ profile lookup is needed.
180
180
  | --- | --- | --- |
181
181
  | `profile [target]` | Fetch a profile; omit target for your own account. | |
182
182
  | `tweets <target>` | Live posts for a user; target required. | |
183
- | `timeline` | Read the home timeline. | |
184
183
  | `mentions [target]` | List posts mentioning a user; omit target for your own account. | |
185
184
  | `liked [target]` | List posts a user liked; omit target for your own account. | |
186
185
  | `followers [target]` | Live follower graph read; omit target for your own account. Costs credits. | |
@@ -190,8 +189,8 @@ profile lookup is needed.
190
189
  | `quotes <target>` | List quote posts of a post. | |
191
190
  | `likers <target>` | List users who liked a post. | |
192
191
  | `reposters <target>` | List users who reposted a post. | |
193
- | `sync [collection]` | Pull your own `tweets`, `followers`, `following`, `bookmarks`, or `messages` into the local mirror; costs credits. | local |
194
- | `sql [query]` | Query your synced local mirror with read-only SQL - free; bare `sql` prints compact schema metadata. | |
192
+ | `sync [collection]` | Pull your own `tweets`, `timeline`, `followers`, `following`, `bookmarks`, `liked`, `mentions`, or `messages` into the local mirror; costs credits. | local |
193
+ | `sql [query]` | Query your local mirror with read-only SQL - free; bare `sql` prints compact schema metadata. | |
195
194
  | `post` | Create a post (body from stdin). | write |
196
195
  | `message <recipients>` | Send a DM (body from stdin). | write |
197
196
  | `like <target>` / `unlike <target>` | Like or unlike a post. | write |
@@ -276,7 +275,9 @@ query it with `sql`. SQL reads show local freshness in `meta.cache.tables` and a
276
275
  ```sh
277
276
  social x sql "SELECT sender_username, text FROM x_messages ORDER BY created_at DESC LIMIT 5" \
278
277
  | jq '{cost: .meta.cost, cache: .meta.cache}'
279
- social x timeline --limit 20 | jq '{cost: .meta.cost, cursor: .meta.cursor, cache: .meta.cache}'
278
+ social x sync timeline
279
+ social x sql "SELECT text, url FROM x_timeline ORDER BY created_at DESC LIMIT 20" \
280
+ | jq '{cost: .meta.cost, cache: .meta.cache}'
280
281
  ```
281
282
 
282
283
  Agents should watch `.meta.cost` during high-fanout loops, use
@@ -303,11 +304,12 @@ social x sync messages
303
304
  social x sql "SELECT sender_username, text FROM x_messages ORDER BY created_at DESC LIMIT 20"
304
305
  social x sql "SELECT text, url FROM x_bookmarks ORDER BY created_at DESC LIMIT 50"
305
306
  social x sql "SELECT username, name, followers_count FROM x_followers ORDER BY followers_count DESC LIMIT 100"
307
+ social x sync timeline
308
+ social x sql "SELECT text, url FROM x_timeline ORDER BY created_at DESC LIMIT 50"
306
309
 
307
310
  # X live reads.
308
311
  social x tweets profile_id:<x-user-id> --limit 30
309
312
  social x replies post_id:<post-id> --limit 25
310
- social x timeline --limit 50
311
313
 
312
314
  # Writes (body via stdin).
313
315
  echo "gm" | social x post
package/dist/index.mjs CHANGED
@@ -752,7 +752,7 @@ const defineRuntimeOperationCommand = (adapter, op, deps) => {
752
752
  });
753
753
  };
754
754
  //#endregion
755
- //#region ../../packages/sync-engine/src/collections/linkedin.ts
755
+ //#region ../../packages/sqlsync/src/collections/linkedin.ts
756
756
  const linkedinCursorPagination = {
757
757
  style: "cursor",
758
758
  limitParam: "limit",
@@ -1072,7 +1072,7 @@ const LINKEDIN_CACHE_METADATA = {
1072
1072
  }
1073
1073
  };
1074
1074
  //#endregion
1075
- //#region ../../packages/sync-engine/src/collections/x.ts
1075
+ //#region ../../packages/sqlsync/src/collections/x.ts
1076
1076
  const xPagination = {
1077
1077
  style: "cursor",
1078
1078
  limitParam: "max_results",
@@ -1216,6 +1216,21 @@ const X_COLLECTIONS = [
1216
1216
  expectedFields: expectedTweetFields,
1217
1217
  lift: liftTweet
1218
1218
  },
1219
+ {
1220
+ key: "x_timeline",
1221
+ table: "x_timeline_raw",
1222
+ platform: "x",
1223
+ requests: [{
1224
+ path: "/users/:id/timelines/reverse_chronological",
1225
+ bakedQuery: defaultTweetBakedQuery
1226
+ }],
1227
+ ownIdToken: ":id",
1228
+ pagination: xPagination,
1229
+ pageSize: 100,
1230
+ sinceField: "created_at",
1231
+ expectedFields: expectedTweetFields,
1232
+ lift: liftTweet
1233
+ },
1219
1234
  {
1220
1235
  key: "x_followers",
1221
1236
  table: "x_followers_raw",
@@ -1313,7 +1328,8 @@ const X_CACHE_METADATA = {
1313
1328
  "x_messages.sender_id = x_profiles.provider_id",
1314
1329
  "x_messages.counterpart_id = x_profiles.provider_id",
1315
1330
  "x_conversation_participants.participant_id = x_profiles.provider_id",
1316
- "x_followers.id = x_profiles.provider_id"
1331
+ "x_followers.id = x_profiles.provider_id",
1332
+ "x_timeline.author_id = x_profiles.provider_id"
1317
1333
  ],
1318
1334
  enums: {
1319
1335
  "x_followers.verified_type": [
@@ -1364,6 +1380,11 @@ const X_CACHE_METADATA = {
1364
1380
  "sender_id"
1365
1381
  ],
1366
1382
  x_profiles: ["username"],
1383
+ x_timeline: [
1384
+ "created_at",
1385
+ "author_id",
1386
+ "conversation_id"
1387
+ ],
1367
1388
  x_tweets: [
1368
1389
  "created_at",
1369
1390
  "author_id",
@@ -1372,7 +1393,7 @@ const X_CACHE_METADATA = {
1372
1393
  }
1373
1394
  };
1374
1395
  //#endregion
1375
- //#region ../../packages/sync-engine/src/collections/index.ts
1396
+ //#region ../../packages/sqlsync/src/collections/index.ts
1376
1397
  const COLLECTIONS = [...X_COLLECTIONS, ...LINKEDIN_COLLECTIONS];
1377
1398
  const CACHE_METADATA_BY_PLATFORM = {
1378
1399
  linkedin: LINKEDIN_CACHE_METADATA,
@@ -1392,7 +1413,7 @@ const resetTablesFor = (collection) => {
1392
1413
  };
1393
1414
  const resetPhysicalTablesFor = (collection) => resetTablesFor(collection).map(physicalTableFor);
1394
1415
  //#endregion
1395
- //#region ../../packages/sync-engine/src/migrations.generated.ts
1416
+ //#region ../../packages/sqlsync/src/migrations.generated.ts
1396
1417
  const MIGRATIONS = [
1397
1418
  {
1398
1419
  "tag": "0000_modern_stephen_strange",
@@ -1433,10 +1454,14 @@ const MIGRATIONS = [
1433
1454
  {
1434
1455
  "tag": "0009_x_liked_mentions",
1435
1456
  "sql": "CREATE TABLE \"x_liked_raw\" (\n \"id\" text PRIMARY KEY NOT NULL,\n \"created_at\" integer,\n \"author_id\" text,\n \"conversation_id\" text,\n \"in_reply_to_user_id\" text,\n \"lang\" text,\n \"reply_settings\" text,\n \"text\" text,\n \"url\" text,\n \"paid_partnership\" integer,\n \"possibly_sensitive\" integer,\n \"like_count\" integer,\n \"retweet_count\" integer,\n \"reply_count\" integer,\n \"quote_count\" integer,\n \"bookmark_count\" integer,\n \"impression_count\" integer,\n \"raw\" text NOT NULL,\n \"synced_at\" integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX \"x_liked_created_at_idx\" ON \"x_liked_raw\" (\"created_at\");--> statement-breakpoint\nCREATE INDEX \"x_liked_author_id_created_at_idx\" ON \"x_liked_raw\" (\"author_id\",\"created_at\");--> statement-breakpoint\nCREATE INDEX \"x_liked_conversation_id_created_at_idx\" ON \"x_liked_raw\" (\"conversation_id\",\"created_at\");--> statement-breakpoint\nCREATE VIEW \"x_liked\" AS\nSELECT \"id\", \"created_at\", \"author_id\", \"conversation_id\", \"in_reply_to_user_id\", \"lang\",\n \"reply_settings\", \"text\", \"url\", \"paid_partnership\", \"possibly_sensitive\",\n \"like_count\", \"retweet_count\", \"reply_count\", \"quote_count\", \"bookmark_count\",\n \"impression_count\"\nFROM \"x_liked_raw\";\n--> statement-breakpoint\nCREATE TABLE \"x_mentions_raw\" (\n \"id\" text PRIMARY KEY NOT NULL,\n \"created_at\" integer,\n \"author_id\" text,\n \"conversation_id\" text,\n \"in_reply_to_user_id\" text,\n \"lang\" text,\n \"reply_settings\" text,\n \"text\" text,\n \"url\" text,\n \"paid_partnership\" integer,\n \"possibly_sensitive\" integer,\n \"like_count\" integer,\n \"retweet_count\" integer,\n \"reply_count\" integer,\n \"quote_count\" integer,\n \"bookmark_count\" integer,\n \"impression_count\" integer,\n \"raw\" text NOT NULL,\n \"synced_at\" integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX \"x_mentions_created_at_idx\" ON \"x_mentions_raw\" (\"created_at\");--> statement-breakpoint\nCREATE INDEX \"x_mentions_author_id_created_at_idx\" ON \"x_mentions_raw\" (\"author_id\",\"created_at\");--> statement-breakpoint\nCREATE INDEX \"x_mentions_conversation_id_created_at_idx\" ON \"x_mentions_raw\" (\"conversation_id\",\"created_at\");--> statement-breakpoint\nCREATE VIEW \"x_mentions\" AS\nSELECT \"id\", \"created_at\", \"author_id\", \"conversation_id\", \"in_reply_to_user_id\", \"lang\",\n \"reply_settings\", \"text\", \"url\", \"paid_partnership\", \"possibly_sensitive\",\n \"like_count\", \"retweet_count\", \"reply_count\", \"quote_count\", \"bookmark_count\",\n \"impression_count\"\nFROM \"x_mentions_raw\";\n"
1457
+ },
1458
+ {
1459
+ "tag": "0010_x_timeline",
1460
+ "sql": "CREATE TABLE \"x_timeline_raw\" (\n \"id\" text PRIMARY KEY NOT NULL,\n \"created_at\" integer,\n \"author_id\" text,\n \"conversation_id\" text,\n \"in_reply_to_user_id\" text,\n \"lang\" text,\n \"reply_settings\" text,\n \"text\" text,\n \"url\" text,\n \"paid_partnership\" integer,\n \"possibly_sensitive\" integer,\n \"like_count\" integer,\n \"retweet_count\" integer,\n \"reply_count\" integer,\n \"quote_count\" integer,\n \"bookmark_count\" integer,\n \"impression_count\" integer,\n \"raw\" text NOT NULL,\n \"synced_at\" integer NOT NULL\n);\n--> statement-breakpoint\nCREATE INDEX \"x_timeline_created_at_idx\" ON \"x_timeline_raw\" (\"created_at\");--> statement-breakpoint\nCREATE INDEX \"x_timeline_author_id_created_at_idx\" ON \"x_timeline_raw\" (\"author_id\",\"created_at\");--> statement-breakpoint\nCREATE INDEX \"x_timeline_conversation_id_created_at_idx\" ON \"x_timeline_raw\" (\"conversation_id\",\"created_at\");--> statement-breakpoint\nCREATE VIEW \"x_timeline\" AS\nSELECT \"id\", \"created_at\", \"author_id\", \"conversation_id\", \"in_reply_to_user_id\", \"lang\",\n \"reply_settings\", \"text\", \"url\", \"paid_partnership\", \"possibly_sensitive\",\n \"like_count\", \"retweet_count\", \"reply_count\", \"quote_count\", \"bookmark_count\",\n \"impression_count\"\nFROM \"x_timeline_raw\";\n"
1436
1461
  }
1437
1462
  ];
1438
1463
  //#endregion
1439
- //#region ../../packages/sync-engine/src/migrate.ts
1464
+ //#region ../../packages/sqlsync/src/migrate.ts
1440
1465
  const readUserVersion = (db) => {
1441
1466
  const version = db.prepare("PRAGMA user_version").get()?.user_version;
1442
1467
  return typeof version === "number" ? version : Number(version ?? 0);
@@ -1470,7 +1495,7 @@ const assertAtLatest = (db) => {
1470
1495
  if (currentVersion < MIGRATIONS.length) throw new Error(`Local SQLite cache schema is at version ${currentVersion}; expected ${MIGRATIONS.length}`);
1471
1496
  };
1472
1497
  //#endregion
1473
- //#region ../../packages/sync-engine/src/sqlite.ts
1498
+ //#region ../../packages/sqlsync/src/sqlite.ts
1474
1499
  const loadModule = createRequire(import.meta.url);
1475
1500
  const isBunRuntime = () => typeof Bun !== "undefined";
1476
1501
  const normalizeValue = (value) => {
@@ -1526,7 +1551,7 @@ const openDatabase = (path, opts) => {
1526
1551
  };
1527
1552
  };
1528
1553
  //#endregion
1529
- //#region ../../packages/sync-engine/src/write-through.ts
1554
+ //#region ../../packages/sqlsync/src/write-through.ts
1530
1555
  const quoteIdentifier$3 = (identifier) => `\`${identifier.replaceAll("`", "``")}\``;
1531
1556
  const collectionForTable = (table) => table.endsWith("_raw") ? table.slice(0, -4) : table;
1532
1557
  const profileTables$1 = new Set(["x_profiles_raw", "li_profiles_raw"]);
@@ -1574,17 +1599,10 @@ const applyWriteThrough = (db, args) => {
1574
1599
  };
1575
1600
  };
1576
1601
  //#endregion
1577
- //#region ../../packages/sync-engine/src/cache.ts
1578
- var NeverSyncedError = class extends Error {
1579
- name = "NeverSyncedError";
1580
- tables;
1581
- constructor(tables, message) {
1582
- super(message);
1583
- this.tables = tables;
1584
- }
1585
- };
1602
+ //#region ../../packages/sqlsync/src/cache.ts
1586
1603
  const publicCacheTables = [
1587
1604
  "x_tweets",
1605
+ "x_timeline",
1588
1606
  "x_followers",
1589
1607
  "x_following",
1590
1608
  "x_bookmarks",
@@ -1614,6 +1632,7 @@ const sanitizeAccountId = (accountId) => accountId.replaceAll(/[^A-Za-z0-9._-]/g
1614
1632
  const quoteIdentifier$2 = (identifier) => `\`${identifier.replaceAll("`", "``")}\``;
1615
1633
  const resolveCachePath = (path) => path === ":memory:" ? path : resolve(path);
1616
1634
  const rawTableFor = (table) => table === "sync_state" ? table : `${table}_raw`;
1635
+ const partialSyncCursorPrefix = "partial-sync-v1:";
1617
1636
  const collectionForReadTable = (table) => table.endsWith("_raw") ? table.slice(0, -4) : table;
1618
1637
  const physicalTableForCacheTable = (table) => table === "sync_state" || table.endsWith("_raw") ? table : rawTableFor(table);
1619
1638
  const stringOrNull = (value) => typeof value === "string" ? value : null;
@@ -1644,7 +1663,6 @@ const referencedTablesForRead = (sql, platform) => {
1644
1663
  const allowedTables = platformTables[platform];
1645
1664
  return knownTableTokens(sql).filter((table) => allowedTables.has(table));
1646
1665
  };
1647
- const collectionShortName = (collection) => collection.replace(/^(x|li)_/, "");
1648
1666
  const tableFreshnessPolicies = {
1649
1667
  x_conversation_participants: {
1650
1668
  kind: "collection",
@@ -1656,13 +1674,11 @@ const tableFreshnessPolicies = {
1656
1674
  },
1657
1675
  li_conversations: {
1658
1676
  kind: "collection",
1659
- collection: "li_conversations",
1660
- commandCollection: "li_messages"
1677
+ collection: "li_conversations"
1661
1678
  },
1662
1679
  li_conversations_raw: {
1663
1680
  kind: "collection",
1664
- collection: "li_conversations",
1665
- commandCollection: "li_messages"
1681
+ collection: "li_conversations"
1666
1682
  },
1667
1683
  li_profiles: {
1668
1684
  kind: "platform",
@@ -1686,44 +1702,25 @@ const freshnessPolicyForTable = (table) => tableFreshnessPolicies[table] ?? {
1686
1702
  collection: collectionForReadTable(table)
1687
1703
  };
1688
1704
  const platformCollectionPrefix = (platform) => platform === "linkedin" ? "li_" : "x_";
1689
- const syncCommandForPolicy = (platform, policy) => {
1690
- if (policy.kind === "platform") return `social ${policy.platform} sync`;
1691
- return `social ${platform} sync ${collectionShortName(policy.commandCollection ?? policy.collection)}`;
1692
- };
1693
- const missingNameForPolicy = (table, policy) => policy.kind === "platform" ? table : policy.collection;
1694
- const formatList = (items) => {
1695
- if (items.length <= 1) return items[0] ?? "";
1696
- if (items.length === 2) return `${items[0]} and ${items[1]}`;
1697
- return `${items.slice(0, -1).join(", ")}, and ${items.at(-1)}`;
1705
+ const partialCollectionLastSynced = (db, collection, cursor) => {
1706
+ if (typeof cursor !== "string" || !cursor.startsWith(partialSyncCursorPrefix)) return null;
1707
+ if (!cacheTables.has(collection)) return null;
1708
+ return numberOrNull(db.prepare(`SELECT MAX(synced_at) AS last_synced FROM ${quoteIdentifier$2(rawTableFor(collection))}`).get()?.last_synced);
1698
1709
  };
1699
1710
  const collectionLastSynced = (db, collection) => {
1700
- return numberOrNull(db.prepare("SELECT last_synced FROM sync_state WHERE collection = :collection").get({ collection })?.last_synced);
1711
+ const syncState = db.prepare("SELECT cursor, last_synced FROM sync_state WHERE collection = :collection").get({ collection });
1712
+ return numberOrNull(syncState?.last_synced) ?? partialCollectionLastSynced(db, collection, syncState?.cursor);
1701
1713
  };
1702
1714
  const platformLastSynced = (db, platform) => {
1703
- return numberOrNull(db.prepare("SELECT MAX(last_synced) AS last_synced FROM sync_state WHERE collection GLOB :glob").get({ glob: `${platformCollectionPrefix(platform)}*` })?.last_synced);
1715
+ const syncState = db.prepare("SELECT MAX(last_synced) AS last_synced FROM sync_state WHERE collection GLOB :glob").get({ glob: `${platformCollectionPrefix(platform)}*` });
1716
+ const partialRows = db.prepare("SELECT collection, cursor FROM sync_state WHERE collection GLOB :glob AND last_synced IS NULL AND cursor LIKE :partial").all({
1717
+ glob: `${platformCollectionPrefix(platform)}*`,
1718
+ partial: `${partialSyncCursorPrefix}%`
1719
+ });
1720
+ const timestamps = [numberOrNull(syncState?.last_synced), ...partialRows.map((row) => partialCollectionLastSynced(db, String(row.collection), row.cursor))].filter((value) => value !== null);
1721
+ return timestamps.length === 0 ? null : Math.max(...timestamps);
1704
1722
  };
1705
1723
  const lastSyncedForPolicy = (db, policy) => policy.kind === "platform" ? platformLastSynced(db, policy.platform) : collectionLastSynced(db, policy.collection);
1706
- const missingFreshnessForTables = (tables) => {
1707
- const missing = [];
1708
- const seen = /* @__PURE__ */ new Set();
1709
- for (const table of tables) {
1710
- if (table.lastSynced != null) continue;
1711
- const key = `${table.missingName}\0${table.command}`;
1712
- if (!seen.has(key)) {
1713
- seen.add(key);
1714
- missing.push({
1715
- name: table.missingName,
1716
- command: table.command
1717
- });
1718
- }
1719
- }
1720
- return missing;
1721
- };
1722
- const neverSyncedMessage = (missing) => {
1723
- const names = missing.map((item) => item.name);
1724
- const commands = missing.map((item) => `\`${item.command}\``);
1725
- return `No synced ${formatList(names)} yet — run ${formatList(commands)} first.`;
1726
- };
1727
1724
  const assertKnownProfileTable = (table) => {
1728
1725
  assertKnownTable(table);
1729
1726
  if (!profileTables.has(physicalTableForCacheTable(table))) throw new Error(`Unknown profile table: ${table}`);
@@ -1817,7 +1814,7 @@ const createCache = (path, db) => ({
1817
1814
  if (!row) return;
1818
1815
  return {
1819
1816
  cursor: stringOrNull(row.cursor),
1820
- lastSynced: numberOrNull(row.last_synced),
1817
+ lastSynced: numberOrNull(row.last_synced) ?? partialCollectionLastSynced(db, collection, row.cursor),
1821
1818
  objectCount: numberOrNull(row.object_count)
1822
1819
  };
1823
1820
  },
@@ -1853,22 +1850,14 @@ const createCache = (path, db) => ({
1853
1850
  read: (sql, opts) => {
1854
1851
  assertPlatformQuery(sql, opts.platform);
1855
1852
  const tables = referencedTablesForRead(sql, opts.platform).map((name) => {
1856
- const policy = freshnessPolicyForTable(name);
1857
1853
  return {
1858
1854
  name,
1859
- missingName: missingNameForPolicy(name, policy),
1860
- command: syncCommandForPolicy(opts.platform, policy),
1861
- lastSynced: lastSyncedForPolicy(db, policy)
1855
+ lastSynced: lastSyncedForPolicy(db, freshnessPolicyForTable(name))
1862
1856
  };
1863
1857
  });
1864
- const missingFreshness = missingFreshnessForTables(tables);
1865
- if (missingFreshness.length > 0) throw new NeverSyncedError(missingFreshness.map((item) => item.name), neverSyncedMessage(missingFreshness));
1866
1858
  return {
1867
1859
  rows: db.prepare(sql).all(),
1868
- tables: tables.map(({ name, lastSynced }) => ({
1869
- name,
1870
- lastSynced
1871
- }))
1860
+ tables
1872
1861
  };
1873
1862
  },
1874
1863
  describe: (opts) => {
@@ -1893,7 +1882,6 @@ const createCache = (path, db) => ({
1893
1882
  name,
1894
1883
  rows: Number(rowCount?.rows ?? 0),
1895
1884
  synced_at: syncedAt,
1896
- query_ready: syncedAt != null,
1897
1885
  columns: columnsForTable(name),
1898
1886
  indexed: metadata.indexed[name] ?? []
1899
1887
  };
@@ -1905,7 +1893,7 @@ const createCache = (path, db) => ({
1905
1893
  }
1906
1894
  });
1907
1895
  //#endregion
1908
- //#region ../../packages/sync-engine/src/since.ts
1896
+ //#region ../../packages/sqlsync/src/since.ts
1909
1897
  const isoDatePattern = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
1910
1898
  const isoDateTimePattern = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])T([01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d{1,3})?)?(?:Z|[+-]([01]\d|2[0-3]):[0-5]\d)?$/;
1911
1899
  const timezonePattern = /(Z|[+-]\d{2}:\d{2})$/;
@@ -6499,17 +6487,17 @@ function superRefine(fn, params) {
6499
6487
  return /* @__PURE__ */ _superRefine(fn, params);
6500
6488
  }
6501
6489
  //#endregion
6502
- //#region ../../packages/sync-engine/src/cost.ts
6490
+ //#region ../../packages/sqlsync/src/cost.ts
6503
6491
  const creditsFromHeader = (headers) => {
6504
6492
  const rawCredits = headers?.get("social-x-credits-used");
6505
6493
  const credits = rawCredits == null ? NaN : Number.parseInt(rawCredits, 10);
6506
6494
  return Number.isNaN(credits) || credits < 0 ? 0 : credits;
6507
6495
  };
6508
6496
  //#endregion
6509
- //#region ../../packages/sync-engine/src/profiles.ts
6497
+ //#region ../../packages/sqlsync/src/profiles.ts
6510
6498
  const profileTableFor = (platform) => platform === "x" ? "x_profiles_raw" : "li_profiles_raw";
6511
6499
  //#endregion
6512
- //#region ../../packages/sync-engine/src/sync.ts
6500
+ //#region ../../packages/sqlsync/src/sync.ts
6513
6501
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
6514
6502
  const pageDelayFor = (pageDelayMs, pagesCompleted) => typeof pageDelayMs === "function" ? pageDelayMs(pagesCompleted) : pageDelayMs;
6515
6503
  var SyncPageError = class extends Error {
@@ -6761,7 +6749,7 @@ const walkRequest = async (args) => {
6761
6749
  ...args.parentId === void 0 ? {} : { parentId: args.parentId }
6762
6750
  }),
6763
6751
  objectCount: upserted,
6764
- lastSynced: null
6752
+ lastSynced: Date.now()
6765
6753
  });
6766
6754
  partialCursorPersisted = true;
6767
6755
  return true;
@@ -6873,7 +6861,10 @@ const parentIdsFromCache = (deps, collection) => {
6873
6861
  const runSync = async (deps, opts) => {
6874
6862
  const { collection } = opts;
6875
6863
  if (opts.since != null && collection.sinceField === void 0) throw new Error(`Collection ${collection.key} does not support --since`);
6876
- if (opts.since != null && collection.requiresFullInitialSync && deps.cache.getCursor(collection.key)?.lastSynced == null) throw new Error(`Collection ${collection.key} requires a full initial sync before --since`);
6864
+ if (opts.since != null && collection.requiresFullInitialSync) {
6865
+ const storedState = deps.cache.getCursor(collection.key);
6866
+ if (storedState?.lastSynced == null || decodePartialSyncCursor(storedState.cursor)) throw new Error(`Collection ${collection.key} requires a full initial sync before --since`);
6867
+ }
6877
6868
  const credits = { spent: 0 };
6878
6869
  let pages = 0;
6879
6870
  let upserted = 0;
@@ -12514,7 +12505,7 @@ const buildSync$1 = (deps, platform) => defineCommand({
12514
12505
  const buildSql$1 = (deps, platform) => defineCommand({
12515
12506
  meta: {
12516
12507
  name: "sql",
12517
- description: "Query your synced local mirror with read-only SQL (free; run with no query to print tables)",
12508
+ description: "Query your local mirror with read-only SQL (free; run with no query to print tables)",
12518
12509
  capability: "read",
12519
12510
  mutates: false,
12520
12511
  schemaContract: {
@@ -12603,7 +12594,6 @@ const buildSql$1 = (deps, platform) => defineCommand({
12603
12594
  }
12604
12595
  });
12605
12596
  } catch (error) {
12606
- if (error instanceof NeverSyncedError) throw new UsageError(incompleteSyncMessageFor(readonlyCache, error));
12607
12597
  throw new UsageError(`Query failed (cache is read-only): ${error.message}`);
12608
12598
  }
12609
12599
  } finally {
@@ -12915,12 +12905,6 @@ const sentMessageRow = (args) => {
12915
12905
  raw: JSON.stringify(raw)
12916
12906
  };
12917
12907
  };
12918
- const incompleteSyncMessageFor = (cache, error) => {
12919
- if (!error.tables.includes("li_conversations")) return error.message;
12920
- const rows = cache.query("SELECT count(*) AS count FROM li_conversations_raw")[0]?.count;
12921
- if (typeof rows !== "number" || rows <= 0) return error.message;
12922
- return `li_conversations has ${rows.toLocaleString("en-US")} rows from an incomplete messages sync (rate-limited before finishing) — run \`social linkedin sync messages\` to complete it before querying.`;
12923
- };
12924
12908
  const buildMessageDelete = (deps) => {
12925
12909
  return defineCommand({
12926
12910
  meta: commandMeta$2({
@@ -13882,6 +13866,7 @@ const xProfileExtractors = {
13882
13866
  "GET /users/:id": extractProfile,
13883
13867
  "GET /users/:id/followers": extractList,
13884
13868
  "GET /users/:id/following": extractList,
13869
+ "GET /users/:id/timelines/reverse_chronological": extractIncludesUsers,
13885
13870
  "GET /users/by/username/:username": extractProfile
13886
13871
  };
13887
13872
  const X_OPERATIONS = [
@@ -16636,11 +16621,10 @@ const defineOperationCommand = (op, deps) => defineRuntimeOperationCommand(xRunt
16636
16621
  //#endregion
16637
16622
  //#region ../../packages/x/src/cli/commands.ts
16638
16623
  const hiddenRootCommands = new Set(["search"]);
16639
- const suppressedRootCommands = new Set(["bookmarks"]);
16624
+ const suppressedRootCommands = new Set(["bookmarks", "timeline"]);
16640
16625
  const rootCommandOrder = [
16641
16626
  "profile",
16642
16627
  "tweets",
16643
- "timeline",
16644
16628
  "mentions",
16645
16629
  "liked",
16646
16630
  "followers",
@@ -16942,7 +16926,7 @@ const buildSql = (deps, platform) => {
16942
16926
  return defineCommand({
16943
16927
  meta: commandMeta$1({
16944
16928
  name: "sql",
16945
- description: "Query your synced local mirror with read-only SQL (free; run with no query to print tables)",
16929
+ description: "Query your local mirror with read-only SQL (free; run with no query to print tables)",
16946
16930
  method: "GET",
16947
16931
  capability: "read",
16948
16932
  mutates: false,
@@ -17000,7 +16984,6 @@ const buildSql = (deps, platform) => {
17000
16984
  }
17001
16985
  });
17002
16986
  } catch (error) {
17003
- if (error instanceof NeverSyncedError) throw new UsageError(error.message);
17004
16987
  throw new UsageError(`Query failed (cache is read-only): ${error.message}`);
17005
16988
  }
17006
16989
  } finally {
@@ -21173,7 +21156,7 @@ function createEnv(opts) {
21173
21156
  }
21174
21157
  //#endregion
21175
21158
  //#region package.json
21176
- var version$1 = "0.9.0";
21159
+ var version$1 = "0.10.0";
21177
21160
  //#endregion
21178
21161
  //#region src/lib/env.ts
21179
21162
  const URLWithTrailingSlash = url().transform(ensureTrailingSlash);