@usesocial/cli 0.9.0 → 0.10.1

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/dist/index.mjs CHANGED
@@ -334,11 +334,12 @@ const buildRuntimeArgs = (adapter, op) => {
334
334
  };
335
335
  for (const [name, param] of Object.entries(op.config.bodyParams ?? {})) {
336
336
  const argName = bodyParamArgFor(adapter, name, param);
337
+ const valueHint = param.valueHint ?? flagName(argName);
337
338
  args[flagName(argName)] = {
338
339
  type: param.positional ? "positional" : "string",
339
340
  required: param.required === true,
340
341
  description: param.description ?? `${name} body value`,
341
- valueHint: param.valueHint ?? flagName(argName)
342
+ valueHint: param.kind === "array" ? `${valueHint}...` : valueHint
342
343
  };
343
344
  }
344
345
  if (op.config.mediaArg) args[flagName(op.config.mediaArg)] = {
@@ -363,6 +364,7 @@ const buildRuntimeArgs = (adapter, op) => {
363
364
  type: "string",
364
365
  description: "Account selector: @username or profile_id:<id>; overrides config default"
365
366
  };
367
+ for (const [name, def] of Object.entries(adapter.extraArgsFor?.(op) ?? {})) args[name] = def;
366
368
  if (adapter.isCacheable(op.endpoint)) args.header = {
367
369
  type: "string",
368
370
  alias: "H",
@@ -432,7 +434,8 @@ const buildRuntimeInput = (adapter, op, args) => {
432
434
  input.body = args.body;
433
435
  if (op.config.bodyTextArg) input[op.config.bodyTextArg] = args[bodyTextDisplayKeyFor(op) ?? op.config.bodyTextArg];
434
436
  Object.entries(op.config.bodyParams ?? {}).forEach(([name, param], index) => {
435
- input[name] = args[flagName(bodyParamArgFor(adapter, name, param))] ?? (Array.isArray(args._) ? args._[index] : void 0);
437
+ const argName = bodyParamArgFor(adapter, name, param);
438
+ input[name] = param.positional && param.kind === "array" && Array.isArray(args._) && args._.length > 0 ? args._ : args[flagName(argName)] ?? (Array.isArray(args._) ? args._[index] : void 0);
436
439
  });
437
440
  if (op.config.replyToArg) input[op.config.replyToArg] = args[op.config.replyToArg];
438
441
  }
@@ -445,7 +448,10 @@ const checkBodyRequired = (adapter, op, args, bodyTextOptional) => {
445
448
  const expressions = [];
446
449
  const bodyTextArg = op.config.bodyTextArg;
447
450
  if (bodyTextArg && op.config.bodyTextRequired !== false) expressions.push(args[bodyTextDisplayKeyFor(op) ?? bodyTextArg] === void 0);
448
- for (const [name, param] of Object.entries(op.config.bodyParams ?? {})) if (param.required) expressions.push(args[flagName(bodyParamArgFor(adapter, name, param))] === void 0);
451
+ for (const [name, param] of Object.entries(op.config.bodyParams ?? {})) if (param.required) {
452
+ const positionalValuePresent = param.positional && Array.isArray(args._) && args._.length > 0;
453
+ expressions.push(args[flagName(bodyParamArgFor(adapter, name, param))] === void 0 && !positionalValuePresent);
454
+ }
449
455
  if (expressions.length > 0 && typeof args.body !== "string" && expressions.some(Boolean)) throw bodyRequiredPreparseError(adapter, op);
450
456
  };
451
457
  const buildBody = (adapter, deps, op, input, bodyTextOptional) => {
@@ -458,7 +464,8 @@ const buildBody = (adapter, deps, op, input, bodyTextOptional) => {
458
464
  const bodyParamValues = /* @__PURE__ */ new Map();
459
465
  for (const [name, param] of bodyParamEntries) {
460
466
  const value = input[name];
461
- bodyParamValues.set(name, value === void 0 ? param.default : deps.parseQueryString(param.positional ? bodyParamArgFor(adapter, name, param) : `--${bodyParamArgFor(adapter, name, param)}`, value));
467
+ const displayName = param.positional ? bodyParamArgFor(adapter, name, param) : `--${bodyParamArgFor(adapter, name, param)}`;
468
+ bodyParamValues.set(name, value === void 0 ? param.default : param.kind === "array" ? deps.parseResourceIdList(displayName, Array.isArray(value) ? value : [value]) : deps.parseQueryString(displayName, value));
462
469
  }
463
470
  const body = { ...bodyFromJSON ?? {} };
464
471
  for (const [name] of bodyParamEntries) {
@@ -626,6 +633,16 @@ const runRuntimeOperation = async (adapter, op, contract, deps, args, rawArgs =
626
633
  }
627
634
  }
628
635
  for (const param of op.parameters.filter((candidate) => candidate.in === "path")) {
636
+ if (input[param.name] === void 0) {
637
+ const seeded = await adapter.seedPathValue?.({
638
+ op,
639
+ param,
640
+ args,
641
+ deps,
642
+ account
643
+ });
644
+ if (seeded !== void 0) input[param.name] = seeded;
645
+ }
629
646
  const mode = ownMode$1(op, param.name);
630
647
  const display = positionalDisplayKeyFor$1(op, param);
631
648
  let resolution;
@@ -752,7 +769,7 @@ const defineRuntimeOperationCommand = (adapter, op, deps) => {
752
769
  });
753
770
  };
754
771
  //#endregion
755
- //#region ../../packages/sync-engine/src/collections/linkedin.ts
772
+ //#region ../../packages/sqlsync/src/collections/linkedin.ts
756
773
  const linkedinCursorPagination = {
757
774
  style: "cursor",
758
775
  limitParam: "limit",
@@ -1072,7 +1089,7 @@ const LINKEDIN_CACHE_METADATA = {
1072
1089
  }
1073
1090
  };
1074
1091
  //#endregion
1075
- //#region ../../packages/sync-engine/src/collections/x.ts
1092
+ //#region ../../packages/sqlsync/src/collections/x.ts
1076
1093
  const xPagination = {
1077
1094
  style: "cursor",
1078
1095
  limitParam: "max_results",
@@ -1216,6 +1233,21 @@ const X_COLLECTIONS = [
1216
1233
  expectedFields: expectedTweetFields,
1217
1234
  lift: liftTweet
1218
1235
  },
1236
+ {
1237
+ key: "x_timeline",
1238
+ table: "x_timeline_raw",
1239
+ platform: "x",
1240
+ requests: [{
1241
+ path: "/users/:id/timelines/reverse_chronological",
1242
+ bakedQuery: defaultTweetBakedQuery
1243
+ }],
1244
+ ownIdToken: ":id",
1245
+ pagination: xPagination,
1246
+ pageSize: 100,
1247
+ sinceField: "created_at",
1248
+ expectedFields: expectedTweetFields,
1249
+ lift: liftTweet
1250
+ },
1219
1251
  {
1220
1252
  key: "x_followers",
1221
1253
  table: "x_followers_raw",
@@ -1313,7 +1345,8 @@ const X_CACHE_METADATA = {
1313
1345
  "x_messages.sender_id = x_profiles.provider_id",
1314
1346
  "x_messages.counterpart_id = x_profiles.provider_id",
1315
1347
  "x_conversation_participants.participant_id = x_profiles.provider_id",
1316
- "x_followers.id = x_profiles.provider_id"
1348
+ "x_followers.id = x_profiles.provider_id",
1349
+ "x_timeline.author_id = x_profiles.provider_id"
1317
1350
  ],
1318
1351
  enums: {
1319
1352
  "x_followers.verified_type": [
@@ -1364,6 +1397,11 @@ const X_CACHE_METADATA = {
1364
1397
  "sender_id"
1365
1398
  ],
1366
1399
  x_profiles: ["username"],
1400
+ x_timeline: [
1401
+ "created_at",
1402
+ "author_id",
1403
+ "conversation_id"
1404
+ ],
1367
1405
  x_tweets: [
1368
1406
  "created_at",
1369
1407
  "author_id",
@@ -1372,7 +1410,7 @@ const X_CACHE_METADATA = {
1372
1410
  }
1373
1411
  };
1374
1412
  //#endregion
1375
- //#region ../../packages/sync-engine/src/collections/index.ts
1413
+ //#region ../../packages/sqlsync/src/collections/index.ts
1376
1414
  const COLLECTIONS = [...X_COLLECTIONS, ...LINKEDIN_COLLECTIONS];
1377
1415
  const CACHE_METADATA_BY_PLATFORM = {
1378
1416
  linkedin: LINKEDIN_CACHE_METADATA,
@@ -1392,7 +1430,7 @@ const resetTablesFor = (collection) => {
1392
1430
  };
1393
1431
  const resetPhysicalTablesFor = (collection) => resetTablesFor(collection).map(physicalTableFor);
1394
1432
  //#endregion
1395
- //#region ../../packages/sync-engine/src/migrations.generated.ts
1433
+ //#region ../../packages/sqlsync/src/migrations.generated.ts
1396
1434
  const MIGRATIONS = [
1397
1435
  {
1398
1436
  "tag": "0000_modern_stephen_strange",
@@ -1433,10 +1471,14 @@ const MIGRATIONS = [
1433
1471
  {
1434
1472
  "tag": "0009_x_liked_mentions",
1435
1473
  "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"
1474
+ },
1475
+ {
1476
+ "tag": "0010_x_timeline",
1477
+ "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
1478
  }
1437
1479
  ];
1438
1480
  //#endregion
1439
- //#region ../../packages/sync-engine/src/migrate.ts
1481
+ //#region ../../packages/sqlsync/src/migrate.ts
1440
1482
  const readUserVersion = (db) => {
1441
1483
  const version = db.prepare("PRAGMA user_version").get()?.user_version;
1442
1484
  return typeof version === "number" ? version : Number(version ?? 0);
@@ -1470,7 +1512,7 @@ const assertAtLatest = (db) => {
1470
1512
  if (currentVersion < MIGRATIONS.length) throw new Error(`Local SQLite cache schema is at version ${currentVersion}; expected ${MIGRATIONS.length}`);
1471
1513
  };
1472
1514
  //#endregion
1473
- //#region ../../packages/sync-engine/src/sqlite.ts
1515
+ //#region ../../packages/sqlsync/src/sqlite.ts
1474
1516
  const loadModule = createRequire(import.meta.url);
1475
1517
  const isBunRuntime = () => typeof Bun !== "undefined";
1476
1518
  const normalizeValue = (value) => {
@@ -1526,7 +1568,7 @@ const openDatabase = (path, opts) => {
1526
1568
  };
1527
1569
  };
1528
1570
  //#endregion
1529
- //#region ../../packages/sync-engine/src/write-through.ts
1571
+ //#region ../../packages/sqlsync/src/write-through.ts
1530
1572
  const quoteIdentifier$3 = (identifier) => `\`${identifier.replaceAll("`", "``")}\``;
1531
1573
  const collectionForTable = (table) => table.endsWith("_raw") ? table.slice(0, -4) : table;
1532
1574
  const profileTables$1 = new Set(["x_profiles_raw", "li_profiles_raw"]);
@@ -1574,17 +1616,10 @@ const applyWriteThrough = (db, args) => {
1574
1616
  };
1575
1617
  };
1576
1618
  //#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
- };
1619
+ //#region ../../packages/sqlsync/src/cache.ts
1586
1620
  const publicCacheTables = [
1587
1621
  "x_tweets",
1622
+ "x_timeline",
1588
1623
  "x_followers",
1589
1624
  "x_following",
1590
1625
  "x_bookmarks",
@@ -1614,6 +1649,7 @@ const sanitizeAccountId = (accountId) => accountId.replaceAll(/[^A-Za-z0-9._-]/g
1614
1649
  const quoteIdentifier$2 = (identifier) => `\`${identifier.replaceAll("`", "``")}\``;
1615
1650
  const resolveCachePath = (path) => path === ":memory:" ? path : resolve(path);
1616
1651
  const rawTableFor = (table) => table === "sync_state" ? table : `${table}_raw`;
1652
+ const partialSyncCursorPrefix = "partial-sync-v1:";
1617
1653
  const collectionForReadTable = (table) => table.endsWith("_raw") ? table.slice(0, -4) : table;
1618
1654
  const physicalTableForCacheTable = (table) => table === "sync_state" || table.endsWith("_raw") ? table : rawTableFor(table);
1619
1655
  const stringOrNull = (value) => typeof value === "string" ? value : null;
@@ -1644,7 +1680,6 @@ const referencedTablesForRead = (sql, platform) => {
1644
1680
  const allowedTables = platformTables[platform];
1645
1681
  return knownTableTokens(sql).filter((table) => allowedTables.has(table));
1646
1682
  };
1647
- const collectionShortName = (collection) => collection.replace(/^(x|li)_/, "");
1648
1683
  const tableFreshnessPolicies = {
1649
1684
  x_conversation_participants: {
1650
1685
  kind: "collection",
@@ -1656,13 +1691,11 @@ const tableFreshnessPolicies = {
1656
1691
  },
1657
1692
  li_conversations: {
1658
1693
  kind: "collection",
1659
- collection: "li_conversations",
1660
- commandCollection: "li_messages"
1694
+ collection: "li_conversations"
1661
1695
  },
1662
1696
  li_conversations_raw: {
1663
1697
  kind: "collection",
1664
- collection: "li_conversations",
1665
- commandCollection: "li_messages"
1698
+ collection: "li_conversations"
1666
1699
  },
1667
1700
  li_profiles: {
1668
1701
  kind: "platform",
@@ -1686,44 +1719,25 @@ const freshnessPolicyForTable = (table) => tableFreshnessPolicies[table] ?? {
1686
1719
  collection: collectionForReadTable(table)
1687
1720
  };
1688
1721
  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)}`;
1722
+ const partialCollectionLastSynced = (db, collection, cursor) => {
1723
+ if (typeof cursor !== "string" || !cursor.startsWith(partialSyncCursorPrefix)) return null;
1724
+ if (!cacheTables.has(collection)) return null;
1725
+ return numberOrNull(db.prepare(`SELECT MAX(synced_at) AS last_synced FROM ${quoteIdentifier$2(rawTableFor(collection))}`).get()?.last_synced);
1698
1726
  };
1699
1727
  const collectionLastSynced = (db, collection) => {
1700
- return numberOrNull(db.prepare("SELECT last_synced FROM sync_state WHERE collection = :collection").get({ collection })?.last_synced);
1728
+ const syncState = db.prepare("SELECT cursor, last_synced FROM sync_state WHERE collection = :collection").get({ collection });
1729
+ return numberOrNull(syncState?.last_synced) ?? partialCollectionLastSynced(db, collection, syncState?.cursor);
1701
1730
  };
1702
1731
  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);
1732
+ const syncState = db.prepare("SELECT MAX(last_synced) AS last_synced FROM sync_state WHERE collection GLOB :glob").get({ glob: `${platformCollectionPrefix(platform)}*` });
1733
+ const partialRows = db.prepare("SELECT collection, cursor FROM sync_state WHERE collection GLOB :glob AND last_synced IS NULL AND cursor LIKE :partial").all({
1734
+ glob: `${platformCollectionPrefix(platform)}*`,
1735
+ partial: `${partialSyncCursorPrefix}%`
1736
+ });
1737
+ const timestamps = [numberOrNull(syncState?.last_synced), ...partialRows.map((row) => partialCollectionLastSynced(db, String(row.collection), row.cursor))].filter((value) => value !== null);
1738
+ return timestamps.length === 0 ? null : Math.max(...timestamps);
1704
1739
  };
1705
1740
  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
1741
  const assertKnownProfileTable = (table) => {
1728
1742
  assertKnownTable(table);
1729
1743
  if (!profileTables.has(physicalTableForCacheTable(table))) throw new Error(`Unknown profile table: ${table}`);
@@ -1817,7 +1831,7 @@ const createCache = (path, db) => ({
1817
1831
  if (!row) return;
1818
1832
  return {
1819
1833
  cursor: stringOrNull(row.cursor),
1820
- lastSynced: numberOrNull(row.last_synced),
1834
+ lastSynced: numberOrNull(row.last_synced) ?? partialCollectionLastSynced(db, collection, row.cursor),
1821
1835
  objectCount: numberOrNull(row.object_count)
1822
1836
  };
1823
1837
  },
@@ -1853,22 +1867,14 @@ const createCache = (path, db) => ({
1853
1867
  read: (sql, opts) => {
1854
1868
  assertPlatformQuery(sql, opts.platform);
1855
1869
  const tables = referencedTablesForRead(sql, opts.platform).map((name) => {
1856
- const policy = freshnessPolicyForTable(name);
1857
1870
  return {
1858
1871
  name,
1859
- missingName: missingNameForPolicy(name, policy),
1860
- command: syncCommandForPolicy(opts.platform, policy),
1861
- lastSynced: lastSyncedForPolicy(db, policy)
1872
+ lastSynced: lastSyncedForPolicy(db, freshnessPolicyForTable(name))
1862
1873
  };
1863
1874
  });
1864
- const missingFreshness = missingFreshnessForTables(tables);
1865
- if (missingFreshness.length > 0) throw new NeverSyncedError(missingFreshness.map((item) => item.name), neverSyncedMessage(missingFreshness));
1866
1875
  return {
1867
1876
  rows: db.prepare(sql).all(),
1868
- tables: tables.map(({ name, lastSynced }) => ({
1869
- name,
1870
- lastSynced
1871
- }))
1877
+ tables
1872
1878
  };
1873
1879
  },
1874
1880
  describe: (opts) => {
@@ -1893,7 +1899,6 @@ const createCache = (path, db) => ({
1893
1899
  name,
1894
1900
  rows: Number(rowCount?.rows ?? 0),
1895
1901
  synced_at: syncedAt,
1896
- query_ready: syncedAt != null,
1897
1902
  columns: columnsForTable(name),
1898
1903
  indexed: metadata.indexed[name] ?? []
1899
1904
  };
@@ -1905,7 +1910,7 @@ const createCache = (path, db) => ({
1905
1910
  }
1906
1911
  });
1907
1912
  //#endregion
1908
- //#region ../../packages/sync-engine/src/since.ts
1913
+ //#region ../../packages/sqlsync/src/since.ts
1909
1914
  const isoDatePattern = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
1910
1915
  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
1916
  const timezonePattern = /(Z|[+-]\d{2}:\d{2})$/;
@@ -6499,17 +6504,17 @@ function superRefine(fn, params) {
6499
6504
  return /* @__PURE__ */ _superRefine(fn, params);
6500
6505
  }
6501
6506
  //#endregion
6502
- //#region ../../packages/sync-engine/src/cost.ts
6507
+ //#region ../../packages/sqlsync/src/cost.ts
6503
6508
  const creditsFromHeader = (headers) => {
6504
6509
  const rawCredits = headers?.get("social-x-credits-used");
6505
6510
  const credits = rawCredits == null ? NaN : Number.parseInt(rawCredits, 10);
6506
6511
  return Number.isNaN(credits) || credits < 0 ? 0 : credits;
6507
6512
  };
6508
6513
  //#endregion
6509
- //#region ../../packages/sync-engine/src/profiles.ts
6514
+ //#region ../../packages/sqlsync/src/profiles.ts
6510
6515
  const profileTableFor = (platform) => platform === "x" ? "x_profiles_raw" : "li_profiles_raw";
6511
6516
  //#endregion
6512
- //#region ../../packages/sync-engine/src/sync.ts
6517
+ //#region ../../packages/sqlsync/src/sync.ts
6513
6518
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
6514
6519
  const pageDelayFor = (pageDelayMs, pagesCompleted) => typeof pageDelayMs === "function" ? pageDelayMs(pagesCompleted) : pageDelayMs;
6515
6520
  var SyncPageError = class extends Error {
@@ -6561,6 +6566,12 @@ const decodePartialSyncCursor = (value) => {
6561
6566
  }
6562
6567
  };
6563
6568
  const encodePartialSyncCursor = (cursor) => `${PARTIAL_SYNC_CURSOR_PREFIX}${JSON.stringify(cursor)}`;
6569
+ const partialCursorMatchesSince = (cursor, since) => {
6570
+ const requestedSince = since ?? null;
6571
+ if (cursor.since === requestedSince) return true;
6572
+ return requestedSince !== null && (cursor.since === null || cursor.since <= requestedSince);
6573
+ };
6574
+ const preservesBroaderPartialOnSince = (cursor, since, stoppedReason) => cursor !== void 0 && cursor.since !== (since ?? null) && stoppedReason === "since";
6564
6575
  const isRateLimitError = (error) => isRecord$9(error) && error.status === 429;
6565
6576
  const retryAfterSecondsFromRateLimitError = (error) => {
6566
6577
  if (!(isRateLimitError(error) && isRecord$9(error))) return;
@@ -6757,11 +6768,11 @@ const walkRequest = async (args) => {
6757
6768
  cursor: state.cursor ?? "",
6758
6769
  checkpoint,
6759
6770
  newestId: state.newestId,
6760
- since: args.since ?? null,
6771
+ since: args.partialCursorSince === void 0 ? args.since ?? null : args.partialCursorSince,
6761
6772
  ...args.parentId === void 0 ? {} : { parentId: args.parentId }
6762
6773
  }),
6763
6774
  objectCount: upserted,
6764
- lastSynced: null
6775
+ lastSynced: Date.now()
6765
6776
  });
6766
6777
  partialCursorPersisted = true;
6767
6778
  return true;
@@ -6837,7 +6848,7 @@ const walkRequest = async (args) => {
6837
6848
  upserted += step.rows.length;
6838
6849
  ids.push(...step.ids);
6839
6850
  state = step.nextState;
6840
- persistPartialCursor();
6851
+ if (!(args.preservePartialCursorOnSince && step.done && step.stoppedReason === "since")) persistPartialCursor();
6841
6852
  await args.onPage?.({
6842
6853
  collectionKey: args.collection.key,
6843
6854
  pagesCompleted: pages,
@@ -6865,15 +6876,19 @@ const singleRequestCheckpoint = (args) => ({
6865
6876
  objectCount: args.result.upserted,
6866
6877
  lastSynced: Date.now()
6867
6878
  });
6868
- const parentIdsFromCache = (deps, collection) => {
6879
+ const parentIdsFromCache = (deps, collection, since) => {
6869
6880
  const table = collection.table ?? collection.key;
6870
6881
  const orderBy = collection.sinceField ? `${collection.sinceField} DESC, id` : "id";
6871
- return deps.cache.query(`SELECT id FROM ${table} ORDER BY ${orderBy}`).map((row) => String(row.id));
6882
+ const sinceFilter = since != null && collection.sinceField ? ` WHERE ${collection.sinceField} >= ${since}` : "";
6883
+ return deps.cache.query(`SELECT id FROM ${table}${sinceFilter} ORDER BY ${orderBy}`).map((row) => String(row.id));
6872
6884
  };
6873
6885
  const runSync = async (deps, opts) => {
6874
6886
  const { collection } = opts;
6875
6887
  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`);
6888
+ if (opts.since != null && collection.requiresFullInitialSync) {
6889
+ const storedState = deps.cache.getCursor(collection.key);
6890
+ if (storedState?.lastSynced == null || decodePartialSyncCursor(storedState.cursor)) throw new Error(`Collection ${collection.key} requires a full initial sync before --since`);
6891
+ }
6877
6892
  const credits = { spent: 0 };
6878
6893
  let pages = 0;
6879
6894
  let upserted = 0;
@@ -6899,13 +6914,14 @@ const runSync = async (deps, opts) => {
6899
6914
  const parentCollection = collectionByKey(collection.parentCollection);
6900
6915
  if (!parentCollection) throw new Error(`Missing parent collection ${collection.parentCollection}`);
6901
6916
  const storedPartialCursor = decodePartialSyncCursor(deps.cache.getCursor(collection.key)?.cursor);
6902
- let activePartialCursor = storedPartialCursor?.since === (opts.since ?? null) ? storedPartialCursor : void 0;
6917
+ let activePartialCursor = storedPartialCursor && partialCursorMatchesSince(storedPartialCursor, opts.since) ? storedPartialCursor : void 0;
6903
6918
  if (activePartialCursor?.phase !== "parent" && activePartialCursor?.phase !== "child") activePartialCursor = void 0;
6919
+ const resumedPartialCursor = activePartialCursor;
6904
6920
  const parentIds = [];
6905
6921
  const parentPersistsCheckpoint = parentCollection.requests.length === 1;
6906
6922
  let parentCheckpoint;
6907
6923
  if (activePartialCursor?.phase === "child") {
6908
- const cachedParentIds = parentIdsFromCache(deps, parentCollection);
6924
+ const cachedParentIds = parentIdsFromCache(deps, parentCollection, opts.since);
6909
6925
  if (activePartialCursor.parentId && cachedParentIds.includes(activePartialCursor.parentId)) parentIds.push(...cachedParentIds);
6910
6926
  else activePartialCursor = void 0;
6911
6927
  }
@@ -6922,6 +6938,8 @@ const runSync = async (deps, opts) => {
6922
6938
  collectIds: true,
6923
6939
  resumeCursor: activePartialCursor?.phase === "parent" && activePartialCursor.requestIndex === requestIndex ? activePartialCursor : void 0,
6924
6940
  allowPartialResume: true,
6941
+ partialCursorSince: activePartialCursor?.since,
6942
+ preservePartialCursorOnSince: activePartialCursor?.since !== (opts.since ?? null),
6925
6943
  partialCursorCollection: collection.key,
6926
6944
  partialCursorPhase: "parent",
6927
6945
  credits,
@@ -6946,7 +6964,7 @@ const runSync = async (deps, opts) => {
6946
6964
  lastSynced: Date.now()
6947
6965
  };
6948
6966
  if (parentCheckpoint) writePendingCursor(parentCheckpoint);
6949
- if (activePartialCursor?.phase === "parent") parentIds.splice(0, parentIds.length, ...parentIdsFromCache(deps, parentCollection));
6967
+ if (activePartialCursor?.phase === "parent") parentIds.splice(0, parentIds.length, ...parentIdsFromCache(deps, parentCollection, opts.since));
6950
6968
  }
6951
6969
  let reachedResumeParent = activePartialCursor?.phase !== "child";
6952
6970
  for (const parentId of parentIds) {
@@ -6965,6 +6983,8 @@ const runSync = async (deps, opts) => {
6965
6983
  useCheckpoint: false,
6966
6984
  resumeCursor: activePartialCursor?.phase === "child" && activePartialCursor.parentId === parentId && activePartialCursor.requestIndex === requestIndex ? activePartialCursor : void 0,
6967
6985
  allowPartialResume: true,
6986
+ partialCursorSince: activePartialCursor?.since,
6987
+ preservePartialCursorOnSince: activePartialCursor?.since !== (opts.since ?? null),
6968
6988
  partialCursorCollection: collection.key,
6969
6989
  partialCursorPhase: "child",
6970
6990
  parentId,
@@ -6980,7 +7000,7 @@ const runSync = async (deps, opts) => {
6980
7000
  if (activePartialCursor?.phase === "child" && activePartialCursor.parentId === parentId) activePartialCursor = void 0;
6981
7001
  }
6982
7002
  }
6983
- pendingCursors.push({
7003
+ if (!preservesBroaderPartialOnSince(resumedPartialCursor, opts.since, stoppedReason)) pendingCursors.push({
6984
7004
  collection: collection.key,
6985
7005
  cursor: null,
6986
7006
  objectCount: upserted,
@@ -6989,7 +7009,7 @@ const runSync = async (deps, opts) => {
6989
7009
  } else {
6990
7010
  const useCheckpoint = collection.requests.length === 1;
6991
7011
  const partialCursor = decodePartialSyncCursor(deps.cache.getCursor(collection.key)?.cursor ?? null);
6992
- const activePartialCursor = partialCursor && partialCursor.since === (opts.since ?? null) && partialCursor.requestIndex < collection.requests.length ? partialCursor : void 0;
7012
+ const activePartialCursor = partialCursor && partialCursorMatchesSince(partialCursor, opts.since) && partialCursor.requestIndex < collection.requests.length ? partialCursor : void 0;
6993
7013
  for (const [requestIndex, request] of collection.requests.entries()) {
6994
7014
  if (activePartialCursor && requestIndex < activePartialCursor.requestIndex) continue;
6995
7015
  const result = await walkRequest({
@@ -7001,6 +7021,8 @@ const runSync = async (deps, opts) => {
7001
7021
  useCheckpoint,
7002
7022
  resumeCursor: activePartialCursor?.requestIndex === requestIndex ? activePartialCursor : void 0,
7003
7023
  allowPartialResume: true,
7024
+ partialCursorSince: activePartialCursor?.since,
7025
+ preservePartialCursorOnSince: activePartialCursor?.since !== (opts.since ?? null),
7004
7026
  credits,
7005
7027
  pageDelayState,
7006
7028
  rateLimit: opts.rateLimit,
@@ -7010,12 +7032,12 @@ const runSync = async (deps, opts) => {
7010
7032
  onProgress: opts.onProgress
7011
7033
  });
7012
7034
  addResult(result);
7013
- if (useCheckpoint) pendingCursors.push(singleRequestCheckpoint({
7035
+ if (!preservesBroaderPartialOnSince(activePartialCursor, opts.since, result.stoppedReason) && useCheckpoint) pendingCursors.push(singleRequestCheckpoint({
7014
7036
  collection,
7015
7037
  result
7016
7038
  }));
7017
7039
  }
7018
- if (!useCheckpoint) pendingCursors.push({
7040
+ if (!(useCheckpoint || preservesBroaderPartialOnSince(activePartialCursor, opts.since, stoppedReason))) pendingCursors.push({
7019
7041
  collection: collection.key,
7020
7042
  cursor: null,
7021
7043
  objectCount: upserted,
@@ -7528,6 +7550,30 @@ const { costBPSForCategory: costBPSForCategory$1 } = createPricing([
7528
7550
  "unitPriceUSD": .015,
7529
7551
  "featureId": "user_interaction_create_request"
7530
7552
  },
7553
+ {
7554
+ "key": "page_analytics_read",
7555
+ "label": "Page Analytics: Read",
7556
+ "description": "Read LinkedIn company Page analytics through the raw LinkedIn passthrough.",
7557
+ "unit": "per_resource",
7558
+ "unitPriceUSD": .02,
7559
+ "featureId": "user_read_resource"
7560
+ },
7561
+ {
7562
+ "key": "page_invite_create",
7563
+ "label": "Page Invite: Create",
7564
+ "description": "Invite LinkedIn users to follow a company Page.",
7565
+ "unit": "per_request",
7566
+ "unitPriceUSD": .015,
7567
+ "featureId": "user_interaction_create_request"
7568
+ },
7569
+ {
7570
+ "key": "proxy_passthrough",
7571
+ "label": "Proxy Passthrough",
7572
+ "description": "Forward a raw LinkedIn passthrough envelope with conservative write-tier pricing.",
7573
+ "unit": "per_request",
7574
+ "unitPriceUSD": .2,
7575
+ "featureId": "content_create_with_url_request"
7576
+ },
7531
7577
  {
7532
7578
  "key": "following_read",
7533
7579
  "label": "Following/Followers: Read",
@@ -10450,6 +10496,54 @@ const LINKEDIN_OPERATIONS = [
10450
10496
  }
10451
10497
  ]
10452
10498
  },
10499
+ {
10500
+ schemaBaseName: "PageVisitors",
10501
+ hasBody: false,
10502
+ config: {
10503
+ command: "visitors",
10504
+ group: "page",
10505
+ root: false,
10506
+ summary: "Read company Page visitor analytics",
10507
+ paramLabels: { companyId: "company" },
10508
+ targetKind: "company"
10509
+ },
10510
+ endpoint: {
10511
+ method: "GET",
10512
+ path: "/page/:companyId/visitors",
10513
+ upstreamMethod: "POST",
10514
+ upstreamPath: "/v2/{account_id}/linkedin/",
10515
+ capability: "read",
10516
+ mutates: false,
10517
+ pagination: { style: "none" },
10518
+ costBPS: 200,
10519
+ billingCategory: "page_analytics_read",
10520
+ kind: "page"
10521
+ },
10522
+ parameters: [
10523
+ {
10524
+ name: "since",
10525
+ in: "query",
10526
+ required: false,
10527
+ kind: "string",
10528
+ description: "Start of the analytics window as an ISO timestamp"
10529
+ },
10530
+ {
10531
+ name: "until",
10532
+ in: "query",
10533
+ required: false,
10534
+ kind: "string",
10535
+ description: "End of the analytics window as an ISO timestamp"
10536
+ },
10537
+ {
10538
+ name: "companyId",
10539
+ in: "path",
10540
+ required: true,
10541
+ kind: "string",
10542
+ valueHint: "company",
10543
+ description: "Company Page selector: company_id:ID, a company URL, or a vanity; supplied by --page or config"
10544
+ }
10545
+ ]
10546
+ },
10453
10547
  {
10454
10548
  schemaBaseName: "PostsComments",
10455
10549
  hasBody: false,
@@ -10892,7 +10986,7 @@ const LINKEDIN_OPERATIONS = [
10892
10986
  summary: "List a user's LinkedIn connections",
10893
10987
  itemUrlTemplate: "https://www.linkedin.com/in/{id}",
10894
10988
  paramLabels: { user_id: "target" },
10895
- positionalQueryParams: ["user_id"],
10989
+ optionalPositionalQueryParams: ["user_id"],
10896
10990
  outputGuardMessage: "Unexpected LinkedIn connections response. Expected a relations list.",
10897
10991
  resolveResourceParams: ["user_id"],
10898
10992
  targetKind: "profile"
@@ -10932,10 +11026,10 @@ const LINKEDIN_OPERATIONS = [
10932
11026
  {
10933
11027
  name: "user_id",
10934
11028
  in: "query",
10935
- required: true,
11029
+ required: false,
10936
11030
  kind: "string",
10937
11031
  valueHint: "target",
10938
- description: "LinkedIn profile target: @public-identifier, profile_id:<id>, a LinkedIn profile URL, or a profile URN. Required for live reads; use `social linkedin sync connections` plus `social linkedin sql` for your own graph."
11032
+ description: "Optional profile target: @public-identifier, profile_id:<id>, a LinkedIn profile URL, or a profile URN. Omit for account-level connections"
10939
11033
  },
10940
11034
  {
10941
11035
  name: "filter",
@@ -11061,6 +11155,44 @@ const LINKEDIN_OPERATIONS = [
11061
11155
  description: "Conversation target: chat_id:<id> or a LinkedIn messaging thread URL"
11062
11156
  }]
11063
11157
  },
11158
+ {
11159
+ schemaBaseName: "PageInvite",
11160
+ hasBody: true,
11161
+ config: {
11162
+ command: "invite",
11163
+ group: "page",
11164
+ root: false,
11165
+ summary: "Invite users to follow a company Page",
11166
+ bodyParams: { invitees: {
11167
+ kind: "array",
11168
+ positional: true,
11169
+ required: true,
11170
+ valueHint: "user",
11171
+ description: "Users to invite: @username, profile_id:<id>, profile URL, or profile URN"
11172
+ } },
11173
+ paramLabels: { companyId: "company" },
11174
+ targetKind: "company"
11175
+ },
11176
+ endpoint: {
11177
+ method: "POST",
11178
+ path: "/page/:companyId/invite",
11179
+ upstreamPath: "/v2/{account_id}/linkedin/",
11180
+ capability: "write",
11181
+ mutates: true,
11182
+ pagination: { style: "none" },
11183
+ costBPS: 150,
11184
+ billingCategory: "page_invite_create",
11185
+ kind: "page"
11186
+ },
11187
+ parameters: [{
11188
+ name: "companyId",
11189
+ in: "path",
11190
+ required: true,
11191
+ kind: "string",
11192
+ valueHint: "company",
11193
+ description: "Company Page selector: company_id:ID, a company URL, or a vanity; supplied by --page or config"
11194
+ }]
11195
+ },
11064
11196
  {
11065
11197
  schemaBaseName: "PostsPost",
11066
11198
  hasBody: true,
@@ -11173,6 +11305,29 @@ const LINKEDIN_OPERATIONS = [
11173
11305
  },
11174
11306
  parameters: []
11175
11307
  },
11308
+ {
11309
+ schemaBaseName: "LinkedinProxy",
11310
+ hasBody: true,
11311
+ config: {
11312
+ command: "proxy",
11313
+ group: "linkedin",
11314
+ root: true,
11315
+ summary: "Forward a raw request to LinkedIn (advanced escape hatch)",
11316
+ bodyTextArg: "body"
11317
+ },
11318
+ endpoint: {
11319
+ method: "POST",
11320
+ path: "/proxy",
11321
+ upstreamPath: "/v2/{account_id}/linkedin/",
11322
+ capability: "write",
11323
+ mutates: true,
11324
+ pagination: { style: "none" },
11325
+ costBPS: 2e3,
11326
+ billingCategory: "proxy_passthrough",
11327
+ kind: "proxy"
11328
+ },
11329
+ parameters: []
11330
+ },
11176
11331
  {
11177
11332
  schemaBaseName: "RequestsSend",
11178
11333
  hasBody: true,
@@ -11429,6 +11584,12 @@ const CompaniesJobsOutput = object({
11429
11584
  total_count: number().optional(),
11430
11585
  next_cursor: string().optional()
11431
11586
  }).passthrough();
11587
+ const PageVisitorsInput = object({
11588
+ since: string().optional(),
11589
+ until: string().optional(),
11590
+ companyId: string().optional()
11591
+ });
11592
+ const PageVisitorsOutput = object({}).passthrough();
11432
11593
  const PostsCommentsInput = object({
11433
11594
  offset: union([string(), number()]).optional(),
11434
11595
  limit: union([string(), number()]).optional(),
@@ -11509,7 +11670,7 @@ const UserPostsOutput = object({
11509
11670
  const UserConnectionsInput = object({
11510
11671
  limit: union([string(), number()]).optional(),
11511
11672
  cursor: string().optional(),
11512
- user_id: string(),
11673
+ user_id: string().optional(),
11513
11674
  filter: string().optional()
11514
11675
  });
11515
11676
  const UserConnectionsOutput = object({
@@ -11558,6 +11719,16 @@ const MessagesMessageOutput = object({
11558
11719
  unknown()
11559
11720
  ])
11560
11721
  }).passthrough();
11722
+ const PageInviteInput = object({
11723
+ companyId: string().optional(),
11724
+ body: string().optional(),
11725
+ invitees: union([array(string()), string()]).optional()
11726
+ });
11727
+ const PageInviteContractInput = PageInviteInput.extend({
11728
+ body: object({ invitees: array(string()) }).passthrough().optional(),
11729
+ invitees: union([array(string()), string()]).optional()
11730
+ });
11731
+ const PageInviteOutput = object({}).passthrough();
11561
11732
  const PostsPostInput = object({
11562
11733
  body: string().optional(),
11563
11734
  text: string().optional(),
@@ -11640,6 +11811,9 @@ const PostsReactContractInput = PostsReactInput.extend({
11640
11811
  reaction_type: string().optional()
11641
11812
  });
11642
11813
  const PostsReactOutput = object({ object: _enum(["PostReactionAdded"]) }).passthrough();
11814
+ const LinkedinProxyInput = object({ body: string().optional() });
11815
+ const LinkedinProxyContractInput = LinkedinProxyInput.extend({ body: object({}).passthrough().optional() });
11816
+ const LinkedinProxyOutput = object({}).passthrough();
11643
11817
  const RequestsSendInput = object({
11644
11818
  id: string(),
11645
11819
  body: string().optional(),
@@ -11654,6 +11828,8 @@ const schemas$1 = {
11654
11828
  CompaniesCompanyOutput,
11655
11829
  CompaniesJobsInput,
11656
11830
  CompaniesJobsOutput,
11831
+ PageVisitorsInput,
11832
+ PageVisitorsOutput,
11657
11833
  PostsCommentsInput,
11658
11834
  PostsCommentsOutput,
11659
11835
  PostsReactionsInput,
@@ -11679,6 +11855,9 @@ const schemas$1 = {
11679
11855
  MessagesMessageInput,
11680
11856
  MessagesMessageContractInput,
11681
11857
  MessagesMessageOutput,
11858
+ PageInviteInput,
11859
+ PageInviteContractInput,
11860
+ PageInviteOutput,
11682
11861
  PostsPostInput,
11683
11862
  PostsPostContractInput,
11684
11863
  PostsPostOutput,
@@ -11688,6 +11867,9 @@ const schemas$1 = {
11688
11867
  PostsReactInput,
11689
11868
  PostsReactContractInput,
11690
11869
  PostsReactOutput,
11870
+ LinkedinProxyInput,
11871
+ LinkedinProxyContractInput,
11872
+ LinkedinProxyOutput,
11691
11873
  RequestsSendInput,
11692
11874
  RequestsSendContractInput: RequestsSendInput.extend({
11693
11875
  body: object({
@@ -11801,6 +11983,12 @@ const targetKindsFor = (op) => {
11801
11983
  if (kinds === void 0) throw new Error(`Missing targetKind for ${commandPathFor(op)}.`);
11802
11984
  return Array.isArray(kinds) ? kinds : [kinds];
11803
11985
  };
11986
+ const isSeededPageCompanyParam = (op, param) => op.config.group === "page" && param.in === "path" && param.name === "companyId";
11987
+ const pageParametersForRuntimeInput = (op) => op.config.group === "page" ? op.parameters.filter((param) => !isSeededPageCompanyParam(op, param)) : op.parameters;
11988
+ const pageSelectorForTargetResolution = (page) => {
11989
+ if (NumericCompanyId.safeParse(page).success || !(page.startsWith("company_id:") || page.startsWith("http") || page.startsWith("urn:"))) return `company_id:${page}`;
11990
+ return page;
11991
+ };
11804
11992
  const positionalDisplayKeyFor = (op, param) => op.config.paramLabels?.[param.name] ?? flagName(param.name);
11805
11993
  const idempotencyFor$1 = (op) => {
11806
11994
  if (op.endpoint.capability === "read") return {
@@ -11850,7 +12038,7 @@ const exampleCommandFor$1 = (op, root) => {
11850
12038
  op.config.command
11851
12039
  ];
11852
12040
  for (const param of op.parameters) {
11853
- if (!(param.in === "path" || positionalQuery.has(param.name)) || op.config.ownAccountPathParams?.[param.name] === "optional") continue;
12041
+ if (!(param.in === "path" || positionalQuery.has(param.name)) || op.config.ownAccountPathParams?.[param.name] === "optional" || isSeededPageCompanyParam(op, param)) continue;
11854
12042
  const hint = `<${positionalDisplayKeyFor(op, param)}>`;
11855
12043
  parts.push(param.in === "path" || param.kind !== "array" ? hint : `${hint}...`);
11856
12044
  }
@@ -11944,10 +12132,21 @@ const linkedinRuntimeAdapter = {
11944
12132
  includeIdField: true,
11945
12133
  maximumItemsHintFor: maximumItemsHintFor$1,
11946
12134
  queryArrayMode: (op, param) => param.kind === "array" || op.schemaBaseName === "UserProfile" && param.name === "with_sections" ? "repeat" : "set",
12135
+ parametersForArgs: pageParametersForRuntimeInput,
12136
+ parametersForInput: pageParametersForRuntimeInput,
11947
12137
  setUnexposedFieldPresets: true,
11948
12138
  signaturePartFor: signaturePartFor$1,
11949
12139
  stdinBodyTextRequired: (op) => op.config.bodyTextArg !== void 0 && op.config.bodyTextRequired !== false,
11950
12140
  targetKinds: targetKindsFor,
12141
+ seedPathValue: async ({ op, param, args, deps }) => {
12142
+ if (op.config.group !== "page" || param.name !== "companyId") return;
12143
+ const page = typeof args.page === "string" ? args.page : void 0;
12144
+ return pageSelectorForTargetResolution(await deps.resolveLinkedinPage({ page }));
12145
+ },
12146
+ extraArgsFor: (op) => op.config.group === "page" ? { page: {
12147
+ type: "string",
12148
+ description: "Company Page selector: company_id:<id>, a company URL, or a vanity; overrides the configured default Page"
12149
+ } } : void 0,
11951
12150
  useGETMaxUsageUnitsEstimate: true,
11952
12151
  transformPathResolution: async ({ args, deps, op, param, rawArgs, resolution, resolveSelectedAccount }) => {
11953
12152
  if ((op.schemaBaseName === "PostsComments" || op.schemaBaseName === "PostsReactions") && param.name === "post_id") {
@@ -11992,6 +12191,26 @@ const linkedinRuntimeAdapter = {
11992
12191
  }]
11993
12192
  };
11994
12193
  }
12194
+ if (op.config.group === "page" && param.name === "companyId" && !NumericCompanyId.safeParse(resolution.id).success) {
12195
+ const companyAccount = await resolveSelectedAccount();
12196
+ const company = await deps.api.get(`linkedin/companies/${encodeURIComponent(resolution.id)}`, { headers: {
12197
+ ...await deps.commandHeaders({
12198
+ headers: args.header,
12199
+ rawArgs
12200
+ }),
12201
+ ...companyAccount.selector === void 0 ? {} : { "x-social-account-id": companyAccount.selector }
12202
+ } }).json();
12203
+ const companyId = NumericCompanyId.safeParse(companyProfileIdFrom(company));
12204
+ if (!companyId.success) throw new Error(`Could not resolve LinkedIn company ID for ${resolution.id}.`);
12205
+ return {
12206
+ pathValue: companyId.data,
12207
+ records: [{
12208
+ ...resolution.record,
12209
+ id: companyId.data,
12210
+ source: "lookup"
12211
+ }]
12212
+ };
12213
+ }
11995
12214
  },
11996
12215
  afterQueryParams: ({ op, params, pathResolutions }) => {
11997
12216
  if (op.schemaBaseName === "UserPosts" && pathResolutions.identifier?.kind === "company") params.set("is_company", "true");
@@ -12045,6 +12264,20 @@ const linkedinRuntimeAdapter = {
12045
12264
  });
12046
12265
  input.post_as = companyId.data;
12047
12266
  }
12267
+ if (op.schemaBaseName === "PageInvite" && input.invitees !== void 0) {
12268
+ const invitees = Array.isArray(input.invitees) ? input.invitees : [input.invitees];
12269
+ const profileIds = [];
12270
+ for (const invitee of invitees) {
12271
+ const resolution = await deps.resolveTarget("invitee", invitee, {
12272
+ platform: "linkedin",
12273
+ account,
12274
+ expect: ["profile"]
12275
+ });
12276
+ resolved.push(resolution.record);
12277
+ profileIds.push(resolution.id);
12278
+ }
12279
+ input.invitees = profileIds;
12280
+ }
12048
12281
  },
12049
12282
  transformOutput: ({ body, data, op }) => {
12050
12283
  const outputData = dataWithAuditableWriteTarget(op, data, body);
@@ -12514,7 +12747,7 @@ const buildSync$1 = (deps, platform) => defineCommand({
12514
12747
  const buildSql$1 = (deps, platform) => defineCommand({
12515
12748
  meta: {
12516
12749
  name: "sql",
12517
- description: "Query your synced local mirror with read-only SQL (free; run with no query to print tables)",
12750
+ description: "Query your local mirror with read-only SQL (free; run with no query to print tables)",
12518
12751
  capability: "read",
12519
12752
  mutates: false,
12520
12753
  schemaContract: {
@@ -12603,7 +12836,6 @@ const buildSql$1 = (deps, platform) => defineCommand({
12603
12836
  }
12604
12837
  });
12605
12838
  } catch (error) {
12606
- if (error instanceof NeverSyncedError) throw new UsageError(incompleteSyncMessageFor(readonlyCache, error));
12607
12839
  throw new UsageError(`Query failed (cache is read-only): ${error.message}`);
12608
12840
  }
12609
12841
  } finally {
@@ -12915,12 +13147,6 @@ const sentMessageRow = (args) => {
12915
13147
  raw: JSON.stringify(raw)
12916
13148
  };
12917
13149
  };
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
13150
  const buildMessageDelete = (deps) => {
12925
13151
  return defineCommand({
12926
13152
  meta: commandMeta$2({
@@ -13583,6 +13809,7 @@ createLinkedinCommands({
13583
13809
  printResult: unconfigured$1,
13584
13810
  resolveAccount: unconfigured$1,
13585
13811
  resolveAccountId: unconfigured$1,
13812
+ resolveLinkedinPage: unconfigured$1,
13586
13813
  resolveOwnLinkedinAccount: unconfigured$1
13587
13814
  });
13588
13815
  //#endregion
@@ -13882,6 +14109,7 @@ const xProfileExtractors = {
13882
14109
  "GET /users/:id": extractProfile,
13883
14110
  "GET /users/:id/followers": extractList,
13884
14111
  "GET /users/:id/following": extractList,
14112
+ "GET /users/:id/timelines/reverse_chronological": extractIncludesUsers,
13885
14113
  "GET /users/by/username/:username": extractProfile
13886
14114
  };
13887
14115
  const X_OPERATIONS = [
@@ -16636,11 +16864,10 @@ const defineOperationCommand = (op, deps) => defineRuntimeOperationCommand(xRunt
16636
16864
  //#endregion
16637
16865
  //#region ../../packages/x/src/cli/commands.ts
16638
16866
  const hiddenRootCommands = new Set(["search"]);
16639
- const suppressedRootCommands = new Set(["bookmarks"]);
16867
+ const suppressedRootCommands = new Set(["bookmarks", "timeline"]);
16640
16868
  const rootCommandOrder = [
16641
16869
  "profile",
16642
16870
  "tweets",
16643
- "timeline",
16644
16871
  "mentions",
16645
16872
  "liked",
16646
16873
  "followers",
@@ -16942,7 +17169,7 @@ const buildSql = (deps, platform) => {
16942
17169
  return defineCommand({
16943
17170
  meta: commandMeta$1({
16944
17171
  name: "sql",
16945
- description: "Query your synced local mirror with read-only SQL (free; run with no query to print tables)",
17172
+ description: "Query your local mirror with read-only SQL (free; run with no query to print tables)",
16946
17173
  method: "GET",
16947
17174
  capability: "read",
16948
17175
  mutates: false,
@@ -17000,7 +17227,6 @@ const buildSql = (deps, platform) => {
17000
17227
  }
17001
17228
  });
17002
17229
  } catch (error) {
17003
- if (error instanceof NeverSyncedError) throw new UsageError(error.message);
17004
17230
  throw new UsageError(`Query failed (cache is read-only): ${error.message}`);
17005
17231
  }
17006
17232
  } finally {
@@ -21173,7 +21399,7 @@ function createEnv(opts) {
21173
21399
  }
21174
21400
  //#endregion
21175
21401
  //#region package.json
21176
- var version$1 = "0.9.0";
21402
+ var version$1 = "0.10.1";
21177
21403
  //#endregion
21178
21404
  //#region src/lib/env.ts
21179
21405
  const URLWithTrailingSlash = url().transform(ensureTrailingSlash);
@@ -21587,8 +21813,17 @@ const authWhoamiOutput = async (client, apiURL = env.SOCIAL_API_URL) => {
21587
21813
  }
21588
21814
  };
21589
21815
  const MAX_CACHE_TTL_SECONDS = 10080 * 60;
21590
- const CLIConfig = object({ cache: object({ cacheTTLSeconds: number().int().min(0).max(MAX_CACHE_TTL_SECONDS).default(900) }).default({ cacheTTLSeconds: 900 }) });
21591
- const defaultCLIConfig = () => ({ cache: { cacheTTLSeconds: 900 } });
21816
+ const CLIConfig = object({
21817
+ cache: object({ cacheTTLSeconds: number().int().min(0).max(MAX_CACHE_TTL_SECONDS).default(900) }).default({ cacheTTLSeconds: 900 }),
21818
+ defaults: object({
21819
+ account: string().optional(),
21820
+ linkedinPage: string().optional()
21821
+ }).default({})
21822
+ });
21823
+ const defaultCLIConfig = () => ({
21824
+ cache: { cacheTTLSeconds: 900 },
21825
+ defaults: {}
21826
+ });
21592
21827
  const configPath = () => join(process.env.HOME ?? homedir(), ".social", "config.json");
21593
21828
  const parseCLIConfig = (serialized) => CLIConfig.parse(JSON.parse(serialized));
21594
21829
  const readCLIConfig = async () => {
@@ -21614,6 +21849,30 @@ const setCacheTTLSeconds = async (cacheTTLSeconds) => {
21614
21849
  await writeCLIConfig(next);
21615
21850
  return next;
21616
21851
  };
21852
+ const setDefaultAccount = async (selector) => {
21853
+ const config = await readCLIConfig();
21854
+ const next = {
21855
+ ...config,
21856
+ defaults: {
21857
+ ...config.defaults,
21858
+ account: selector
21859
+ }
21860
+ };
21861
+ await writeCLIConfig(next);
21862
+ return next;
21863
+ };
21864
+ const setDefaultLinkedinPage = async (companyId) => {
21865
+ const config = await readCLIConfig();
21866
+ const next = {
21867
+ ...config,
21868
+ defaults: {
21869
+ ...config.defaults,
21870
+ linkedinPage: companyId
21871
+ }
21872
+ };
21873
+ await writeCLIConfig(next);
21874
+ return next;
21875
+ };
21617
21876
  const cacheControlHeaderFromConfig = async () => `max-age=${(await readCLIConfig()).cache.cacheTTLSeconds}`;
21618
21877
  //#endregion
21619
21878
  //#region src/lib/output.ts
@@ -22123,24 +22382,70 @@ const commandMeta = (meta) => {
22123
22382
  };
22124
22383
  };
22125
22384
  const CacheStateOutput = object({ cacheTTLSeconds: number().int().nonnegative() });
22385
+ const AccountDefaultOutput = object({ account: string().nullable() });
22386
+ const PageDefaultOutput = object({ linkedinPage: string().nullable() });
22126
22387
  const cacheStateFrom = (config) => ({ cacheTTLSeconds: config.cache.cacheTTLSeconds });
22127
22388
  const printCacheState = (config) => {
22128
22389
  printData(cacheStateFrom(config));
22129
22390
  };
22391
+ const printDefaultAccount = (config) => {
22392
+ printData({ account: config.defaults.account ?? null });
22393
+ };
22394
+ const printDefaultLinkedinPage = (config) => {
22395
+ printData({ linkedinPage: config.defaults.linkedinPage ?? null });
22396
+ };
22397
+ const cacheCommand = defineCommand({
22398
+ meta: {
22399
+ name: "cache",
22400
+ description: "Read or configure proxy cache defaults."
22401
+ },
22402
+ subCommands: { ttl: defineCommand({
22403
+ meta: commandMeta({
22404
+ name: "ttl",
22405
+ description: "Read or set the default cache TTL in seconds.",
22406
+ contract: {
22407
+ capability: "none",
22408
+ auth: {
22409
+ local: true,
22410
+ required: false
22411
+ },
22412
+ mutates: true,
22413
+ outputSchema: CacheStateOutput,
22414
+ idempotency: "idempotent",
22415
+ responseShaping: { formats: ["json"] },
22416
+ hazards: [{
22417
+ code: "local_config_write",
22418
+ message: "Writes local CLI configuration when ttl_seconds is provided."
22419
+ }]
22420
+ }
22421
+ }),
22422
+ args: { ttl_seconds: {
22423
+ type: "positional",
22424
+ required: false,
22425
+ description: "Cache TTL in seconds."
22426
+ } },
22427
+ run: async ({ args }) => {
22428
+ if (args.ttl_seconds === void 0) {
22429
+ printCacheState(await readCLIConfig());
22430
+ return;
22431
+ }
22432
+ printCacheState(await setCacheTTLSeconds(parseIntegerString("--ttl-seconds", args.ttl_seconds, {
22433
+ min: 0,
22434
+ max: MAX_CACHE_TTL_SECONDS
22435
+ })));
22436
+ }
22437
+ }) }
22438
+ });
22130
22439
  const configCommand = defineCommand({
22131
22440
  meta: {
22132
22441
  name: "config",
22133
22442
  description: "Read or configure local social CLI settings."
22134
22443
  },
22135
- subCommands: { cache: defineCommand({
22136
- meta: {
22137
- name: "cache",
22138
- description: "Read or configure proxy cache defaults."
22139
- },
22140
- subCommands: { ttl: defineCommand({
22444
+ subCommands: {
22445
+ account: defineCommand({
22141
22446
  meta: commandMeta({
22142
- name: "ttl",
22143
- description: "Read or set the default cache TTL in seconds.",
22447
+ name: "account",
22448
+ description: "Read or set the default connected account selector.",
22144
22449
  contract: {
22145
22450
  capability: "none",
22146
22451
  auth: {
@@ -22148,32 +22453,63 @@ const configCommand = defineCommand({
22148
22453
  required: false
22149
22454
  },
22150
22455
  mutates: true,
22151
- outputSchema: CacheStateOutput,
22456
+ outputSchema: AccountDefaultOutput,
22152
22457
  idempotency: "idempotent",
22153
22458
  responseShaping: { formats: ["json"] },
22154
22459
  hazards: [{
22155
22460
  code: "local_config_write",
22156
- message: "Writes local CLI configuration when ttl_seconds is provided."
22461
+ message: "Writes local CLI configuration when selector is provided."
22157
22462
  }]
22158
22463
  }
22159
22464
  }),
22160
- args: { ttl_seconds: {
22465
+ args: { selector: {
22161
22466
  type: "positional",
22162
22467
  required: false,
22163
- description: "Cache TTL in seconds."
22468
+ description: "Default account selector."
22164
22469
  } },
22165
22470
  run: async ({ args }) => {
22166
- if (args.ttl_seconds === void 0) {
22167
- printCacheState(await readCLIConfig());
22471
+ if (args.selector === void 0) {
22472
+ printDefaultAccount(await readCLIConfig());
22168
22473
  return;
22169
22474
  }
22170
- printCacheState(await setCacheTTLSeconds(parseIntegerString("--ttl-seconds", args.ttl_seconds, {
22171
- min: 0,
22172
- max: MAX_CACHE_TTL_SECONDS
22173
- })));
22475
+ printDefaultAccount(await setDefaultAccount(String(args.selector)));
22174
22476
  }
22175
- }) }
22176
- }) }
22477
+ }),
22478
+ cache: cacheCommand,
22479
+ page: defineCommand({
22480
+ meta: commandMeta({
22481
+ name: "page",
22482
+ description: "Read or set the default LinkedIn company Page selector.",
22483
+ contract: {
22484
+ capability: "none",
22485
+ auth: {
22486
+ local: true,
22487
+ required: false
22488
+ },
22489
+ mutates: true,
22490
+ outputSchema: PageDefaultOutput,
22491
+ idempotency: "idempotent",
22492
+ responseShaping: { formats: ["json"] },
22493
+ hazards: [{
22494
+ code: "local_config_write",
22495
+ message: "Writes local CLI configuration when company_id is provided."
22496
+ }]
22497
+ }
22498
+ }),
22499
+ args: { company_id: {
22500
+ type: "positional",
22501
+ required: false,
22502
+ description: "Default LinkedIn Page selector."
22503
+ } },
22504
+ run: async ({ args }) => {
22505
+ if (args.company_id === void 0) {
22506
+ printDefaultLinkedinPage(await readCLIConfig());
22507
+ return;
22508
+ }
22509
+ printDefaultLinkedinPage(await setDefaultLinkedinPage(String(args.company_id)));
22510
+ }
22511
+ })
22512
+ }
22177
22513
  });
22178
22514
  //#endregion
22179
22515
  //#region src/billing/index.ts
@@ -34209,17 +34545,20 @@ const createCLIDeps = () => {
34209
34545
  }
34210
34546
  };
34211
34547
  const resolveAccount = async ({ account, platform }) => {
34212
- if (account) resolveAccountId({ account });
34548
+ const configDefault = (await readCLIConfig()).defaults.account;
34549
+ const selectorToTry = account ?? configDefault;
34550
+ if (selectorToTry) resolveAccountId({ account: selectorToTry });
34213
34551
  const accounts = await client.accounts.list({
34214
34552
  platform,
34215
34553
  includeDisconnected: false
34216
34554
  });
34217
- const selected = account ? accounts.find((candidate) => accountMatchesIdentifier(candidate, account)) : accounts[0];
34218
- if (account && !selected) throw new NotFoundError("account_not_found");
34555
+ const matched = selectorToTry ? accounts.find((candidate) => accountMatchesIdentifier(candidate, selectorToTry)) : void 0;
34556
+ if (account && !matched) throw new NotFoundError("account_not_found");
34557
+ const selected = matched ?? accounts[0];
34219
34558
  return {
34220
34559
  platform,
34221
- username: selected?.username ?? (account?.startsWith("@") ? account.slice(1) : account ?? "default"),
34222
- selector: selected ? accountSelectorFor(selected) : account
34560
+ username: selected?.username ?? (selectorToTry?.startsWith("@") ? selectorToTry.slice(1) : selectorToTry ?? "default"),
34561
+ selector: selected ? accountSelectorFor(selected) : selectorToTry
34223
34562
  };
34224
34563
  };
34225
34564
  const lookupXHandle = async (input, account) => {
@@ -34320,13 +34659,23 @@ const createCLIDeps = () => {
34320
34659
  printResult: printResultWithUsage,
34321
34660
  resolveAccount,
34322
34661
  resolveAccountId: async (args) => resolveAccountId(args),
34662
+ resolveLinkedinPage: async ({ page }) => {
34663
+ const selectedPage = page ?? (await readCLIConfig()).defaults.linkedinPage;
34664
+ if (!selectedPage) throw new UsageError("No Page selected — pass --page or run `social config page <id>`");
34665
+ return selectedPage;
34666
+ },
34323
34667
  resolveOwnXAccount: async ({ account }) => {
34668
+ const configDefault = (await readCLIConfig()).defaults.account;
34669
+ const selectorToTry = account ?? configDefault;
34670
+ if (selectorToTry) resolveAccountId({ account: selectorToTry });
34324
34671
  const accounts = await client.accounts.list({
34325
34672
  platform: "x",
34326
34673
  includeDisconnected: false
34327
34674
  });
34328
- const selected = account ? accounts.find((candidate) => accountMatchesIdentifier(candidate, account)) : accounts[0];
34329
- if (!selected) throw account ? new NotFoundError("x_account_not_found") : /* @__PURE__ */ new Error("x_account_not_connected");
34675
+ const matched = selectorToTry ? accounts.find((candidate) => accountMatchesIdentifier(candidate, selectorToTry)) : void 0;
34676
+ if (account && !matched) throw new NotFoundError("x_account_not_found");
34677
+ const selected = matched ?? accounts[0];
34678
+ if (!selected) throw new Error("x_account_not_connected");
34330
34679
  return {
34331
34680
  account: {
34332
34681
  platform: "x",
@@ -34338,14 +34687,18 @@ const createCLIDeps = () => {
34338
34687
  };
34339
34688
  },
34340
34689
  resolveOwnLinkedinAccount: async ({ account }) => {
34341
- if (account) resolveAccountId({ account });
34690
+ const configDefault = (await readCLIConfig()).defaults.account;
34691
+ const selectorToTry = account ?? configDefault;
34692
+ if (selectorToTry) resolveAccountId({ account: selectorToTry });
34342
34693
  const accounts = await client.accounts.list({
34343
34694
  platform: "linkedin",
34344
34695
  includeDisconnected: false
34345
34696
  });
34346
- const selected = account ? accounts.find((candidate) => accountMatchesIdentifier(candidate, account)) : accounts[0];
34347
- if (!selected) throw account ? new NotFoundError("linkedin_account_not_found") : /* @__PURE__ */ new Error("linkedin_account_not_connected");
34348
- const profile = await lookupLinkedinProfile("me", account, {
34697
+ const matched = selectorToTry ? accounts.find((candidate) => accountMatchesIdentifier(candidate, selectorToTry)) : void 0;
34698
+ if (account && !matched) throw new NotFoundError("linkedin_account_not_found");
34699
+ const selected = matched ?? accounts[0];
34700
+ if (!selected) throw new Error("linkedin_account_not_connected");
34701
+ const profile = await lookupLinkedinProfile("me", selectorToTry, {
34349
34702
  platform: "linkedin",
34350
34703
  username: selected.username,
34351
34704
  selector: accountSelectorFor(selected)