@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 +6 -0
- package/README.md +9 -7
- package/dist/index.mjs +68 -85
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
|
1690
|
-
if (
|
|
1691
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
21159
|
+
var version$1 = "0.10.0";
|
|
21177
21160
|
//#endregion
|
|
21178
21161
|
//#region src/lib/env.ts
|
|
21179
21162
|
const URLWithTrailingSlash = url().transform(ensureTrailingSlash);
|