@spectratools/xapi-cli 0.2.0 → 0.2.2
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/cli.js +191 -102
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,35 +1,152 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { realpathSync } from "fs";
|
|
4
|
+
import { readFileSync, realpathSync } from "fs";
|
|
5
|
+
import { dirname, resolve } from "path";
|
|
5
6
|
import { fileURLToPath } from "url";
|
|
6
7
|
import { Cli as Cli7 } from "incur";
|
|
7
8
|
|
|
9
|
+
// src/auth.ts
|
|
10
|
+
import { HttpError } from "@spectratools/cli-shared";
|
|
11
|
+
import { z } from "incur";
|
|
12
|
+
var bearerTokenSchema = z.string().describe("X app-only bearer token (read-only endpoints)");
|
|
13
|
+
var accessTokenSchema = z.string().describe("X OAuth 2.0 user access token (required for write endpoints)");
|
|
14
|
+
var xApiReadEnv = z.object({
|
|
15
|
+
X_BEARER_TOKEN: bearerTokenSchema.optional(),
|
|
16
|
+
X_ACCESS_TOKEN: accessTokenSchema.optional()
|
|
17
|
+
}).refine((env) => Boolean(env.X_ACCESS_TOKEN || env.X_BEARER_TOKEN), {
|
|
18
|
+
message: "Set X_ACCESS_TOKEN or X_BEARER_TOKEN to authenticate X API requests."
|
|
19
|
+
});
|
|
20
|
+
var xApiWriteEnv = z.object({
|
|
21
|
+
X_ACCESS_TOKEN: accessTokenSchema,
|
|
22
|
+
X_BEARER_TOKEN: bearerTokenSchema.optional()
|
|
23
|
+
});
|
|
24
|
+
function readAuthToken(env) {
|
|
25
|
+
if (env.X_ACCESS_TOKEN) {
|
|
26
|
+
return env.X_ACCESS_TOKEN;
|
|
27
|
+
}
|
|
28
|
+
return env.X_BEARER_TOKEN;
|
|
29
|
+
}
|
|
30
|
+
function writeAuthToken(env) {
|
|
31
|
+
return env.X_ACCESS_TOKEN;
|
|
32
|
+
}
|
|
33
|
+
function parseXApiErrorDetail(body) {
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(body);
|
|
36
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
const candidate = parsed;
|
|
40
|
+
if (typeof candidate.detail === "string" && candidate.detail.trim()) return candidate.detail;
|
|
41
|
+
if (typeof candidate.title === "string" && candidate.title.trim()) return candidate.title;
|
|
42
|
+
const firstError = candidate.errors?.[0];
|
|
43
|
+
if (typeof firstError?.message === "string" && firstError.message.trim())
|
|
44
|
+
return firstError.message;
|
|
45
|
+
if (typeof firstError?.detail === "string" && firstError.detail.trim())
|
|
46
|
+
return firstError.detail;
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
return void 0;
|
|
50
|
+
}
|
|
51
|
+
function readEndpointNeedsUserToken(detail) {
|
|
52
|
+
if (!detail) return false;
|
|
53
|
+
const normalized = detail.toLowerCase();
|
|
54
|
+
return normalized.includes("application-only is forbidden") || normalized.includes("oauth 2.0 application-only") || normalized.includes("app-only") || normalized.includes("user context");
|
|
55
|
+
}
|
|
56
|
+
function toReadAuthError(operation, error) {
|
|
57
|
+
if (error instanceof HttpError && (error.status === 401 || error.status === 403)) {
|
|
58
|
+
const detail = parseXApiErrorDetail(error.body);
|
|
59
|
+
const requiresUserToken = readEndpointNeedsUserToken(detail);
|
|
60
|
+
return {
|
|
61
|
+
code: "INSUFFICIENT_READ_AUTH",
|
|
62
|
+
message: [
|
|
63
|
+
"Insufficient auth for read endpoint:",
|
|
64
|
+
`- operation: ${operation}`,
|
|
65
|
+
`- status: ${error.status} ${error.statusText}`,
|
|
66
|
+
requiresUserToken ? "- required auth: X_ACCESS_TOKEN (OAuth 2.0 user token required for this endpoint)" : "- required auth: X_ACCESS_TOKEN or X_BEARER_TOKEN (with required read scopes)",
|
|
67
|
+
...detail ? [`- x_api_detail: ${detail}`] : []
|
|
68
|
+
].join("\n")
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return void 0;
|
|
72
|
+
}
|
|
73
|
+
function toWriteAuthError(operation, error) {
|
|
74
|
+
if (error instanceof HttpError && (error.status === 401 || error.status === 403)) {
|
|
75
|
+
const detail = parseXApiErrorDetail(error.body);
|
|
76
|
+
return {
|
|
77
|
+
code: "INSUFFICIENT_WRITE_AUTH",
|
|
78
|
+
message: [
|
|
79
|
+
"Insufficient auth for write endpoint:",
|
|
80
|
+
`- operation: ${operation}`,
|
|
81
|
+
`- status: ${error.status} ${error.statusText}`,
|
|
82
|
+
"- required auth: X_ACCESS_TOKEN (OAuth 2.0 user token with write scopes)",
|
|
83
|
+
"- note: app-only X_BEARER_TOKEN cannot perform write actions",
|
|
84
|
+
...detail ? [`- x_api_detail: ${detail}`] : []
|
|
85
|
+
].join("\n")
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return void 0;
|
|
89
|
+
}
|
|
90
|
+
function toXApiHttpError(operation, error) {
|
|
91
|
+
if (!(error instanceof HttpError)) {
|
|
92
|
+
return void 0;
|
|
93
|
+
}
|
|
94
|
+
const detail = parseXApiErrorDetail(error.body);
|
|
95
|
+
return {
|
|
96
|
+
code: "X_API_REQUEST_FAILED",
|
|
97
|
+
message: [
|
|
98
|
+
"X API request failed:",
|
|
99
|
+
`- operation: ${operation}`,
|
|
100
|
+
`- status: ${error.status} ${error.statusText}`,
|
|
101
|
+
...detail ? [`- x_api_detail: ${detail}`] : []
|
|
102
|
+
].join("\n")
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function toXApiCommandError(operation, error, authScope = "read") {
|
|
106
|
+
const authError = authScope === "write" ? toWriteAuthError(operation, error) : toReadAuthError(operation, error);
|
|
107
|
+
if (authError) {
|
|
108
|
+
return authError;
|
|
109
|
+
}
|
|
110
|
+
return toXApiHttpError(operation, error);
|
|
111
|
+
}
|
|
112
|
+
|
|
8
113
|
// src/commands/dm.ts
|
|
9
114
|
import { Cli, z as z2 } from "incur";
|
|
10
115
|
|
|
11
116
|
// src/api.ts
|
|
12
117
|
import { createHttpClient, withRetry } from "@spectratools/cli-shared";
|
|
13
|
-
var
|
|
118
|
+
var BASE_URL_V2 = "https://api.x.com/2";
|
|
119
|
+
var BASE_URL_V1_1 = "https://api.x.com/1.1";
|
|
14
120
|
var RETRY_OPTIONS = { maxRetries: 3, baseMs: 500, maxMs: 1e4 };
|
|
15
121
|
function createXApiClient(bearerToken) {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
122
|
+
const defaultHeaders = {
|
|
123
|
+
Authorization: `Bearer ${bearerToken}`
|
|
124
|
+
};
|
|
125
|
+
const httpV2 = createHttpClient({
|
|
126
|
+
baseUrl: BASE_URL_V2,
|
|
127
|
+
defaultHeaders
|
|
128
|
+
});
|
|
129
|
+
const httpV1_1 = createHttpClient({
|
|
130
|
+
baseUrl: BASE_URL_V1_1,
|
|
131
|
+
defaultHeaders
|
|
21
132
|
});
|
|
22
133
|
function get(path, query) {
|
|
23
134
|
return withRetry(
|
|
24
|
-
() =>
|
|
135
|
+
() => httpV2.request(path, query !== void 0 ? { query } : {}),
|
|
136
|
+
RETRY_OPTIONS
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
function getV1_1(path, query) {
|
|
140
|
+
return withRetry(
|
|
141
|
+
() => httpV1_1.request(path, query !== void 0 ? { query } : {}),
|
|
25
142
|
RETRY_OPTIONS
|
|
26
143
|
);
|
|
27
144
|
}
|
|
28
145
|
function post(path, body) {
|
|
29
|
-
return withRetry(() =>
|
|
146
|
+
return withRetry(() => httpV2.request(path, { method: "POST", body }), RETRY_OPTIONS);
|
|
30
147
|
}
|
|
31
148
|
function del(path) {
|
|
32
|
-
return withRetry(() =>
|
|
149
|
+
return withRetry(() => httpV2.request(path, { method: "DELETE" }), RETRY_OPTIONS);
|
|
33
150
|
}
|
|
34
151
|
const POST_FIELDS = "id,text,author_id,created_at,public_metrics";
|
|
35
152
|
function getPost(id) {
|
|
@@ -148,14 +265,19 @@ function createXApiClient(bearerToken) {
|
|
|
148
265
|
});
|
|
149
266
|
}
|
|
150
267
|
function getTrendingPlaces() {
|
|
151
|
-
return
|
|
152
|
-
"/trends/available"
|
|
153
|
-
);
|
|
268
|
+
return getV1_1(
|
|
269
|
+
"/trends/available.json"
|
|
270
|
+
).then((places) => ({ data: places }));
|
|
154
271
|
}
|
|
155
272
|
function getTrendsByLocation(woeid) {
|
|
156
|
-
return
|
|
157
|
-
|
|
158
|
-
|
|
273
|
+
return getV1_1("/trends/place.json", { id: woeid }).then((locations) => {
|
|
274
|
+
const trends2 = (locations[0]?.trends ?? []).map((trend) => ({
|
|
275
|
+
name: trend.name,
|
|
276
|
+
query: trend.query,
|
|
277
|
+
tweet_volume: trend.tweet_volume ?? void 0
|
|
278
|
+
}));
|
|
279
|
+
return { data: trends2 };
|
|
280
|
+
});
|
|
159
281
|
}
|
|
160
282
|
function getDmConversations(userId, maxResults, nextToken) {
|
|
161
283
|
return get(`/users/${userId}/dm_conversations`, {
|
|
@@ -214,66 +336,6 @@ function truncateText(text, max = 100) {
|
|
|
214
336
|
return `${text.slice(0, max - 3)}...`;
|
|
215
337
|
}
|
|
216
338
|
|
|
217
|
-
// src/auth.ts
|
|
218
|
-
import { HttpError } from "@spectratools/cli-shared";
|
|
219
|
-
import { z } from "incur";
|
|
220
|
-
var bearerTokenSchema = z.string().describe("X app-only bearer token (read-only endpoints)");
|
|
221
|
-
var accessTokenSchema = z.string().describe("X OAuth 2.0 user access token (required for write endpoints)");
|
|
222
|
-
var xApiReadEnv = z.object({
|
|
223
|
-
X_BEARER_TOKEN: bearerTokenSchema.optional(),
|
|
224
|
-
X_ACCESS_TOKEN: accessTokenSchema.optional()
|
|
225
|
-
}).refine((env) => Boolean(env.X_ACCESS_TOKEN || env.X_BEARER_TOKEN), {
|
|
226
|
-
message: "Set X_ACCESS_TOKEN or X_BEARER_TOKEN to authenticate X API requests."
|
|
227
|
-
});
|
|
228
|
-
var xApiWriteEnv = z.object({
|
|
229
|
-
X_ACCESS_TOKEN: accessTokenSchema,
|
|
230
|
-
X_BEARER_TOKEN: bearerTokenSchema.optional()
|
|
231
|
-
});
|
|
232
|
-
function readAuthToken(env) {
|
|
233
|
-
if (env.X_ACCESS_TOKEN) {
|
|
234
|
-
return env.X_ACCESS_TOKEN;
|
|
235
|
-
}
|
|
236
|
-
return env.X_BEARER_TOKEN;
|
|
237
|
-
}
|
|
238
|
-
function writeAuthToken(env) {
|
|
239
|
-
return env.X_ACCESS_TOKEN;
|
|
240
|
-
}
|
|
241
|
-
function parseXApiErrorDetail(body) {
|
|
242
|
-
try {
|
|
243
|
-
const parsed = JSON.parse(body);
|
|
244
|
-
if (typeof parsed !== "object" || parsed === null) {
|
|
245
|
-
return void 0;
|
|
246
|
-
}
|
|
247
|
-
const candidate = parsed;
|
|
248
|
-
if (typeof candidate.detail === "string" && candidate.detail.trim()) return candidate.detail;
|
|
249
|
-
if (typeof candidate.title === "string" && candidate.title.trim()) return candidate.title;
|
|
250
|
-
const firstError = candidate.errors?.[0];
|
|
251
|
-
if (typeof firstError?.message === "string" && firstError.message.trim())
|
|
252
|
-
return firstError.message;
|
|
253
|
-
if (typeof firstError?.detail === "string" && firstError.detail.trim())
|
|
254
|
-
return firstError.detail;
|
|
255
|
-
} catch {
|
|
256
|
-
}
|
|
257
|
-
return void 0;
|
|
258
|
-
}
|
|
259
|
-
function toWriteAuthError(operation, error) {
|
|
260
|
-
if (error instanceof HttpError && (error.status === 401 || error.status === 403)) {
|
|
261
|
-
const detail = parseXApiErrorDetail(error.body);
|
|
262
|
-
return {
|
|
263
|
-
code: "INSUFFICIENT_WRITE_AUTH",
|
|
264
|
-
message: [
|
|
265
|
-
"Insufficient auth for write endpoint:",
|
|
266
|
-
`- operation: ${operation}`,
|
|
267
|
-
`- status: ${error.status} ${error.statusText}`,
|
|
268
|
-
"- required auth: X_ACCESS_TOKEN (OAuth 2.0 user token with write scopes)",
|
|
269
|
-
"- note: app-only X_BEARER_TOKEN cannot perform write actions",
|
|
270
|
-
...detail ? [`- x_api_detail: ${detail}`] : []
|
|
271
|
-
].join("\n")
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
return void 0;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
339
|
// src/collect-paged.ts
|
|
278
340
|
import { paginateCursor } from "@spectratools/cli-shared";
|
|
279
341
|
async function collectPaged(fetchFn, mapFn, maxResults, pageSize = 100) {
|
|
@@ -332,7 +394,7 @@ dm.command("conversations", {
|
|
|
332
394
|
const firstParticipant = allConvos[0]?.participant_ids[0];
|
|
333
395
|
return c.ok(
|
|
334
396
|
{ conversations: allConvos, count: allConvos.length },
|
|
335
|
-
{
|
|
397
|
+
c.format === "json" || c.format === "jsonl" ? void 0 : {
|
|
336
398
|
cta: firstParticipant ? {
|
|
337
399
|
description: "Next steps:",
|
|
338
400
|
commands: [
|
|
@@ -412,12 +474,20 @@ lists.command("get", {
|
|
|
412
474
|
owner_id: list.owner_id,
|
|
413
475
|
member_count: list.member_count
|
|
414
476
|
},
|
|
415
|
-
{
|
|
477
|
+
c.format === "json" || c.format === "jsonl" ? void 0 : {
|
|
416
478
|
cta: {
|
|
417
479
|
description: "Explore this list:",
|
|
418
480
|
commands: [
|
|
419
|
-
{
|
|
420
|
-
|
|
481
|
+
{
|
|
482
|
+
command: "lists members",
|
|
483
|
+
args: { id: c.args.id },
|
|
484
|
+
description: "See list members"
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
command: "lists posts",
|
|
488
|
+
args: { id: c.args.id },
|
|
489
|
+
description: "See list posts"
|
|
490
|
+
}
|
|
421
491
|
]
|
|
422
492
|
}
|
|
423
493
|
}
|
|
@@ -501,7 +571,7 @@ lists.command("posts", {
|
|
|
501
571
|
const firstId = allPosts[0]?.id;
|
|
502
572
|
return c.ok(
|
|
503
573
|
{ posts: allPosts, count: allPosts.length },
|
|
504
|
-
{
|
|
574
|
+
c.format === "json" || c.format === "jsonl" ? void 0 : {
|
|
505
575
|
cta: firstId ? {
|
|
506
576
|
description: "Next steps:",
|
|
507
577
|
commands: [
|
|
@@ -556,7 +626,7 @@ posts.command("get", {
|
|
|
556
626
|
retweets: post.public_metrics?.retweet_count,
|
|
557
627
|
replies: post.public_metrics?.reply_count
|
|
558
628
|
},
|
|
559
|
-
{
|
|
629
|
+
c.format === "json" || c.format === "jsonl" ? void 0 : {
|
|
560
630
|
cta: {
|
|
561
631
|
description: "Explore this post:",
|
|
562
632
|
commands: [
|
|
@@ -582,7 +652,7 @@ posts.command("search", {
|
|
|
582
652
|
query: z4.string().describe("Search query")
|
|
583
653
|
}),
|
|
584
654
|
options: z4.object({
|
|
585
|
-
maxResults: z4.number().default(10).describe("Maximum results to return (10\u2013100)"),
|
|
655
|
+
maxResults: z4.number().int().min(10).max(100).default(10).describe("Maximum results to return (10\u2013100)"),
|
|
586
656
|
sort: z4.enum(["recency", "relevancy"]).default("recency").describe("Sort order"),
|
|
587
657
|
verbose: z4.boolean().optional().describe("Show full text without truncation")
|
|
588
658
|
}),
|
|
@@ -621,7 +691,7 @@ posts.command("search", {
|
|
|
621
691
|
const firstId = items[0]?.id;
|
|
622
692
|
return c.ok(
|
|
623
693
|
{ posts: items, count: items.length },
|
|
624
|
-
{
|
|
694
|
+
c.format === "json" || c.format === "jsonl" ? void 0 : {
|
|
625
695
|
cta: firstId ? {
|
|
626
696
|
description: "Next steps:",
|
|
627
697
|
commands: [
|
|
@@ -656,18 +726,21 @@ posts.command("create", {
|
|
|
656
726
|
try {
|
|
657
727
|
const client = createXApiClient(writeAuthToken(c.env));
|
|
658
728
|
const res = await client.createPost(c.options.text, c.options.replyTo, c.options.quote);
|
|
659
|
-
return c.ok(
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
729
|
+
return c.ok(
|
|
730
|
+
res.data,
|
|
731
|
+
c.format === "json" || c.format === "jsonl" ? void 0 : {
|
|
732
|
+
cta: {
|
|
733
|
+
description: "View your post:",
|
|
734
|
+
commands: [
|
|
735
|
+
{
|
|
736
|
+
command: "posts get",
|
|
737
|
+
args: { id: res.data.id },
|
|
738
|
+
description: "See the created post"
|
|
739
|
+
}
|
|
740
|
+
]
|
|
741
|
+
}
|
|
669
742
|
}
|
|
670
|
-
|
|
743
|
+
);
|
|
671
744
|
} catch (error) {
|
|
672
745
|
const authError = toWriteAuthError("posts create", error);
|
|
673
746
|
if (authError) return c.error(authError);
|
|
@@ -734,7 +807,7 @@ posts.command("likes", {
|
|
|
734
807
|
);
|
|
735
808
|
return c.ok(
|
|
736
809
|
{ users: allUsers, count: allUsers.length },
|
|
737
|
-
{
|
|
810
|
+
c.format === "json" || c.format === "jsonl" ? void 0 : {
|
|
738
811
|
cta: {
|
|
739
812
|
description: "Next steps:",
|
|
740
813
|
commands: allUsers.slice(0, 1).map((u) => ({
|
|
@@ -834,7 +907,7 @@ timeline.command("home", {
|
|
|
834
907
|
const firstId = allPosts[0]?.id;
|
|
835
908
|
return c.ok(
|
|
836
909
|
{ posts: allPosts, count: allPosts.length },
|
|
837
|
-
{
|
|
910
|
+
c.format === "json" || c.format === "jsonl" ? void 0 : {
|
|
838
911
|
cta: firstId ? {
|
|
839
912
|
description: "Next steps:",
|
|
840
913
|
commands: [
|
|
@@ -913,7 +986,7 @@ trends.command("places", {
|
|
|
913
986
|
const first = places[0];
|
|
914
987
|
return c.ok(
|
|
915
988
|
{ places, count: places.length },
|
|
916
|
-
{
|
|
989
|
+
c.format === "json" || c.format === "jsonl" ? void 0 : {
|
|
917
990
|
cta: first ? {
|
|
918
991
|
description: "Next steps:",
|
|
919
992
|
commands: [
|
|
@@ -955,7 +1028,7 @@ trends.command("location", {
|
|
|
955
1028
|
const firstTrend = trendItems[0];
|
|
956
1029
|
return c.ok(
|
|
957
1030
|
{ trends: trendItems, count: trendItems.length },
|
|
958
|
-
{
|
|
1031
|
+
c.format === "json" || c.format === "jsonl" ? void 0 : {
|
|
959
1032
|
cta: firstTrend ? {
|
|
960
1033
|
description: "Next steps:",
|
|
961
1034
|
commands: [
|
|
@@ -1020,7 +1093,7 @@ users.command("get", {
|
|
|
1020
1093
|
tweets: user.public_metrics?.tweet_count,
|
|
1021
1094
|
joined: user.created_at ? relativeTime(user.created_at) : void 0
|
|
1022
1095
|
},
|
|
1023
|
-
{
|
|
1096
|
+
c.format === "json" || c.format === "jsonl" ? void 0 : {
|
|
1024
1097
|
cta: {
|
|
1025
1098
|
description: "Explore this user:",
|
|
1026
1099
|
commands: [
|
|
@@ -1162,7 +1235,7 @@ users.command("posts", {
|
|
|
1162
1235
|
const firstId = allPosts[0]?.id;
|
|
1163
1236
|
return c.ok(
|
|
1164
1237
|
{ posts: allPosts, count: allPosts.length },
|
|
1165
|
-
{
|
|
1238
|
+
c.format === "json" || c.format === "jsonl" ? void 0 : {
|
|
1166
1239
|
cta: firstId ? {
|
|
1167
1240
|
description: "Next steps:",
|
|
1168
1241
|
commands: [
|
|
@@ -1245,7 +1318,7 @@ users.command("search", {
|
|
|
1245
1318
|
const first = items[0];
|
|
1246
1319
|
return c.ok(
|
|
1247
1320
|
{ users: items, count: items.length },
|
|
1248
|
-
{
|
|
1321
|
+
c.format === "json" || c.format === "jsonl" ? void 0 : {
|
|
1249
1322
|
cta: first ? {
|
|
1250
1323
|
description: "Next steps:",
|
|
1251
1324
|
commands: [
|
|
@@ -1262,9 +1335,25 @@ users.command("search", {
|
|
|
1262
1335
|
});
|
|
1263
1336
|
|
|
1264
1337
|
// src/cli.ts
|
|
1338
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
1339
|
+
var pkg = JSON.parse(readFileSync(resolve(__dirname, "../package.json"), "utf8"));
|
|
1265
1340
|
var cli = Cli7.create("xapi", {
|
|
1341
|
+
version: pkg.version,
|
|
1266
1342
|
description: "X (Twitter) API CLI for spectra-the-bot."
|
|
1267
1343
|
});
|
|
1344
|
+
var WRITE_OPERATIONS = /* @__PURE__ */ new Set(["posts create", "posts delete", "dm send"]);
|
|
1345
|
+
cli.use(async ({ command, error }, next) => {
|
|
1346
|
+
try {
|
|
1347
|
+
return await next();
|
|
1348
|
+
} catch (cause) {
|
|
1349
|
+
const authScope = WRITE_OPERATIONS.has(command) ? "write" : "read";
|
|
1350
|
+
const mapped = toXApiCommandError(command, cause, authScope);
|
|
1351
|
+
if (mapped) {
|
|
1352
|
+
return error(mapped);
|
|
1353
|
+
}
|
|
1354
|
+
throw cause;
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1268
1357
|
cli.command(posts);
|
|
1269
1358
|
cli.command(users);
|
|
1270
1359
|
cli.command(timeline);
|