@usesocial/cli 0.7.0 → 0.7.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/CHANGELOG.md +7 -0
- package/README.md +11 -7
- package/dist/index.mjs +103 -46
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @usesocial/cli
|
|
2
2
|
|
|
3
|
+
## 0.7.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#32](https://github.com/usesocial/monorepo/pull/32) [`b2ca106`](https://github.com/usesocial/monorepo/commit/b2ca106f970bfe73d776f3f10225df7c6c7a1b80) Thanks [@CyrusNuevoDia](https://github.com/CyrusNuevoDia)! - Use cursor pagination for `social linkedin posts` so the CLI matches the LinkedIn
|
|
8
|
+
posts provider contract.
|
|
9
|
+
|
|
3
10
|
## 0.7.0
|
|
4
11
|
|
|
5
12
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -115,7 +115,7 @@ profile lookup is needed.
|
|
|
115
115
|
| `disconnect x <account>` | Disconnect an X account. | write |
|
|
116
116
|
| `billing` | Show seats, subscription, and current usage billing. | |
|
|
117
117
|
| `billing portal` | Open the hosted billing portal and print its URL. | |
|
|
118
|
-
| `usage` | Aggregate proxy
|
|
118
|
+
| `usage` | Aggregate proxy calls and credits. | |
|
|
119
119
|
| `logs` | Recent proxy calls. | |
|
|
120
120
|
| `config cache mode` | Read or set the live-read proxy cache mode. | |
|
|
121
121
|
| `config cache ttl` | Set a live-read proxy cache TTL in seconds. | |
|
|
@@ -125,7 +125,7 @@ profile lookup is needed.
|
|
|
125
125
|
| Command | Description | |
|
|
126
126
|
| --- | --- | --- |
|
|
127
127
|
| `profile [target]` | Fetch a profile; omit target for your own account. | |
|
|
128
|
-
| `posts <target>` | Live posts for a person or company; target required. | |
|
|
128
|
+
| `posts <target>` | Live posts for a person or company; target required. Supports `--limit` and `--cursor` from `.meta.cursor`. | |
|
|
129
129
|
| `comments <target>` | List a post's comments; supports `--limit` and `--offset`. | |
|
|
130
130
|
| `reactions <target>` | List a post's reactions; supports `--limit` and `--offset`. | |
|
|
131
131
|
| `connections [target]` | Live connection graph read; omit target for your own account. Supports `--limit` and `--cursor` from `.meta.cursor`. Costs credits. | |
|
|
@@ -175,8 +175,9 @@ profile lookup is needed.
|
|
|
175
175
|
## Built for agents
|
|
176
176
|
|
|
177
177
|
- Everything speaks JSON: success on stdout, structured errors on stderr.
|
|
178
|
-
-
|
|
179
|
-
lives under `.meta`.
|
|
178
|
+
- Platform reads return one envelope: lists use `.items[]`, single resources use
|
|
179
|
+
`.data`, metadata lives under `.meta`. `social account` service commands return
|
|
180
|
+
bare JSON; `account logs` returns `{ items, meta: { cursor } }`.
|
|
180
181
|
- Schema-backed planning: `social schema --list` is the compact runnable index.
|
|
181
182
|
- Local SQL is the free surface for your own synced data.
|
|
182
183
|
|
|
@@ -187,7 +188,7 @@ social linkedin sql "SELECT c.user_display_name, c.user_public_identifier, c.use
|
|
|
187
188
|
| jq '.items[]'
|
|
188
189
|
|
|
189
190
|
# Live fan-out: review recent posts, fan out to reactions, rank warm contacts.
|
|
190
|
-
social linkedin posts profile_id:<profile-id> --limit 25
|
|
191
|
+
social linkedin posts profile_id:<profile-id> --limit 25 > /tmp/recent-posts.json
|
|
191
192
|
jq -r '.items[].id' /tmp/recent-posts.json \
|
|
192
193
|
| xargs -I{} social linkedin reactions post_id:{} --limit 100 --offset 0 \
|
|
193
194
|
| jq -s '[ .[] | .items[] ]
|
|
@@ -267,7 +268,9 @@ social linkedin sql "SELECT user_display_name, user_public_identifier FROM li_re
|
|
|
267
268
|
|
|
268
269
|
# LinkedIn live reads.
|
|
269
270
|
social linkedin profile
|
|
270
|
-
social linkedin posts profile_id:<profile-id> --limit 25
|
|
271
|
+
PAGE1=$(social linkedin posts profile_id:<profile-id> --limit 25)
|
|
272
|
+
NEXT=$(echo "$PAGE1" | jq -r '.meta.cursor // empty')
|
|
273
|
+
[ -n "$NEXT" ] && social linkedin posts profile_id:<profile-id> --limit 25 --cursor "$NEXT"
|
|
271
274
|
social linkedin reactions post_id:<post-id> --limit 100 --offset 0
|
|
272
275
|
|
|
273
276
|
# X local mirror.
|
|
@@ -290,7 +293,8 @@ echo "Loved your launch notes." | social linkedin requests send profile_id:<prof
|
|
|
290
293
|
social account billing
|
|
291
294
|
social account billing portal
|
|
292
295
|
social account usage
|
|
293
|
-
social account logs --limit 20 --from 2026-05-01T00:00:00Z
|
|
296
|
+
social account logs --limit 20 --from 2026-05-01T00:00:00Z \
|
|
297
|
+
| jq -r '.items[] | [.createdAt, .platform, .method, .path, .responseStatus, .cacheStatus, .credits] | @tsv'
|
|
294
298
|
|
|
295
299
|
# Command schema.
|
|
296
300
|
social schema
|
package/dist/index.mjs
CHANGED
|
@@ -307,14 +307,67 @@ const referencedTablesForRead = (sql, platform) => {
|
|
|
307
307
|
};
|
|
308
308
|
const collectionForReadTable = (table) => table === "x_raw_messages" ? "x_messages" : table;
|
|
309
309
|
const collectionShortName = (collection) => collection.replace(/^(x|li)_/, "");
|
|
310
|
+
const tableFreshnessPolicies = {
|
|
311
|
+
x_raw_messages: {
|
|
312
|
+
kind: "collection",
|
|
313
|
+
collection: "x_messages"
|
|
314
|
+
},
|
|
315
|
+
li_conversations: {
|
|
316
|
+
kind: "collection",
|
|
317
|
+
collection: "li_conversations",
|
|
318
|
+
commandCollection: "li_messages"
|
|
319
|
+
},
|
|
320
|
+
li_profiles: {
|
|
321
|
+
kind: "platform",
|
|
322
|
+
platform: "linkedin"
|
|
323
|
+
},
|
|
324
|
+
x_profiles: {
|
|
325
|
+
kind: "platform",
|
|
326
|
+
platform: "x"
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
const freshnessPolicyForTable = (table) => tableFreshnessPolicies[table] ?? {
|
|
330
|
+
kind: "collection",
|
|
331
|
+
collection: collectionForReadTable(table)
|
|
332
|
+
};
|
|
333
|
+
const platformCollectionPrefix = (platform) => platform === "linkedin" ? "li_" : "x_";
|
|
334
|
+
const syncCommandForPolicy = (platform, policy) => {
|
|
335
|
+
if (policy.kind === "platform") return `social ${policy.platform} sync`;
|
|
336
|
+
return `social ${platform} sync ${collectionShortName(policy.commandCollection ?? policy.collection)}`;
|
|
337
|
+
};
|
|
338
|
+
const missingNameForPolicy = (table, policy) => policy.kind === "platform" ? table : policy.collection;
|
|
310
339
|
const formatList = (items) => {
|
|
311
340
|
if (items.length <= 1) return items[0] ?? "";
|
|
312
341
|
if (items.length === 2) return `${items[0]} and ${items[1]}`;
|
|
313
342
|
return `${items.slice(0, -1).join(", ")}, and ${items.at(-1)}`;
|
|
314
343
|
};
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
344
|
+
const collectionLastSynced = (db, collection) => {
|
|
345
|
+
return numberOrNull(db.prepare("SELECT last_synced FROM sync_state WHERE collection = :collection").get({ collection })?.last_synced);
|
|
346
|
+
};
|
|
347
|
+
const platformLastSynced = (db, platform) => {
|
|
348
|
+
return numberOrNull(db.prepare("SELECT MAX(last_synced) AS last_synced FROM sync_state WHERE collection GLOB :glob").get({ glob: `${platformCollectionPrefix(platform)}*` })?.last_synced);
|
|
349
|
+
};
|
|
350
|
+
const lastSyncedForPolicy = (db, policy) => policy.kind === "platform" ? platformLastSynced(db, policy.platform) : collectionLastSynced(db, policy.collection);
|
|
351
|
+
const missingFreshnessForTables = (tables) => {
|
|
352
|
+
const missing = [];
|
|
353
|
+
const seen = /* @__PURE__ */ new Set();
|
|
354
|
+
for (const table of tables) {
|
|
355
|
+
if (table.lastSynced != null) continue;
|
|
356
|
+
const key = `${table.missingName}\0${table.command}`;
|
|
357
|
+
if (!seen.has(key)) {
|
|
358
|
+
seen.add(key);
|
|
359
|
+
missing.push({
|
|
360
|
+
name: table.missingName,
|
|
361
|
+
command: table.command
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return missing;
|
|
366
|
+
};
|
|
367
|
+
const neverSyncedMessage = (missing) => {
|
|
368
|
+
const names = missing.map((item) => item.name);
|
|
369
|
+
const commands = missing.map((item) => `\`${item.command}\``);
|
|
370
|
+
return `No synced ${formatList(names)} yet — run ${formatList(commands)} first.`;
|
|
318
371
|
};
|
|
319
372
|
const assertKnownProfileTable = (table) => {
|
|
320
373
|
assertKnownTable(table);
|
|
@@ -432,15 +485,16 @@ const createCache = (path, db) => ({
|
|
|
432
485
|
read: (sql, opts) => {
|
|
433
486
|
assertPlatformQuery(sql, opts.platform);
|
|
434
487
|
const tables = referencedTablesForRead(sql, opts.platform).map((name) => {
|
|
435
|
-
const
|
|
488
|
+
const policy = freshnessPolicyForTable(name);
|
|
436
489
|
return {
|
|
437
490
|
name,
|
|
438
|
-
|
|
439
|
-
|
|
491
|
+
missingName: missingNameForPolicy(name, policy),
|
|
492
|
+
command: syncCommandForPolicy(opts.platform, policy),
|
|
493
|
+
lastSynced: lastSyncedForPolicy(db, policy)
|
|
440
494
|
};
|
|
441
495
|
});
|
|
442
|
-
const
|
|
443
|
-
if (
|
|
496
|
+
const missingFreshness = missingFreshnessForTables(tables);
|
|
497
|
+
if (missingFreshness.length > 0) throw new NeverSyncedError(missingFreshness.map((item) => item.name), neverSyncedMessage(missingFreshness));
|
|
444
498
|
return {
|
|
445
499
|
rows: db.prepare(sql).all(),
|
|
446
500
|
tables: tables.map(({ name, lastSynced }) => ({
|
|
@@ -451,20 +505,23 @@ const createCache = (path, db) => ({
|
|
|
451
505
|
},
|
|
452
506
|
describe: (opts) => {
|
|
453
507
|
const allowedTables = opts?.platform ? tablesForPlatform(opts.platform) : void 0;
|
|
508
|
+
const tables = db.prepare("SELECT name, type, sql FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' ORDER BY name").all().filter((table) => String(table.name) !== "x_raw_messages").filter((table) => allowedTables === void 0 || allowedTables.has(String(table.name)));
|
|
509
|
+
const lastSyncedForTable = (name) => {
|
|
510
|
+
if (name === "sync_state") return null;
|
|
511
|
+
return lastSyncedForPolicy(db, freshnessPolicyForTable(name));
|
|
512
|
+
};
|
|
454
513
|
return {
|
|
455
514
|
path,
|
|
456
|
-
tables:
|
|
515
|
+
tables: tables.map((table) => {
|
|
457
516
|
const name = String(table.name);
|
|
458
517
|
const type = table.type === "view" ? "view" : "table";
|
|
459
518
|
const rowCount = db.prepare(`SELECT count(*) AS rows FROM ${quoteIdentifier(name)}`).get();
|
|
460
|
-
const collection = collectionForReadTable(name);
|
|
461
|
-
const syncState = name === "sync_state" ? void 0 : db.prepare("SELECT last_synced FROM sync_state WHERE collection = :collection").get({ collection });
|
|
462
519
|
return {
|
|
463
520
|
name,
|
|
464
521
|
type,
|
|
465
522
|
sql: String(table.sql),
|
|
466
523
|
rows: Number(rowCount?.rows ?? 0),
|
|
467
|
-
last_synced:
|
|
524
|
+
last_synced: lastSyncedForTable(name)
|
|
468
525
|
};
|
|
469
526
|
})
|
|
470
527
|
};
|
|
@@ -490,13 +547,6 @@ const linkedinRelationsCursorPagination = {
|
|
|
490
547
|
cursorPath: "next_cursor",
|
|
491
548
|
itemsPath: "data"
|
|
492
549
|
};
|
|
493
|
-
const linkedinOffsetPagination = {
|
|
494
|
-
style: "offset",
|
|
495
|
-
limitParam: "limit",
|
|
496
|
-
offsetParam: "offset",
|
|
497
|
-
totalCountPath: "total_count",
|
|
498
|
-
itemsPath: "data"
|
|
499
|
-
};
|
|
500
550
|
const asRecord$1 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
501
551
|
const read$1 = (value) => value === null || value === void 0 ? null : value;
|
|
502
552
|
const readReaction = (value) => typeof value === "string" ? value : null;
|
|
@@ -723,7 +773,7 @@ const LINKEDIN_COLLECTIONS = [
|
|
|
723
773
|
platform: "linkedin",
|
|
724
774
|
requests: [{ path: "/users/:identifier/posts" }],
|
|
725
775
|
ownIdToken: ":identifier",
|
|
726
|
-
pagination:
|
|
776
|
+
pagination: linkedinCursorPagination,
|
|
727
777
|
pageSize: 50,
|
|
728
778
|
sinceField: "created_at",
|
|
729
779
|
lift: liftPost
|
|
@@ -8756,6 +8806,7 @@ const stepPage = (state, page, opts) => {
|
|
|
8756
8806
|
const newestId = state.firstPage && page.items[0] ? String(page.items[0].id) : state.newestId;
|
|
8757
8807
|
const rows = [];
|
|
8758
8808
|
const ids = [];
|
|
8809
|
+
let crossedSince = false;
|
|
8759
8810
|
for (const item of page.items) {
|
|
8760
8811
|
const id = String(item.id);
|
|
8761
8812
|
if (state.checkpoint !== null && id === state.checkpoint) return {
|
|
@@ -8771,17 +8822,7 @@ const stepPage = (state, page, opts) => {
|
|
|
8771
8822
|
};
|
|
8772
8823
|
if (opts.since != null && opts.collection.sinceField) {
|
|
8773
8824
|
const timestamp = liftedTimestamp(opts.collection, item);
|
|
8774
|
-
if (timestamp != null && timestamp < opts.since)
|
|
8775
|
-
rows,
|
|
8776
|
-
ids,
|
|
8777
|
-
nextState: {
|
|
8778
|
-
...state,
|
|
8779
|
-
newestId,
|
|
8780
|
-
firstPage: false
|
|
8781
|
-
},
|
|
8782
|
-
stoppedReason: "since",
|
|
8783
|
-
done: true
|
|
8784
|
-
};
|
|
8825
|
+
if (timestamp != null && timestamp < opts.since) crossedSince = true;
|
|
8785
8826
|
}
|
|
8786
8827
|
const row = opts.collection.lift(item);
|
|
8787
8828
|
applyBakedQueryOverlay(row, opts.request.bakedQuery);
|
|
@@ -8789,6 +8830,17 @@ const stepPage = (state, page, opts) => {
|
|
|
8789
8830
|
rows.push(row);
|
|
8790
8831
|
if (opts.collectIds) ids.push(id);
|
|
8791
8832
|
}
|
|
8833
|
+
if (crossedSince) return {
|
|
8834
|
+
rows,
|
|
8835
|
+
ids,
|
|
8836
|
+
nextState: {
|
|
8837
|
+
...state,
|
|
8838
|
+
newestId,
|
|
8839
|
+
firstPage: false
|
|
8840
|
+
},
|
|
8841
|
+
stoppedReason: "since",
|
|
8842
|
+
done: true
|
|
8843
|
+
};
|
|
8792
8844
|
if (opts.collection.pagination.style === "offset") {
|
|
8793
8845
|
const nextOffset = state.offset + opts.collection.pageSize;
|
|
8794
8846
|
const done = page.items.length === 0 || page.totalCount != null && nextOffset >= page.totalCount || page.totalCount == null && page.items.length < opts.collection.pageSize;
|
|
@@ -8942,11 +8994,15 @@ const runSync = async (deps, opts) => {
|
|
|
8942
8994
|
upserted += result.upserted;
|
|
8943
8995
|
stoppedReason = mergeStoppedReason(stoppedReason, result.stoppedReason);
|
|
8944
8996
|
};
|
|
8997
|
+
const writePendingCursor = (pending) => {
|
|
8998
|
+
deps.cache.setCursor(pending.collection, pending.cursor, pending.objectCount, pending.lastSynced);
|
|
8999
|
+
};
|
|
8945
9000
|
if (collection.parentCollection) {
|
|
8946
9001
|
const parentCollection = collectionByKey(collection.parentCollection);
|
|
8947
9002
|
if (!parentCollection) throw new Error(`Missing parent collection ${collection.parentCollection}`);
|
|
8948
9003
|
const parentIds = [];
|
|
8949
9004
|
const parentPersistsCheckpoint = parentCollection.requests.length === 1;
|
|
9005
|
+
let parentCheckpoint;
|
|
8950
9006
|
for (const request of parentCollection.requests) {
|
|
8951
9007
|
const result = await walkRequest({
|
|
8952
9008
|
deps,
|
|
@@ -8962,17 +9018,18 @@ const runSync = async (deps, opts) => {
|
|
|
8962
9018
|
});
|
|
8963
9019
|
addResult(result);
|
|
8964
9020
|
parentIds.push(...result.ids);
|
|
8965
|
-
if (parentPersistsCheckpoint)
|
|
9021
|
+
if (parentPersistsCheckpoint) parentCheckpoint = singleRequestCheckpoint({
|
|
8966
9022
|
collection: parentCollection,
|
|
8967
9023
|
result
|
|
8968
|
-
})
|
|
9024
|
+
});
|
|
8969
9025
|
}
|
|
8970
|
-
if (!parentPersistsCheckpoint)
|
|
9026
|
+
if (!parentPersistsCheckpoint) parentCheckpoint = {
|
|
8971
9027
|
collection: parentCollection.key,
|
|
8972
9028
|
cursor: null,
|
|
8973
9029
|
objectCount: upserted,
|
|
8974
9030
|
lastSynced: Date.now()
|
|
8975
|
-
}
|
|
9031
|
+
};
|
|
9032
|
+
if (parentCheckpoint) writePendingCursor(parentCheckpoint);
|
|
8976
9033
|
for (const parentId of parentIds) for (const request of collection.requests) addResult(await walkRequest({
|
|
8977
9034
|
deps,
|
|
8978
9035
|
collection,
|
|
@@ -9019,7 +9076,7 @@ const runSync = async (deps, opts) => {
|
|
|
9019
9076
|
lastSynced: Date.now()
|
|
9020
9077
|
});
|
|
9021
9078
|
}
|
|
9022
|
-
for (const pending of pendingCursors)
|
|
9079
|
+
for (const pending of pendingCursors) writePendingCursor(pending);
|
|
9023
9080
|
return {
|
|
9024
9081
|
collection: collection.key,
|
|
9025
9082
|
objectsUpserted: upserted,
|
|
@@ -10676,10 +10733,10 @@ const LINKEDIN_OPERATIONS = [
|
|
|
10676
10733
|
capability: "read",
|
|
10677
10734
|
mutates: false,
|
|
10678
10735
|
pagination: {
|
|
10679
|
-
style: "
|
|
10736
|
+
style: "cursor",
|
|
10680
10737
|
limitParam: "limit",
|
|
10681
|
-
|
|
10682
|
-
|
|
10738
|
+
cursorParam: "cursor",
|
|
10739
|
+
cursorPath: "next_cursor",
|
|
10683
10740
|
itemsPath: "data"
|
|
10684
10741
|
},
|
|
10685
10742
|
costBPS: 50,
|
|
@@ -10688,18 +10745,18 @@ const LINKEDIN_OPERATIONS = [
|
|
|
10688
10745
|
},
|
|
10689
10746
|
parameters: [
|
|
10690
10747
|
{
|
|
10691
|
-
name: "
|
|
10748
|
+
name: "limit",
|
|
10692
10749
|
in: "query",
|
|
10693
10750
|
required: false,
|
|
10694
10751
|
kind: "integer",
|
|
10695
|
-
description: "
|
|
10752
|
+
description: "Rows to return (1-100)"
|
|
10696
10753
|
},
|
|
10697
10754
|
{
|
|
10698
|
-
name: "
|
|
10755
|
+
name: "cursor",
|
|
10699
10756
|
in: "query",
|
|
10700
10757
|
required: false,
|
|
10701
|
-
kind: "
|
|
10702
|
-
description: "
|
|
10758
|
+
kind: "string",
|
|
10759
|
+
description: "Pagination cursor"
|
|
10703
10760
|
},
|
|
10704
10761
|
{
|
|
10705
10762
|
name: "identifier",
|
|
@@ -13669,8 +13726,8 @@ const UserProfileInput = object$1({
|
|
|
13669
13726
|
});
|
|
13670
13727
|
const UserProfileOutput = record$1(string$1(), unknown$1());
|
|
13671
13728
|
const UserPostsInput = object$1({
|
|
13672
|
-
offset: union$1([string$1(), number$1()]).optional(),
|
|
13673
13729
|
limit: union$1([string$1(), number$1()]).optional(),
|
|
13730
|
+
cursor: string$1().optional(),
|
|
13674
13731
|
identifier: string$1()
|
|
13675
13732
|
});
|
|
13676
13733
|
const UserPostsOutput = object$1({
|
|
@@ -21572,7 +21629,7 @@ const createInstance = (defaults) => {
|
|
|
21572
21629
|
const ky = createInstance();
|
|
21573
21630
|
//#endregion
|
|
21574
21631
|
//#region package.json
|
|
21575
|
-
var version$1 = "0.7.
|
|
21632
|
+
var version$1 = "0.7.1";
|
|
21576
21633
|
//#endregion
|
|
21577
21634
|
//#region src/lib/env.ts
|
|
21578
21635
|
const URLWithTrailingSlash = url().transform(ensureTrailingSlash);
|