xapi-to 0.1.15 → 0.1.16
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 +1 -0
- package/dist/index.js +230 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -140,6 +140,7 @@ Config is stored at `~/.xapi/config.json`.
|
|
|
140
140
|
| `twitter.user_by_screen_name` | Get user profile by username |
|
|
141
141
|
| `twitter.user_by_screen_names` | Batch get user profiles by usernames |
|
|
142
142
|
| `twitter.user_tweets` | Get tweets from a user |
|
|
143
|
+
| `twitter.user_tweets_and_replies` | Get tweets and replies from a user |
|
|
143
144
|
| `twitter.user_media` | Get media posts from a user |
|
|
144
145
|
| `twitter.following` | Get user following list |
|
|
145
146
|
| `twitter.followers` | Get user followers |
|
package/dist/index.js
CHANGED
|
@@ -24,12 +24,46 @@ function output(data, format) {
|
|
|
24
24
|
console.log(JSON.stringify(data, null, 2));
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
|
-
if (fmt === "table"
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
if (fmt === "table") {
|
|
28
|
+
const rows = tableRows(data);
|
|
29
|
+
if (rows) {
|
|
30
|
+
printTable(rows);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
30
33
|
}
|
|
31
34
|
console.log(JSON.stringify(data, null, 2));
|
|
32
35
|
}
|
|
36
|
+
function tableRows(data) {
|
|
37
|
+
if (Array.isArray(data)) return normalizeRows(data, "value");
|
|
38
|
+
if (!data || typeof data !== "object") return null;
|
|
39
|
+
const obj = data;
|
|
40
|
+
const preferredKeys = ["items", "actions", "results", "services", "categories", "bindings", "providers"];
|
|
41
|
+
for (const key of preferredKeys) {
|
|
42
|
+
const value = obj[key];
|
|
43
|
+
if (Array.isArray(value)) return normalizeRows(value, singularKey(key));
|
|
44
|
+
}
|
|
45
|
+
const firstArray = Object.entries(obj).find(([, value]) => Array.isArray(value));
|
|
46
|
+
return firstArray ? normalizeRows(firstArray[1], singularKey(firstArray[0])) : null;
|
|
47
|
+
}
|
|
48
|
+
function normalizeRows(rows, primitiveKey) {
|
|
49
|
+
return rows.map((row) => {
|
|
50
|
+
if (row && typeof row === "object" && !Array.isArray(row)) {
|
|
51
|
+
return row;
|
|
52
|
+
}
|
|
53
|
+
return { [primitiveKey]: row };
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function singularKey(key) {
|
|
57
|
+
if (key === "categories") return "category";
|
|
58
|
+
if (key.endsWith("ies")) return `${key.slice(0, -3)}y`;
|
|
59
|
+
if (key.endsWith("s")) return key.slice(0, -1);
|
|
60
|
+
return "value";
|
|
61
|
+
}
|
|
62
|
+
function formatCell(value) {
|
|
63
|
+
if (value === null || value === void 0) return "";
|
|
64
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
65
|
+
return String(value);
|
|
66
|
+
}
|
|
33
67
|
function printTable(rows) {
|
|
34
68
|
if (rows.length === 0) {
|
|
35
69
|
console.log("(empty)");
|
|
@@ -37,14 +71,14 @@ function printTable(rows) {
|
|
|
37
71
|
}
|
|
38
72
|
const keys = Object.keys(rows[0]);
|
|
39
73
|
const widths = keys.map(
|
|
40
|
-
(k) => Math.min(40, Math.max(k.length, ...rows.map((r) =>
|
|
74
|
+
(k) => Math.min(40, Math.max(k.length, ...rows.map((r) => formatCell(r[k]).length)))
|
|
41
75
|
);
|
|
42
76
|
const sep = widths.map((w) => "-".repeat(w)).join(" ");
|
|
43
77
|
const header = keys.map((k, i) => k.padEnd(widths[i])).join(" ");
|
|
44
78
|
console.log(header);
|
|
45
79
|
console.log(sep);
|
|
46
80
|
for (const row of rows) {
|
|
47
|
-
const line = keys.map((k, i) =>
|
|
81
|
+
const line = keys.map((k, i) => formatCell(row[k]).slice(0, widths[i]).padEnd(widths[i])).join(" ");
|
|
48
82
|
console.log(line);
|
|
49
83
|
}
|
|
50
84
|
}
|
|
@@ -118,10 +152,17 @@ async function request(url, options, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
|
118
152
|
try {
|
|
119
153
|
const res = await fetch(url, { ...options, signal: controller.signal });
|
|
120
154
|
if (!res.ok) {
|
|
121
|
-
const
|
|
122
|
-
throw new Error(`HTTP ${res.status}: ${
|
|
155
|
+
const text2 = await res.text();
|
|
156
|
+
throw new Error(`HTTP ${res.status}: ${text2.slice(0, 300)}`);
|
|
157
|
+
}
|
|
158
|
+
if (res.status === 204) {
|
|
159
|
+
return void 0;
|
|
123
160
|
}
|
|
124
|
-
const
|
|
161
|
+
const text = await res.text();
|
|
162
|
+
if (!text.trim()) {
|
|
163
|
+
return void 0;
|
|
164
|
+
}
|
|
165
|
+
const body = JSON.parse(text);
|
|
125
166
|
if (body && typeof body === "object" && "success" in body && body.success === false) {
|
|
126
167
|
const data = body.data;
|
|
127
168
|
if (data?.statusCode === 401 || data?.error === "Unauthorized") {
|
|
@@ -249,13 +290,15 @@ async function listOAuthProviders(apiHost) {
|
|
|
249
290
|
{ method: "GET", headers: { "Content-Type": "application/json" } }
|
|
250
291
|
);
|
|
251
292
|
}
|
|
252
|
-
async function initiateOAuth(apiKeyId, providerId, jwtToken, apiHost) {
|
|
293
|
+
async function initiateOAuth(apiKeyId, providerId, jwtToken, apiHost, scopes) {
|
|
294
|
+
const body = { apiKeyId, providerId };
|
|
295
|
+
if (scopes) body.scopes = scopes;
|
|
253
296
|
return request(
|
|
254
297
|
`${scheme(apiHost)}://${apiHost}/api/oauth/authorize`,
|
|
255
298
|
{
|
|
256
299
|
method: "POST",
|
|
257
300
|
headers: jwtHeaders(jwtToken),
|
|
258
|
-
body: JSON.stringify(
|
|
301
|
+
body: JSON.stringify(body)
|
|
259
302
|
}
|
|
260
303
|
);
|
|
261
304
|
}
|
|
@@ -266,10 +309,11 @@ async function listOAuthBindings(jwtToken, apiHost) {
|
|
|
266
309
|
);
|
|
267
310
|
}
|
|
268
311
|
async function deleteOAuthBinding(bindingId, jwtToken, apiHost) {
|
|
269
|
-
|
|
312
|
+
const result = await request(
|
|
270
313
|
`${scheme(apiHost)}://${apiHost}/api/oauth/bindings/${bindingId}`,
|
|
271
314
|
{ method: "DELETE", headers: jwtHeaders(jwtToken) }
|
|
272
315
|
);
|
|
316
|
+
return result ?? { success: true };
|
|
273
317
|
}
|
|
274
318
|
|
|
275
319
|
// src/codegen.ts
|
|
@@ -659,6 +703,7 @@ function getSource(flags) {
|
|
|
659
703
|
async function actionList2(args, flags) {
|
|
660
704
|
showHelpIfRequested(flags, LIST_HELP);
|
|
661
705
|
const cfg = getConfig();
|
|
706
|
+
const fmt = flags.format || getFormat();
|
|
662
707
|
try {
|
|
663
708
|
const res = await actionList(cfg, {
|
|
664
709
|
source: getSource(flags),
|
|
@@ -668,7 +713,7 @@ async function actionList2(args, flags) {
|
|
|
668
713
|
service_id: flags["service-id"]
|
|
669
714
|
});
|
|
670
715
|
const actions = res.actions || [];
|
|
671
|
-
if (
|
|
716
|
+
if (fmt === "table") {
|
|
672
717
|
output(actions.map((a) => ({
|
|
673
718
|
id: a.id,
|
|
674
719
|
method: a.method ?? "",
|
|
@@ -690,6 +735,7 @@ async function actionSearch2(args, flags) {
|
|
|
690
735
|
const query = args[0];
|
|
691
736
|
if (!query) err("usage: xapi-to search <query>");
|
|
692
737
|
const cfg = getConfig();
|
|
738
|
+
const fmt = flags.format || getFormat();
|
|
693
739
|
try {
|
|
694
740
|
const res = await actionSearch(query, cfg, {
|
|
695
741
|
source: getSource(flags),
|
|
@@ -698,7 +744,7 @@ async function actionSearch2(args, flags) {
|
|
|
698
744
|
page_size: flags["page-size"] ? parseInt(flags["page-size"]) : void 0
|
|
699
745
|
});
|
|
700
746
|
const results = res.results || [];
|
|
701
|
-
if (
|
|
747
|
+
if (fmt === "table") {
|
|
702
748
|
output(results.map((a) => ({
|
|
703
749
|
id: a.id,
|
|
704
750
|
method: a.method ?? "",
|
|
@@ -717,9 +763,10 @@ async function actionSearch2(args, flags) {
|
|
|
717
763
|
}
|
|
718
764
|
async function actionCategories2(args, flags) {
|
|
719
765
|
const cfg = getConfig();
|
|
766
|
+
const fmt = flags.format || getFormat();
|
|
720
767
|
try {
|
|
721
768
|
const res = await actionCategories(cfg, { source: getSource(flags) });
|
|
722
|
-
if (
|
|
769
|
+
if (fmt === "table") {
|
|
723
770
|
output(res.categories.map((c) => ({ category: c })), "table");
|
|
724
771
|
} else {
|
|
725
772
|
output(res, flags.format);
|
|
@@ -730,6 +777,7 @@ async function actionCategories2(args, flags) {
|
|
|
730
777
|
}
|
|
731
778
|
async function actionServices2(args, flags) {
|
|
732
779
|
const cfg = getConfig();
|
|
780
|
+
const fmt = flags.format || getFormat();
|
|
733
781
|
try {
|
|
734
782
|
const res = await actionServices(cfg, {
|
|
735
783
|
page: flags.page ? parseInt(flags.page) : void 0,
|
|
@@ -737,7 +785,7 @@ async function actionServices2(args, flags) {
|
|
|
737
785
|
category: flags.category
|
|
738
786
|
});
|
|
739
787
|
const services = res.services || [];
|
|
740
|
-
if (
|
|
788
|
+
if (fmt === "table") {
|
|
741
789
|
output(services.map((s) => ({
|
|
742
790
|
id: s.id,
|
|
743
791
|
name: s.name ?? "",
|
|
@@ -895,6 +943,11 @@ async function registerAccount(referralCode) {
|
|
|
895
943
|
}
|
|
896
944
|
async function register(args, flags) {
|
|
897
945
|
try {
|
|
946
|
+
const cfg = getConfig();
|
|
947
|
+
const force = flags.force === "true" || flags.force === "1" || flags.force === "yes";
|
|
948
|
+
if (cfg.apiKey && !force) {
|
|
949
|
+
err("register would overwrite existing apiKey", 'Run "xapi-to register --force" to create a new account and replace the saved key.');
|
|
950
|
+
}
|
|
898
951
|
const rawReferral = flags["referral-code"] ?? flags["referralCode"] ?? args[0];
|
|
899
952
|
const referralCode = typeof rawReferral === "string" && rawReferral !== "true" && rawReferral.length > 0 ? rawReferral : void 0;
|
|
900
953
|
const res = await registerAccount(referralCode);
|
|
@@ -910,7 +963,7 @@ async function register(args, flags) {
|
|
|
910
963
|
},
|
|
911
964
|
tweetTemplate: res.tweetTemplate,
|
|
912
965
|
...referralCode ? { referredBy: referralCode } : {},
|
|
913
|
-
note: "apiKey saved to ~/.xapi/config.json"
|
|
966
|
+
note: force && cfg.apiKey ? "apiKey replaced in ~/.xapi/config.json" : "apiKey saved to ~/.xapi/config.json"
|
|
914
967
|
}, flags.format);
|
|
915
968
|
} catch (e) {
|
|
916
969
|
err("register failed", e.message);
|
|
@@ -967,7 +1020,8 @@ __export(oauth_exports, {
|
|
|
967
1020
|
oauthBind: () => oauthBind,
|
|
968
1021
|
oauthProviders: () => oauthProviders,
|
|
969
1022
|
oauthStatus: () => oauthStatus,
|
|
970
|
-
oauthUnbind: () => oauthUnbind
|
|
1023
|
+
oauthUnbind: () => oauthUnbind,
|
|
1024
|
+
pollForBinding: () => pollForBinding
|
|
971
1025
|
});
|
|
972
1026
|
import { spawnSync } from "child_process";
|
|
973
1027
|
function openBrowser(url) {
|
|
@@ -977,14 +1031,22 @@ function openBrowser(url) {
|
|
|
977
1031
|
} catch {
|
|
978
1032
|
}
|
|
979
1033
|
}
|
|
980
|
-
|
|
1034
|
+
function bindingChangedAfter(binding, startedAtMs, existingBindingIds) {
|
|
1035
|
+
const changedAt = Date.parse(binding.updatedAt || binding.createdAt || "");
|
|
1036
|
+
if (!Number.isFinite(changedAt)) return !existingBindingIds.has(binding.id);
|
|
1037
|
+
return changedAt >= startedAtMs;
|
|
1038
|
+
}
|
|
1039
|
+
async function pollForBinding(apiKeyId, providerId, jwtToken, startedAt, existingBindingIds = /* @__PURE__ */ new Set(), timeoutMs = 5 * 60 * 1e3, intervalMs = 3e3) {
|
|
981
1040
|
const deadline = Date.now() + timeoutMs;
|
|
982
1041
|
const isTTY = process.stdout.isTTY;
|
|
1042
|
+
const startedAtMs = startedAt.getTime() - 5e3;
|
|
983
1043
|
while (Date.now() < deadline) {
|
|
984
1044
|
await new Promise((r) => setTimeout(r, intervalMs));
|
|
985
1045
|
try {
|
|
986
1046
|
const bindings = await listOAuthBindings(jwtToken, XAPI_API_HOST);
|
|
987
|
-
const match = Array.isArray(bindings) ? bindings.find(
|
|
1047
|
+
const match = Array.isArray(bindings) ? bindings.find(
|
|
1048
|
+
(b) => b.apiKeyId === apiKeyId && b.providerId === providerId && bindingChangedAfter(b, startedAtMs, existingBindingIds)
|
|
1049
|
+
) : null;
|
|
988
1050
|
if (match) return match;
|
|
989
1051
|
} catch {
|
|
990
1052
|
}
|
|
@@ -1012,10 +1074,105 @@ async function findCurrentKeyRecord(plaintextKey, jwtToken) {
|
|
|
1012
1074
|
const prefix = plaintextKey.substring(0, 7);
|
|
1013
1075
|
const match = keys.find((k) => k.keyPreview.startsWith(prefix));
|
|
1014
1076
|
if (!match) {
|
|
1015
|
-
|
|
1077
|
+
throw new Error(
|
|
1078
|
+
`Current API key (${prefix}...) was not found in your account keys. Run "xapi-to config set apiKey=<key>" with a valid key before binding OAuth.`
|
|
1079
|
+
);
|
|
1016
1080
|
}
|
|
1017
1081
|
return match;
|
|
1018
1082
|
}
|
|
1083
|
+
function resolveScopeDefs(provider) {
|
|
1084
|
+
if (Array.isArray(provider.scopeDefinitions) && provider.scopeDefinitions.length > 0) {
|
|
1085
|
+
return provider.scopeDefinitions;
|
|
1086
|
+
}
|
|
1087
|
+
const raw = (provider.defaultScopes || "").split(/[\s,]+/).filter(Boolean);
|
|
1088
|
+
return raw.map((s) => ({
|
|
1089
|
+
scope: s,
|
|
1090
|
+
label: s,
|
|
1091
|
+
description: "",
|
|
1092
|
+
required: false,
|
|
1093
|
+
category: ""
|
|
1094
|
+
}));
|
|
1095
|
+
}
|
|
1096
|
+
async function selectScopesInteractive(provider) {
|
|
1097
|
+
const defs = resolveScopeDefs(provider);
|
|
1098
|
+
if (defs.length === 0) return "";
|
|
1099
|
+
const required = defs.filter((d) => d.required);
|
|
1100
|
+
const optional = defs.filter((d) => !d.required);
|
|
1101
|
+
const selected = new Set(defs.map((d) => d.scope));
|
|
1102
|
+
if (optional.length === 0) {
|
|
1103
|
+
return required.map((d) => d.scope).join(" ");
|
|
1104
|
+
}
|
|
1105
|
+
const out = process.stderr;
|
|
1106
|
+
let cursor = 0;
|
|
1107
|
+
const hint = " \u2191\u2193 navigate \xB7 space toggle \xB7 a all \xB7 n none \xB7 enter confirm";
|
|
1108
|
+
const buildFrame = () => {
|
|
1109
|
+
const lines = [];
|
|
1110
|
+
for (const d of required) {
|
|
1111
|
+
const desc = d.description ? ` \u2014 ${d.description}` : "";
|
|
1112
|
+
lines.push(` \x1B[2m[*] ${d.label}${desc} (required)\x1B[0m`);
|
|
1113
|
+
}
|
|
1114
|
+
for (let i = 0; i < optional.length; i++) {
|
|
1115
|
+
const d = optional[i];
|
|
1116
|
+
const ptr = cursor === i ? " \x1B[36m\u276F\x1B[0m" : " ";
|
|
1117
|
+
const chk = selected.has(d.scope) ? "\x1B[32m\u2714\x1B[0m" : " ";
|
|
1118
|
+
const desc = d.description ? ` \x1B[2m\u2014 ${d.description}\x1B[0m` : "";
|
|
1119
|
+
lines.push(` ${ptr} [${chk}] ${d.label}${desc}`);
|
|
1120
|
+
}
|
|
1121
|
+
lines.push(`\x1B[2m${hint}\x1B[0m`);
|
|
1122
|
+
return lines.join("\n");
|
|
1123
|
+
};
|
|
1124
|
+
out.write("\n Scopes:\n");
|
|
1125
|
+
out.write("\x1B[s");
|
|
1126
|
+
out.write("\x1B[?25l");
|
|
1127
|
+
out.write(buildFrame());
|
|
1128
|
+
const redraw = () => {
|
|
1129
|
+
out.write("\x1B[u");
|
|
1130
|
+
out.write("\x1B[J");
|
|
1131
|
+
out.write(buildFrame());
|
|
1132
|
+
};
|
|
1133
|
+
return new Promise((resolve) => {
|
|
1134
|
+
const { stdin } = process;
|
|
1135
|
+
const wasRaw = stdin.isRaw;
|
|
1136
|
+
stdin.setRawMode(true);
|
|
1137
|
+
stdin.resume();
|
|
1138
|
+
const finish = (result) => {
|
|
1139
|
+
stdin.removeListener("data", onData);
|
|
1140
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
1141
|
+
stdin.pause();
|
|
1142
|
+
out.write("\x1B[?25h");
|
|
1143
|
+
out.write("\n");
|
|
1144
|
+
resolve(result);
|
|
1145
|
+
};
|
|
1146
|
+
const onData = (buf) => {
|
|
1147
|
+
const key = buf.toString();
|
|
1148
|
+
if (key === "\r" || key === "\n") {
|
|
1149
|
+
finish(Array.from(selected).join(" "));
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
if (key === "") {
|
|
1153
|
+
finish("");
|
|
1154
|
+
process.exit(130);
|
|
1155
|
+
}
|
|
1156
|
+
if (key === "\x1B[A" || key === "k") {
|
|
1157
|
+
cursor = (cursor - 1 + optional.length) % optional.length;
|
|
1158
|
+
} else if (key === "\x1B[B" || key === "j") {
|
|
1159
|
+
cursor = (cursor + 1) % optional.length;
|
|
1160
|
+
} else if (key === " ") {
|
|
1161
|
+
const scope = optional[cursor].scope;
|
|
1162
|
+
if (selected.has(scope)) selected.delete(scope);
|
|
1163
|
+
else selected.add(scope);
|
|
1164
|
+
} else if (key === "a") {
|
|
1165
|
+
for (const d of optional) selected.add(d.scope);
|
|
1166
|
+
} else if (key === "n") {
|
|
1167
|
+
for (const d of optional) selected.delete(d.scope);
|
|
1168
|
+
} else {
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
redraw();
|
|
1172
|
+
};
|
|
1173
|
+
stdin.on("data", onData);
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1019
1176
|
var OAUTH_HELP = `xapi-to oauth - Manage OAuth bindings
|
|
1020
1177
|
|
|
1021
1178
|
USAGE
|
|
@@ -1029,11 +1186,13 @@ COMMANDS
|
|
|
1029
1186
|
|
|
1030
1187
|
FLAGS
|
|
1031
1188
|
--provider <name> OAuth provider (default: twitter)
|
|
1189
|
+
--scopes <scopes> Space-separated scopes (skips interactive selection)
|
|
1032
1190
|
--format json|pretty|table Output format
|
|
1033
1191
|
|
|
1034
1192
|
EXAMPLES
|
|
1035
1193
|
xapi-to oauth bind
|
|
1036
1194
|
xapi-to oauth bind --provider twitter
|
|
1195
|
+
xapi-to oauth bind --scopes "tweet.read users.read"
|
|
1037
1196
|
xapi-to oauth status
|
|
1038
1197
|
xapi-to oauth status --format pretty
|
|
1039
1198
|
xapi-to oauth unbind abc123
|
|
@@ -1063,13 +1222,49 @@ async function oauthBind(args, flags) {
|
|
|
1063
1222
|
`Provider "${providerName}" not found. Available: ${available}`
|
|
1064
1223
|
);
|
|
1065
1224
|
}
|
|
1066
|
-
|
|
1225
|
+
let scopes;
|
|
1226
|
+
let headerPrinted = false;
|
|
1227
|
+
const isTTY = Boolean(
|
|
1228
|
+
process.stdout.isTTY && process.stdin.isTTY && typeof process.stdin.setRawMode === "function"
|
|
1229
|
+
);
|
|
1230
|
+
if (flags.scopes) {
|
|
1231
|
+
scopes = flags.scopes;
|
|
1232
|
+
} else if (isTTY) {
|
|
1233
|
+
const defs = resolveScopeDefs(provider);
|
|
1234
|
+
if (defs.length > 0) {
|
|
1235
|
+
console.error(`
|
|
1236
|
+
Provider : ${provider.name}`);
|
|
1237
|
+
console.error(` API Key : ${keyRecord.keyPreview}`);
|
|
1238
|
+
headerPrinted = true;
|
|
1239
|
+
scopes = await selectScopesInteractive(provider) || void 0;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
const existingBindingIds = /* @__PURE__ */ new Set();
|
|
1243
|
+
if (isTTY) {
|
|
1244
|
+
try {
|
|
1245
|
+
const existingBindings = await listOAuthBindings(jwtToken, XAPI_API_HOST);
|
|
1246
|
+
if (Array.isArray(existingBindings)) {
|
|
1247
|
+
for (const binding of existingBindings) {
|
|
1248
|
+
if (binding.apiKeyId === keyRecord.id && binding.providerId === provider.id) {
|
|
1249
|
+
existingBindingIds.add(binding.id);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
} catch {
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
const authorizationStartedAt = /* @__PURE__ */ new Date();
|
|
1257
|
+
const result = await initiateOAuth(keyRecord.id, provider.id, jwtToken, XAPI_API_HOST, scopes);
|
|
1067
1258
|
const { authorizationUrl } = result;
|
|
1068
|
-
const isTTY = process.stdout.isTTY;
|
|
1069
1259
|
if (isTTY) {
|
|
1070
|
-
|
|
1260
|
+
if (!headerPrinted) {
|
|
1261
|
+
console.error(`
|
|
1071
1262
|
Provider : ${provider.name}`);
|
|
1072
|
-
|
|
1263
|
+
console.error(` API Key : ${keyRecord.keyPreview}`);
|
|
1264
|
+
}
|
|
1265
|
+
if (scopes) {
|
|
1266
|
+
console.error(` Scopes : ${scopes}`);
|
|
1267
|
+
}
|
|
1073
1268
|
console.error(`
|
|
1074
1269
|
Authorization URL:
|
|
1075
1270
|
${authorizationUrl}
|
|
@@ -1077,14 +1272,20 @@ async function oauthBind(args, flags) {
|
|
|
1077
1272
|
console.error(" Opening browser...");
|
|
1078
1273
|
openBrowser(authorizationUrl);
|
|
1079
1274
|
console.error(" Waiting for you to complete authorization in the browser...\n");
|
|
1080
|
-
const binding = await pollForBinding(
|
|
1275
|
+
const binding = await pollForBinding(
|
|
1276
|
+
keyRecord.id,
|
|
1277
|
+
provider.id,
|
|
1278
|
+
jwtToken,
|
|
1279
|
+
authorizationStartedAt,
|
|
1280
|
+
existingBindingIds
|
|
1281
|
+
);
|
|
1081
1282
|
if (process.stdout.isTTY) process.stdout.write("\n");
|
|
1082
1283
|
if (binding) {
|
|
1083
1284
|
const account = binding.providerAccountName || "unknown";
|
|
1084
1285
|
console.error(`
|
|
1085
1286
|
Authorization complete! Bound to @${account}
|
|
1086
1287
|
`);
|
|
1087
|
-
output({ status: "success", provider: provider.name, account }, flags.format);
|
|
1288
|
+
output({ status: "success", provider: provider.name, account, scopes }, flags.format);
|
|
1088
1289
|
} else {
|
|
1089
1290
|
err("oauth bind timed out", 'Authorization was not completed within 5 minutes. Run "xapi-to oauth bind" again.');
|
|
1090
1291
|
}
|
|
@@ -1093,7 +1294,8 @@ async function oauthBind(args, flags) {
|
|
|
1093
1294
|
status: "pending",
|
|
1094
1295
|
provider: provider.name,
|
|
1095
1296
|
apiKey: keyRecord.keyPreview,
|
|
1096
|
-
authorizationUrl
|
|
1297
|
+
authorizationUrl,
|
|
1298
|
+
scopes
|
|
1097
1299
|
}, flags.format);
|
|
1098
1300
|
}
|
|
1099
1301
|
} catch (e) {
|
|
@@ -1216,6 +1418,7 @@ COMMANDS
|
|
|
1216
1418
|
|
|
1217
1419
|
register [referral-code] Create a new user account (apiKey saved automatically)
|
|
1218
1420
|
--referral-code <code> Register with an inviter's referral code (also: --referralCode, or as positional arg)
|
|
1421
|
+
--force Replace an existing saved apiKey
|
|
1219
1422
|
balance Show current account balance
|
|
1220
1423
|
topup [--amount <usd>] [--method stripe|x402] Generate payment URL
|
|
1221
1424
|
|