@spectratools/xapi-cli 0.4.0 → 0.6.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/README.md +20 -7
- package/dist/cli.js +468 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,8 +27,8 @@ xapi-cli mcp add
|
|
|
27
27
|
# Read-only endpoints (search, profiles, trends, list reads)
|
|
28
28
|
export X_BEARER_TOKEN=your_app_bearer_token
|
|
29
29
|
|
|
30
|
-
# Write endpoints (
|
|
31
|
-
# OAuth 2.0 user context token with
|
|
30
|
+
# Write endpoints (post interactions, list mutations, user moderation, and send DM)
|
|
31
|
+
# OAuth 2.0 user context token with required write scopes
|
|
32
32
|
export X_ACCESS_TOKEN=your_oauth2_user_access_token
|
|
33
33
|
```
|
|
34
34
|
|
|
@@ -38,10 +38,10 @@ Auth behavior:
|
|
|
38
38
|
|
|
39
39
|
## Command Group Intent Summary
|
|
40
40
|
|
|
41
|
-
- `posts` — Read/search/create/delete posts and
|
|
42
|
-
- `users` — Profile lookup, social graph traversal, and
|
|
41
|
+
- `posts` — Read/search/create/delete posts, plus like/unlike, retweet, and bookmark workflows
|
|
42
|
+
- `users` — Profile lookup, social graph traversal, timelines, and follow/block/mute moderation actions
|
|
43
43
|
- `timeline` — Home timeline and mention stream monitoring
|
|
44
|
-
- `lists` — List
|
|
44
|
+
- `lists` — List lookup, creation/deletion, member management, and list feed reads
|
|
45
45
|
- `trends` — Trend place discovery and per-location trend fetch
|
|
46
46
|
- `dm` — Conversation listing and outbound direct messages
|
|
47
47
|
|
|
@@ -63,10 +63,18 @@ xapi-cli users followers jack --max-results 100 --format json
|
|
|
63
63
|
# Baseline file format: one follower ID per line
|
|
64
64
|
xapi-cli users followers jack --new-only --seen-ids-file ./seen-followers.txt --format json
|
|
65
65
|
|
|
66
|
-
# 3)
|
|
66
|
+
# 3) Engagement + moderation helper flow
|
|
67
67
|
xapi-cli posts get 1234567890 --format json
|
|
68
|
+
xapi-cli posts like 1234567890 --format json
|
|
69
|
+
xapi-cli posts unlike 1234567890 --format json
|
|
70
|
+
xapi-cli posts bookmark 1234567890 --format json
|
|
71
|
+
xapi-cli posts unbookmark 1234567890 --format json
|
|
68
72
|
xapi-cli posts likes 1234567890 --max-results 100 --format json
|
|
69
73
|
xapi-cli posts retweets 1234567890 --max-results 100 --format json
|
|
74
|
+
xapi-cli users block spammer123 --format json
|
|
75
|
+
xapi-cli users unblock spammer123 --format json
|
|
76
|
+
xapi-cli users mute noisyaccount --format json
|
|
77
|
+
xapi-cli users unmute noisyaccount --format json
|
|
70
78
|
|
|
71
79
|
# 4) Timeline monitor
|
|
72
80
|
xapi-cli timeline home --max-results 50 --format json
|
|
@@ -75,6 +83,11 @@ xapi-cli timeline mentions --max-results 50 --format json
|
|
|
75
83
|
# 5) DM assistant loop
|
|
76
84
|
xapi-cli dm conversations --max-results 20 --format json
|
|
77
85
|
xapi-cli dm send 12345 --text "hello from agent" --format json
|
|
86
|
+
|
|
87
|
+
# 6) List curation loop
|
|
88
|
+
xapi-cli lists create --name "Core devs" --description "Builders only" --private --format json
|
|
89
|
+
xapi-cli lists add-member 1234567890 jack --format json
|
|
90
|
+
xapi-cli lists members 1234567890 --max-results 100 --format json
|
|
78
91
|
```
|
|
79
92
|
|
|
80
93
|
## Notes
|
|
@@ -83,4 +96,4 @@ xapi-cli dm send 12345 --text "hello from agent" --format json
|
|
|
83
96
|
- `users followers --new-only` performs **client-side diffing** against `--seen-ids-file`; it does not use an API-native `since_id` filter for follower deltas.
|
|
84
97
|
- Baseline files are read-only input (newline-delimited follower IDs) and are never mutated by the CLI.
|
|
85
98
|
- `X_BEARER_TOKEN` is for read-only app auth.
|
|
86
|
-
- `X_ACCESS_TOKEN` is required for write actions (`posts create`, `
|
|
99
|
+
- `X_ACCESS_TOKEN` is required for write actions (`posts create|delete|like|unlike|bookmark|unbookmark|retweet`, `users follow|unfollow|block|unblock|mute|unmute`, `lists create|delete|add-member|remove-member`, `dm send`).
|
package/dist/cli.js
CHANGED
|
@@ -189,6 +189,15 @@ function createXApiClient(bearerToken) {
|
|
|
189
189
|
function likePost(userId, tweetId) {
|
|
190
190
|
return post(`/users/${userId}/likes`, { tweet_id: tweetId });
|
|
191
191
|
}
|
|
192
|
+
function unlikePost(userId, tweetId) {
|
|
193
|
+
return del(`/users/${userId}/likes/${tweetId}`);
|
|
194
|
+
}
|
|
195
|
+
function bookmarkPost(userId, tweetId) {
|
|
196
|
+
return post(`/users/${userId}/bookmarks`, { tweet_id: tweetId });
|
|
197
|
+
}
|
|
198
|
+
function unbookmarkPost(userId, tweetId) {
|
|
199
|
+
return del(`/users/${userId}/bookmarks/${tweetId}`);
|
|
200
|
+
}
|
|
192
201
|
function retweetPost(userId, tweetId) {
|
|
193
202
|
return post(`/users/${userId}/retweets`, { tweet_id: tweetId });
|
|
194
203
|
}
|
|
@@ -225,6 +234,22 @@ function createXApiClient(bearerToken) {
|
|
|
225
234
|
function unfollowUser(sourceUserId, targetUserId) {
|
|
226
235
|
return del(`/users/${sourceUserId}/following/${targetUserId}`);
|
|
227
236
|
}
|
|
237
|
+
function blockUser(sourceUserId, targetUserId) {
|
|
238
|
+
return post(`/users/${sourceUserId}/blocking`, {
|
|
239
|
+
target_user_id: targetUserId
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function unblockUser(sourceUserId, targetUserId) {
|
|
243
|
+
return del(`/users/${sourceUserId}/blocking/${targetUserId}`);
|
|
244
|
+
}
|
|
245
|
+
function muteUser(sourceUserId, targetUserId) {
|
|
246
|
+
return post(`/users/${sourceUserId}/muting`, {
|
|
247
|
+
target_user_id: targetUserId
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
function unmuteUser(sourceUserId, targetUserId) {
|
|
251
|
+
return del(`/users/${sourceUserId}/muting/${targetUserId}`);
|
|
252
|
+
}
|
|
228
253
|
function getUserPosts(id, maxResults, nextToken) {
|
|
229
254
|
return get(`/users/${id}/tweets`, {
|
|
230
255
|
max_results: maxResults,
|
|
@@ -280,6 +305,24 @@ function createXApiClient(bearerToken) {
|
|
|
280
305
|
...nextToken ? { pagination_token: nextToken } : {}
|
|
281
306
|
});
|
|
282
307
|
}
|
|
308
|
+
function createList(name, description, isPrivate) {
|
|
309
|
+
return post("/lists", {
|
|
310
|
+
name,
|
|
311
|
+
...description ? { description } : {},
|
|
312
|
+
...isPrivate !== void 0 ? { private: isPrivate } : {}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
function deleteList(id) {
|
|
316
|
+
return del(`/lists/${id}`);
|
|
317
|
+
}
|
|
318
|
+
function addListMember(id, userId) {
|
|
319
|
+
return post(`/lists/${id}/members`, {
|
|
320
|
+
user_id: userId
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
function removeListMember(id, userId) {
|
|
324
|
+
return del(`/lists/${id}/members/${userId}`);
|
|
325
|
+
}
|
|
283
326
|
function getTrendingPlaces() {
|
|
284
327
|
return getV1_1(
|
|
285
328
|
"/trends/available.json"
|
|
@@ -318,6 +361,9 @@ function createXApiClient(bearerToken) {
|
|
|
318
361
|
getPostLikes,
|
|
319
362
|
getPostRetweets,
|
|
320
363
|
likePost,
|
|
364
|
+
unlikePost,
|
|
365
|
+
bookmarkPost,
|
|
366
|
+
unbookmarkPost,
|
|
321
367
|
retweetPost,
|
|
322
368
|
getUserByUsername,
|
|
323
369
|
getUserById,
|
|
@@ -325,6 +371,10 @@ function createXApiClient(bearerToken) {
|
|
|
325
371
|
getUserFollowing,
|
|
326
372
|
followUser,
|
|
327
373
|
unfollowUser,
|
|
374
|
+
blockUser,
|
|
375
|
+
unblockUser,
|
|
376
|
+
muteUser,
|
|
377
|
+
unmuteUser,
|
|
328
378
|
getUserPosts,
|
|
329
379
|
getUserMentions,
|
|
330
380
|
searchUsers,
|
|
@@ -333,6 +383,10 @@ function createXApiClient(bearerToken) {
|
|
|
333
383
|
getList,
|
|
334
384
|
getListMembers,
|
|
335
385
|
getListPosts,
|
|
386
|
+
createList,
|
|
387
|
+
deleteList,
|
|
388
|
+
addListMember,
|
|
389
|
+
removeListMember,
|
|
336
390
|
getTrendingPlaces,
|
|
337
391
|
getTrendsByLocation,
|
|
338
392
|
getDmConversations,
|
|
@@ -468,10 +522,28 @@ import { Cli as Cli2, z as z3 } from "incur";
|
|
|
468
522
|
var lists = Cli2.create("lists", {
|
|
469
523
|
description: "Manage and browse X lists."
|
|
470
524
|
});
|
|
525
|
+
var listIdSchema = z3.string().min(1).describe("List ID");
|
|
526
|
+
var memberSchema = z3.string().trim().min(1).describe("Member to target (username with or without @, or user ID)");
|
|
527
|
+
var createListOptionsSchema = z3.object({
|
|
528
|
+
name: z3.string().trim().min(1).max(25).describe("List name (1-25 characters)"),
|
|
529
|
+
description: z3.string().trim().min(1).max(100).optional().describe("Optional list description (1-100 characters)"),
|
|
530
|
+
private: z3.boolean().default(false).describe("Create as a private list")
|
|
531
|
+
});
|
|
532
|
+
async function resolveMemberTarget(client, usernameOrId) {
|
|
533
|
+
const normalized = usernameOrId.replace(/^@/, "");
|
|
534
|
+
if (/^\d+$/.test(normalized)) {
|
|
535
|
+
return { id: normalized };
|
|
536
|
+
}
|
|
537
|
+
const user = await client.getUserByUsername(normalized);
|
|
538
|
+
return {
|
|
539
|
+
id: user.data.id,
|
|
540
|
+
username: user.data.username
|
|
541
|
+
};
|
|
542
|
+
}
|
|
471
543
|
lists.command("get", {
|
|
472
544
|
description: "Get a list by ID.",
|
|
473
545
|
args: z3.object({
|
|
474
|
-
id:
|
|
546
|
+
id: listIdSchema
|
|
475
547
|
}),
|
|
476
548
|
env: xApiReadEnv,
|
|
477
549
|
output: z3.object({
|
|
@@ -514,10 +586,180 @@ lists.command("get", {
|
|
|
514
586
|
);
|
|
515
587
|
}
|
|
516
588
|
});
|
|
589
|
+
lists.command("create", {
|
|
590
|
+
description: "Create a new list.",
|
|
591
|
+
options: createListOptionsSchema,
|
|
592
|
+
env: xApiWriteEnv,
|
|
593
|
+
output: z3.object({
|
|
594
|
+
id: z3.string(),
|
|
595
|
+
name: z3.string()
|
|
596
|
+
}),
|
|
597
|
+
examples: [
|
|
598
|
+
{
|
|
599
|
+
options: { name: "Core devs", description: "Builders only", private: true },
|
|
600
|
+
description: "Create a private list"
|
|
601
|
+
}
|
|
602
|
+
],
|
|
603
|
+
async run(c) {
|
|
604
|
+
try {
|
|
605
|
+
const client = createXApiClient(writeAuthToken(c.env));
|
|
606
|
+
const res = await client.createList(c.options.name, c.options.description, c.options.private);
|
|
607
|
+
return c.ok(
|
|
608
|
+
res.data,
|
|
609
|
+
c.format === "json" || c.format === "jsonl" ? void 0 : {
|
|
610
|
+
cta: {
|
|
611
|
+
description: "Next steps:",
|
|
612
|
+
commands: [
|
|
613
|
+
{
|
|
614
|
+
command: "lists get",
|
|
615
|
+
args: { id: res.data.id },
|
|
616
|
+
description: "View the list details"
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
command: "lists add-member",
|
|
620
|
+
args: { id: res.data.id, member: "username-or-id" },
|
|
621
|
+
description: "Add members to the new list"
|
|
622
|
+
}
|
|
623
|
+
]
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
);
|
|
627
|
+
} catch (error) {
|
|
628
|
+
const authError = toWriteAuthError("lists create", error);
|
|
629
|
+
if (authError) return c.error(authError);
|
|
630
|
+
throw error;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
lists.command("delete", {
|
|
635
|
+
description: "Delete a list by ID.",
|
|
636
|
+
args: z3.object({
|
|
637
|
+
id: listIdSchema.describe("List ID to delete")
|
|
638
|
+
}),
|
|
639
|
+
env: xApiWriteEnv,
|
|
640
|
+
output: z3.object({
|
|
641
|
+
deleted: z3.boolean(),
|
|
642
|
+
id: z3.string()
|
|
643
|
+
}),
|
|
644
|
+
examples: [{ args: { id: "1234567890" }, description: "Delete a list" }],
|
|
645
|
+
async run(c) {
|
|
646
|
+
try {
|
|
647
|
+
const client = createXApiClient(writeAuthToken(c.env));
|
|
648
|
+
const res = await client.deleteList(c.args.id);
|
|
649
|
+
return c.ok({ deleted: res.data.deleted, id: c.args.id });
|
|
650
|
+
} catch (error) {
|
|
651
|
+
const authError = toWriteAuthError("lists delete", error);
|
|
652
|
+
if (authError) return c.error(authError);
|
|
653
|
+
throw error;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
lists.command("add-member", {
|
|
658
|
+
description: "Add a member to an X list.",
|
|
659
|
+
args: z3.object({
|
|
660
|
+
id: listIdSchema,
|
|
661
|
+
member: memberSchema
|
|
662
|
+
}),
|
|
663
|
+
env: xApiWriteEnv,
|
|
664
|
+
output: z3.object({
|
|
665
|
+
id: z3.string(),
|
|
666
|
+
member_id: z3.string(),
|
|
667
|
+
member_username: z3.string().optional(),
|
|
668
|
+
is_member: z3.boolean()
|
|
669
|
+
}),
|
|
670
|
+
examples: [
|
|
671
|
+
{
|
|
672
|
+
args: { id: "1234567890", member: "jack" },
|
|
673
|
+
description: "Add @jack to a list"
|
|
674
|
+
}
|
|
675
|
+
],
|
|
676
|
+
async run(c) {
|
|
677
|
+
try {
|
|
678
|
+
const client = createXApiClient(writeAuthToken(c.env));
|
|
679
|
+
const member = await resolveMemberTarget(client, c.args.member);
|
|
680
|
+
const res = await client.addListMember(c.args.id, member.id);
|
|
681
|
+
return c.ok(
|
|
682
|
+
{
|
|
683
|
+
id: c.args.id,
|
|
684
|
+
member_id: member.id,
|
|
685
|
+
member_username: member.username,
|
|
686
|
+
is_member: res.data.is_member
|
|
687
|
+
},
|
|
688
|
+
c.format === "json" || c.format === "jsonl" ? void 0 : {
|
|
689
|
+
cta: {
|
|
690
|
+
description: "Verify list membership:",
|
|
691
|
+
commands: [
|
|
692
|
+
{
|
|
693
|
+
command: "lists members",
|
|
694
|
+
args: { id: c.args.id },
|
|
695
|
+
description: "View current list members"
|
|
696
|
+
}
|
|
697
|
+
]
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
);
|
|
701
|
+
} catch (error) {
|
|
702
|
+
const authError = toWriteAuthError("lists add-member", error);
|
|
703
|
+
if (authError) return c.error(authError);
|
|
704
|
+
throw error;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
lists.command("remove-member", {
|
|
709
|
+
description: "Remove a member from an X list.",
|
|
710
|
+
args: z3.object({
|
|
711
|
+
id: listIdSchema,
|
|
712
|
+
member: memberSchema
|
|
713
|
+
}),
|
|
714
|
+
env: xApiWriteEnv,
|
|
715
|
+
output: z3.object({
|
|
716
|
+
id: z3.string(),
|
|
717
|
+
member_id: z3.string(),
|
|
718
|
+
member_username: z3.string().optional(),
|
|
719
|
+
is_member: z3.boolean()
|
|
720
|
+
}),
|
|
721
|
+
examples: [
|
|
722
|
+
{
|
|
723
|
+
args: { id: "1234567890", member: "jack" },
|
|
724
|
+
description: "Remove @jack from a list"
|
|
725
|
+
}
|
|
726
|
+
],
|
|
727
|
+
async run(c) {
|
|
728
|
+
try {
|
|
729
|
+
const client = createXApiClient(writeAuthToken(c.env));
|
|
730
|
+
const member = await resolveMemberTarget(client, c.args.member);
|
|
731
|
+
const res = await client.removeListMember(c.args.id, member.id);
|
|
732
|
+
return c.ok(
|
|
733
|
+
{
|
|
734
|
+
id: c.args.id,
|
|
735
|
+
member_id: member.id,
|
|
736
|
+
member_username: member.username,
|
|
737
|
+
is_member: res.data.is_member
|
|
738
|
+
},
|
|
739
|
+
c.format === "json" || c.format === "jsonl" ? void 0 : {
|
|
740
|
+
cta: {
|
|
741
|
+
description: "Next steps:",
|
|
742
|
+
commands: [
|
|
743
|
+
{
|
|
744
|
+
command: "lists members",
|
|
745
|
+
args: { id: c.args.id },
|
|
746
|
+
description: "Confirm updated membership"
|
|
747
|
+
}
|
|
748
|
+
]
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
);
|
|
752
|
+
} catch (error) {
|
|
753
|
+
const authError = toWriteAuthError("lists remove-member", error);
|
|
754
|
+
if (authError) return c.error(authError);
|
|
755
|
+
throw error;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
});
|
|
517
759
|
lists.command("members", {
|
|
518
760
|
description: "List members of an X list.",
|
|
519
761
|
args: z3.object({
|
|
520
|
-
id:
|
|
762
|
+
id: listIdSchema
|
|
521
763
|
}),
|
|
522
764
|
options: z3.object({
|
|
523
765
|
maxResults: z3.number().default(100).describe("Maximum members to return")
|
|
@@ -554,7 +796,7 @@ lists.command("members", {
|
|
|
554
796
|
lists.command("posts", {
|
|
555
797
|
description: "Get posts from an X list.",
|
|
556
798
|
args: z3.object({
|
|
557
|
-
id:
|
|
799
|
+
id: listIdSchema
|
|
558
800
|
}),
|
|
559
801
|
options: z3.object({
|
|
560
802
|
maxResults: z3.number().default(25).describe("Maximum posts to return"),
|
|
@@ -815,6 +1057,78 @@ posts.command("like", {
|
|
|
815
1057
|
}
|
|
816
1058
|
}
|
|
817
1059
|
});
|
|
1060
|
+
posts.command("unlike", {
|
|
1061
|
+
description: "Unlike a post by ID.",
|
|
1062
|
+
args: z4.object({
|
|
1063
|
+
id: z4.string().describe("Post ID to unlike")
|
|
1064
|
+
}),
|
|
1065
|
+
env: xApiWriteEnv,
|
|
1066
|
+
output: z4.object({
|
|
1067
|
+
liked: z4.boolean(),
|
|
1068
|
+
id: z4.string()
|
|
1069
|
+
}),
|
|
1070
|
+
examples: [{ args: { id: "1234567890" }, description: "Unlike a post" }],
|
|
1071
|
+
async run(c) {
|
|
1072
|
+
try {
|
|
1073
|
+
const client = createXApiClient(writeAuthToken(c.env));
|
|
1074
|
+
const me = await client.getMe();
|
|
1075
|
+
const res = await client.unlikePost(me.data.id, c.args.id);
|
|
1076
|
+
return c.ok({ liked: res.data.liked, id: c.args.id });
|
|
1077
|
+
} catch (error) {
|
|
1078
|
+
const authError = toWriteAuthError("posts unlike", error);
|
|
1079
|
+
if (authError) return c.error(authError);
|
|
1080
|
+
throw error;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
posts.command("bookmark", {
|
|
1085
|
+
description: "Bookmark a post by ID.",
|
|
1086
|
+
args: z4.object({
|
|
1087
|
+
id: z4.string().describe("Post ID to bookmark")
|
|
1088
|
+
}),
|
|
1089
|
+
env: xApiWriteEnv,
|
|
1090
|
+
output: z4.object({
|
|
1091
|
+
bookmarked: z4.boolean(),
|
|
1092
|
+
id: z4.string()
|
|
1093
|
+
}),
|
|
1094
|
+
examples: [{ args: { id: "1234567890" }, description: "Bookmark a post" }],
|
|
1095
|
+
async run(c) {
|
|
1096
|
+
try {
|
|
1097
|
+
const client = createXApiClient(writeAuthToken(c.env));
|
|
1098
|
+
const me = await client.getMe();
|
|
1099
|
+
const res = await client.bookmarkPost(me.data.id, c.args.id);
|
|
1100
|
+
return c.ok({ bookmarked: res.data.bookmarked, id: c.args.id });
|
|
1101
|
+
} catch (error) {
|
|
1102
|
+
const authError = toWriteAuthError("posts bookmark", error);
|
|
1103
|
+
if (authError) return c.error(authError);
|
|
1104
|
+
throw error;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
posts.command("unbookmark", {
|
|
1109
|
+
description: "Remove bookmark from a post by ID.",
|
|
1110
|
+
args: z4.object({
|
|
1111
|
+
id: z4.string().describe("Post ID to unbookmark")
|
|
1112
|
+
}),
|
|
1113
|
+
env: xApiWriteEnv,
|
|
1114
|
+
output: z4.object({
|
|
1115
|
+
bookmarked: z4.boolean(),
|
|
1116
|
+
id: z4.string()
|
|
1117
|
+
}),
|
|
1118
|
+
examples: [{ args: { id: "1234567890" }, description: "Remove a bookmark from a post" }],
|
|
1119
|
+
async run(c) {
|
|
1120
|
+
try {
|
|
1121
|
+
const client = createXApiClient(writeAuthToken(c.env));
|
|
1122
|
+
const me = await client.getMe();
|
|
1123
|
+
const res = await client.unbookmarkPost(me.data.id, c.args.id);
|
|
1124
|
+
return c.ok({ bookmarked: res.data.bookmarked, id: c.args.id });
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
const authError = toWriteAuthError("posts unbookmark", error);
|
|
1127
|
+
if (authError) return c.error(authError);
|
|
1128
|
+
throw error;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
});
|
|
818
1132
|
posts.command("retweet", {
|
|
819
1133
|
description: "Retweet a post by ID.",
|
|
820
1134
|
args: z4.object({
|
|
@@ -935,6 +1249,7 @@ timeline.command("home", {
|
|
|
935
1249
|
description: "View your home timeline.",
|
|
936
1250
|
options: z5.object({
|
|
937
1251
|
maxResults: z5.number().default(25).describe("Maximum posts to return (5\u2013100)"),
|
|
1252
|
+
sinceId: z5.string().optional().describe("Only return posts newer than this post ID"),
|
|
938
1253
|
verbose: z5.boolean().optional().describe("Show full text without truncation")
|
|
939
1254
|
}),
|
|
940
1255
|
alias: { maxResults: "n" },
|
|
@@ -954,14 +1269,18 @@ timeline.command("home", {
|
|
|
954
1269
|
}),
|
|
955
1270
|
examples: [
|
|
956
1271
|
{ description: "View your home timeline" },
|
|
957
|
-
{ options: { maxResults: 50 }, description: "View 50 posts" }
|
|
1272
|
+
{ options: { maxResults: 50 }, description: "View 50 posts" },
|
|
1273
|
+
{
|
|
1274
|
+
options: { sinceId: "1900123456789012345" },
|
|
1275
|
+
description: "Resume from last-seen post ID"
|
|
1276
|
+
}
|
|
958
1277
|
],
|
|
959
1278
|
async run(c) {
|
|
960
1279
|
const client = createXApiClient(readAuthToken(c.env));
|
|
961
1280
|
const meRes = await client.getMe();
|
|
962
1281
|
const userId = meRes.data.id;
|
|
963
1282
|
const allPosts = await collectPaged(
|
|
964
|
-
(limit, cursor) => client.getHomeTimeline(userId, limit, cursor),
|
|
1283
|
+
(limit, cursor) => client.getHomeTimeline(userId, limit, cursor, c.options.sinceId),
|
|
965
1284
|
(post) => ({
|
|
966
1285
|
id: post.id,
|
|
967
1286
|
text: c.options.verbose ? post.text : truncateText(post.text),
|
|
@@ -994,6 +1313,7 @@ timeline.command("mentions", {
|
|
|
994
1313
|
description: "View your recent mentions.",
|
|
995
1314
|
options: z5.object({
|
|
996
1315
|
maxResults: z5.number().default(25).describe("Maximum mentions to return"),
|
|
1316
|
+
sinceId: z5.string().optional().describe("Only return mentions newer than this post ID"),
|
|
997
1317
|
verbose: z5.boolean().optional().describe("Show full text without truncation")
|
|
998
1318
|
}),
|
|
999
1319
|
alias: { maxResults: "n" },
|
|
@@ -1009,13 +1329,19 @@ timeline.command("mentions", {
|
|
|
1009
1329
|
),
|
|
1010
1330
|
count: z5.number()
|
|
1011
1331
|
}),
|
|
1012
|
-
examples: [
|
|
1332
|
+
examples: [
|
|
1333
|
+
{ description: "View your recent mentions" },
|
|
1334
|
+
{
|
|
1335
|
+
options: { sinceId: "1900123456789012345" },
|
|
1336
|
+
description: "Resume mentions from last-seen post ID"
|
|
1337
|
+
}
|
|
1338
|
+
],
|
|
1013
1339
|
async run(c) {
|
|
1014
1340
|
const client = createXApiClient(readAuthToken(c.env));
|
|
1015
1341
|
const meRes = await client.getMe();
|
|
1016
1342
|
const userId = meRes.data.id;
|
|
1017
1343
|
const allPosts = await collectPaged(
|
|
1018
|
-
(limit, cursor) => client.getMentionsTimeline(userId, limit, cursor),
|
|
1344
|
+
(limit, cursor) => client.getMentionsTimeline(userId, limit, cursor, c.options.sinceId),
|
|
1019
1345
|
(post) => ({
|
|
1020
1346
|
id: post.id,
|
|
1021
1347
|
text: c.options.verbose ? post.text : truncateText(post.text),
|
|
@@ -1310,6 +1636,130 @@ users.command("unfollow", {
|
|
|
1310
1636
|
}
|
|
1311
1637
|
}
|
|
1312
1638
|
});
|
|
1639
|
+
users.command("block", {
|
|
1640
|
+
description: "Block a user by username or ID.",
|
|
1641
|
+
args: z7.object({
|
|
1642
|
+
username: z7.string().describe("Username (with or without @) or user ID")
|
|
1643
|
+
}),
|
|
1644
|
+
env: xApiWriteEnv,
|
|
1645
|
+
output: z7.object({
|
|
1646
|
+
id: z7.string(),
|
|
1647
|
+
username: z7.string(),
|
|
1648
|
+
blocking: z7.boolean()
|
|
1649
|
+
}),
|
|
1650
|
+
examples: [{ args: { username: "jack" }, description: "Block @jack" }],
|
|
1651
|
+
async run(c) {
|
|
1652
|
+
try {
|
|
1653
|
+
const client = createXApiClient(writeAuthToken(c.env));
|
|
1654
|
+
const me = await client.getMe();
|
|
1655
|
+
const targetRes = await resolveUser(client, c.args.username);
|
|
1656
|
+
const target = targetRes.data;
|
|
1657
|
+
const res = await client.blockUser(me.data.id, target.id);
|
|
1658
|
+
return c.ok({
|
|
1659
|
+
id: target.id,
|
|
1660
|
+
username: target.username,
|
|
1661
|
+
blocking: res.data.blocking
|
|
1662
|
+
});
|
|
1663
|
+
} catch (error) {
|
|
1664
|
+
const authError = toWriteAuthError("users block", error);
|
|
1665
|
+
if (authError) return c.error(authError);
|
|
1666
|
+
throw error;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
});
|
|
1670
|
+
users.command("unblock", {
|
|
1671
|
+
description: "Unblock a user by username or ID.",
|
|
1672
|
+
args: z7.object({
|
|
1673
|
+
username: z7.string().describe("Username (with or without @) or user ID")
|
|
1674
|
+
}),
|
|
1675
|
+
env: xApiWriteEnv,
|
|
1676
|
+
output: z7.object({
|
|
1677
|
+
id: z7.string(),
|
|
1678
|
+
username: z7.string(),
|
|
1679
|
+
blocking: z7.boolean()
|
|
1680
|
+
}),
|
|
1681
|
+
examples: [{ args: { username: "jack" }, description: "Unblock @jack" }],
|
|
1682
|
+
async run(c) {
|
|
1683
|
+
try {
|
|
1684
|
+
const client = createXApiClient(writeAuthToken(c.env));
|
|
1685
|
+
const me = await client.getMe();
|
|
1686
|
+
const targetRes = await resolveUser(client, c.args.username);
|
|
1687
|
+
const target = targetRes.data;
|
|
1688
|
+
const res = await client.unblockUser(me.data.id, target.id);
|
|
1689
|
+
return c.ok({
|
|
1690
|
+
id: target.id,
|
|
1691
|
+
username: target.username,
|
|
1692
|
+
blocking: res.data.blocking
|
|
1693
|
+
});
|
|
1694
|
+
} catch (error) {
|
|
1695
|
+
const authError = toWriteAuthError("users unblock", error);
|
|
1696
|
+
if (authError) return c.error(authError);
|
|
1697
|
+
throw error;
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
});
|
|
1701
|
+
users.command("mute", {
|
|
1702
|
+
description: "Mute a user by username or ID.",
|
|
1703
|
+
args: z7.object({
|
|
1704
|
+
username: z7.string().describe("Username (with or without @) or user ID")
|
|
1705
|
+
}),
|
|
1706
|
+
env: xApiWriteEnv,
|
|
1707
|
+
output: z7.object({
|
|
1708
|
+
id: z7.string(),
|
|
1709
|
+
username: z7.string(),
|
|
1710
|
+
muting: z7.boolean()
|
|
1711
|
+
}),
|
|
1712
|
+
examples: [{ args: { username: "jack" }, description: "Mute @jack" }],
|
|
1713
|
+
async run(c) {
|
|
1714
|
+
try {
|
|
1715
|
+
const client = createXApiClient(writeAuthToken(c.env));
|
|
1716
|
+
const me = await client.getMe();
|
|
1717
|
+
const targetRes = await resolveUser(client, c.args.username);
|
|
1718
|
+
const target = targetRes.data;
|
|
1719
|
+
const res = await client.muteUser(me.data.id, target.id);
|
|
1720
|
+
return c.ok({
|
|
1721
|
+
id: target.id,
|
|
1722
|
+
username: target.username,
|
|
1723
|
+
muting: res.data.muting
|
|
1724
|
+
});
|
|
1725
|
+
} catch (error) {
|
|
1726
|
+
const authError = toWriteAuthError("users mute", error);
|
|
1727
|
+
if (authError) return c.error(authError);
|
|
1728
|
+
throw error;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
});
|
|
1732
|
+
users.command("unmute", {
|
|
1733
|
+
description: "Unmute a user by username or ID.",
|
|
1734
|
+
args: z7.object({
|
|
1735
|
+
username: z7.string().describe("Username (with or without @) or user ID")
|
|
1736
|
+
}),
|
|
1737
|
+
env: xApiWriteEnv,
|
|
1738
|
+
output: z7.object({
|
|
1739
|
+
id: z7.string(),
|
|
1740
|
+
username: z7.string(),
|
|
1741
|
+
muting: z7.boolean()
|
|
1742
|
+
}),
|
|
1743
|
+
examples: [{ args: { username: "jack" }, description: "Unmute @jack" }],
|
|
1744
|
+
async run(c) {
|
|
1745
|
+
try {
|
|
1746
|
+
const client = createXApiClient(writeAuthToken(c.env));
|
|
1747
|
+
const me = await client.getMe();
|
|
1748
|
+
const targetRes = await resolveUser(client, c.args.username);
|
|
1749
|
+
const target = targetRes.data;
|
|
1750
|
+
const res = await client.unmuteUser(me.data.id, target.id);
|
|
1751
|
+
return c.ok({
|
|
1752
|
+
id: target.id,
|
|
1753
|
+
username: target.username,
|
|
1754
|
+
muting: res.data.muting
|
|
1755
|
+
});
|
|
1756
|
+
} catch (error) {
|
|
1757
|
+
const authError = toWriteAuthError("users unmute", error);
|
|
1758
|
+
if (authError) return c.error(authError);
|
|
1759
|
+
throw error;
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
});
|
|
1313
1763
|
users.command("followers", {
|
|
1314
1764
|
description: "List followers of a user. Supports optional client-side baseline diffing for new follower detection.",
|
|
1315
1765
|
args: z7.object({
|
|
@@ -1555,9 +2005,20 @@ var WRITE_OPERATIONS = /* @__PURE__ */ new Set([
|
|
|
1555
2005
|
"posts create",
|
|
1556
2006
|
"posts delete",
|
|
1557
2007
|
"posts like",
|
|
2008
|
+
"posts unlike",
|
|
2009
|
+
"posts bookmark",
|
|
2010
|
+
"posts unbookmark",
|
|
1558
2011
|
"posts retweet",
|
|
1559
2012
|
"users follow",
|
|
1560
2013
|
"users unfollow",
|
|
2014
|
+
"users block",
|
|
2015
|
+
"users unblock",
|
|
2016
|
+
"users mute",
|
|
2017
|
+
"users unmute",
|
|
2018
|
+
"lists create",
|
|
2019
|
+
"lists delete",
|
|
2020
|
+
"lists add-member",
|
|
2021
|
+
"lists remove-member",
|
|
1561
2022
|
"dm send"
|
|
1562
2023
|
]);
|
|
1563
2024
|
cli.use(async ({ command, error }, next) => {
|