@usesocial/cli 0.11.5 → 0.11.6
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 +6 -0
- package/README.md +3 -3
- package/dist/index.mjs +28 -129
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @usesocial/cli
|
|
2
2
|
|
|
3
|
+
## 0.11.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#59](https://github.com/usesocial/monorepo/pull/59) [`d6f5807`](https://github.com/usesocial/monorepo/commit/d6f58074561524aff14fccb831785fcbd31b4216) Thanks [@CyrusNuevoDia](https://github.com/CyrusNuevoDia)! - Bind X account connect links to the authenticated CLI user so copied OAuth URLs cannot attach to the wrong usesocial web session.
|
|
8
|
+
|
|
3
9
|
## 0.11.5
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -142,14 +142,14 @@ profile lookup is needed.
|
|
|
142
142
|
| `(bare)` | Show who is signed in and list connected accounts. | |
|
|
143
143
|
| `login` | Device sign-in, scope selection, and billing. | |
|
|
144
144
|
| `logout` | Revoke the session and clear the saved token. | |
|
|
145
|
-
| `connect linkedin` |
|
|
146
|
-
| `connect x` |
|
|
145
|
+
| `connect linkedin` | Print a LinkedIn connection URL and poll for approval. | write |
|
|
146
|
+
| `connect x` | Print an X OAuth URL and poll for approval. | write |
|
|
147
147
|
| `reconnect linkedin <account>` | Re-auth an existing LinkedIn account. | write |
|
|
148
148
|
| `reconnect x <account>` | Re-auth an existing X account. | write |
|
|
149
149
|
| `disconnect linkedin <account>` | Disconnect a LinkedIn account. | write |
|
|
150
150
|
| `disconnect x <account>` | Disconnect an X account. | write |
|
|
151
151
|
| `billing` | Show seats, subscription, and current usage billing. | |
|
|
152
|
-
| `billing portal` |
|
|
152
|
+
| `billing portal` | Print the hosted billing portal URL. | |
|
|
153
153
|
| `usage` | Aggregate proxy calls and usage. | |
|
|
154
154
|
| `logs` | Recent proxy calls. | |
|
|
155
155
|
| `config account` | Read or set the default connected account selector. | local |
|
package/dist/index.mjs
CHANGED
|
@@ -15,7 +15,6 @@ import l__default, { createInterface } from "node:readline";
|
|
|
15
15
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
16
16
|
import process$1, { stdin, stdout } from "node:process";
|
|
17
17
|
import { ReadStream } from "node:tty";
|
|
18
|
-
import { spawn } from "node:child_process";
|
|
19
18
|
//#region \0rolldown/runtime.js
|
|
20
19
|
var __create = Object.create;
|
|
21
20
|
var __defProp = Object.defineProperty;
|
|
@@ -8204,7 +8203,7 @@ async function resolvePlugins(plugins) {
|
|
|
8204
8203
|
function defineCommand(def) {
|
|
8205
8204
|
return def;
|
|
8206
8205
|
}
|
|
8207
|
-
async function runCommand
|
|
8206
|
+
async function runCommand(cmd, opts) {
|
|
8208
8207
|
const cmdArgs = await resolveValue$1(cmd.args || {});
|
|
8209
8208
|
const parsedArgs = parseArgs$1(opts.rawArgs, cmdArgs);
|
|
8210
8209
|
const context = {
|
|
@@ -8226,14 +8225,14 @@ async function runCommand$1(cmd, opts) {
|
|
|
8226
8225
|
if (explicitName) {
|
|
8227
8226
|
const subCommand = await _findSubCommand(subCommands, explicitName);
|
|
8228
8227
|
if (!subCommand) throw new CLIError(`Unknown command ${cyan(explicitName)}`, "E_UNKNOWN_COMMAND");
|
|
8229
|
-
await runCommand
|
|
8228
|
+
await runCommand(subCommand, { rawArgs: opts.rawArgs.slice(subCommandArgIndex + 1) });
|
|
8230
8229
|
} else {
|
|
8231
8230
|
const defaultSubCommand = await resolveValue$1(cmd.default);
|
|
8232
8231
|
if (defaultSubCommand) {
|
|
8233
8232
|
if (cmd.run) throw new CLIError(`Cannot specify both 'run' and 'default' on the same command.`, "E_DEFAULT_CONFLICT");
|
|
8234
8233
|
const subCommand = await _findSubCommand(subCommands, defaultSubCommand);
|
|
8235
8234
|
if (!subCommand) throw new CLIError(`Default sub command ${cyan(defaultSubCommand)} not found in subCommands.`, "E_UNKNOWN_COMMAND");
|
|
8236
|
-
await runCommand
|
|
8235
|
+
await runCommand(subCommand, { rawArgs: opts.rawArgs });
|
|
8237
8236
|
} else if (!cmd.run) throw new CLIError(`No command specified.`, "E_NO_COMMAND");
|
|
8238
8237
|
}
|
|
8239
8238
|
}
|
|
@@ -21509,15 +21508,9 @@ const LINKEDIN_PLATFORM = "linkedin";
|
|
|
21509
21508
|
const DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
21510
21509
|
const DEFAULT_POLL_TIMEOUT_MS = 12e4;
|
|
21511
21510
|
const SEAT_POLL_TIMEOUT_MS$2 = 600 * 1e3;
|
|
21512
|
-
const
|
|
21513
|
-
const openURL = async (deps, url, options = {}) => {
|
|
21511
|
+
const surfaceURL$1 = (deps, url, options = {}) => {
|
|
21514
21512
|
const log = deps.log ?? console.error;
|
|
21515
|
-
if (options.log ?? true) log(`
|
|
21516
|
-
if (!(isInteractiveTerminal$1(deps) && deps.openURL)) return {
|
|
21517
|
-
opened: false,
|
|
21518
|
-
url
|
|
21519
|
-
};
|
|
21520
|
-
return await Promise.resolve(deps.openURL(url));
|
|
21513
|
+
if (options.log ?? true) log(`Open this URL: ${url}`);
|
|
21521
21514
|
};
|
|
21522
21515
|
const pollForSeat$2 = async (deps) => {
|
|
21523
21516
|
const intervalMs = deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
@@ -21533,7 +21526,7 @@ const prepareAccountConnect$1 = async (deps) => {
|
|
|
21533
21526
|
if ((await deps.client.billing.status()).canConnect) return;
|
|
21534
21527
|
const status = await deps.client.billing.prepareAccountConnect();
|
|
21535
21528
|
if (status.canConnect) return;
|
|
21536
|
-
if (status.paymentURL)
|
|
21529
|
+
if (status.paymentURL) surfaceURL$1(deps, status.paymentURL);
|
|
21537
21530
|
await pollForSeat$2(deps);
|
|
21538
21531
|
};
|
|
21539
21532
|
const accountUsername = (account) => account.username.replace(/^@+/, "");
|
|
@@ -21583,7 +21576,7 @@ const connectLinkedinAccount = async (deps) => {
|
|
|
21583
21576
|
platform: LINKEDIN_PLATFORM,
|
|
21584
21577
|
mode: "create"
|
|
21585
21578
|
})).url);
|
|
21586
|
-
|
|
21579
|
+
surfaceURL$1(deps, authURL.url, { log: !authURL.openingLogged });
|
|
21587
21580
|
return {
|
|
21588
21581
|
platform: LINKEDIN_PLATFORM,
|
|
21589
21582
|
status: "connected",
|
|
@@ -21597,7 +21590,7 @@ const reconnectLinkedinAccount = async (deps, identifier) => {
|
|
|
21597
21590
|
mode: "reconnect",
|
|
21598
21591
|
account: reconnectCommandTargetFor(account)
|
|
21599
21592
|
})).url);
|
|
21600
|
-
|
|
21593
|
+
surfaceURL$1(deps, authURL.url, { log: !authURL.openingLogged });
|
|
21601
21594
|
return {
|
|
21602
21595
|
platform: LINKEDIN_PLATFORM,
|
|
21603
21596
|
status: "connected",
|
|
@@ -21669,20 +21662,16 @@ const throwXLifecycleError = (error) => {
|
|
|
21669
21662
|
const defaultSleep = async (ms) => {
|
|
21670
21663
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
21671
21664
|
};
|
|
21672
|
-
const resolveWebBaseURL = async (deps) => {
|
|
21673
|
-
return (typeof deps.webBaseURL === "function" ? await deps.webBaseURL() : deps.webBaseURL).replace(/\/+$/, "");
|
|
21674
|
-
};
|
|
21675
21665
|
const connectURLFor = async (deps, reconnectProfileId) => {
|
|
21676
|
-
|
|
21677
|
-
|
|
21678
|
-
|
|
21666
|
+
return (await deps.client.accounts.reconnectURL({
|
|
21667
|
+
platform: "x",
|
|
21668
|
+
mode: reconnectProfileId ? "reconnect" : "create",
|
|
21669
|
+
...reconnectProfileId ? { account: `profile_id:${reconnectProfileId}` } : {}
|
|
21670
|
+
})).url;
|
|
21679
21671
|
};
|
|
21680
|
-
const
|
|
21681
|
-
const openOrPrint = async (deps, url, options = {}) => {
|
|
21672
|
+
const surfaceURL = (deps, url, options = {}) => {
|
|
21682
21673
|
const log = deps.log ?? console.error;
|
|
21683
|
-
if (options.log ?? true) log(`
|
|
21684
|
-
if (!(isInteractiveTerminal(deps) && deps.openBrowser)) return;
|
|
21685
|
-
await deps.openBrowser(url);
|
|
21674
|
+
if (options.log ?? true) log(`Open this URL: ${url}`);
|
|
21686
21675
|
};
|
|
21687
21676
|
const pollForAccount = async (deps, operation, matches) => {
|
|
21688
21677
|
const sleep = deps.sleep ?? defaultSleep;
|
|
@@ -21719,7 +21708,7 @@ const prepareAccountConnect = async (deps) => {
|
|
|
21719
21708
|
if ((await deps.client.billing.status()).canConnect) return G.ok();
|
|
21720
21709
|
const status = await deps.client.billing.prepareAccountConnect();
|
|
21721
21710
|
if (status.canConnect) return G.ok();
|
|
21722
|
-
if (status.paymentURL)
|
|
21711
|
+
if (status.paymentURL) surfaceURL(deps, status.paymentURL);
|
|
21723
21712
|
return pollForSeat$1(deps);
|
|
21724
21713
|
};
|
|
21725
21714
|
const accountKey = (account) => account.profileId;
|
|
@@ -21768,7 +21757,7 @@ const connectXAccount = async (deps) => {
|
|
|
21768
21757
|
const prepared = await prepareAccountConnect(deps);
|
|
21769
21758
|
if (G.isError(prepared)) return G.err(prepared.error);
|
|
21770
21759
|
const authURL = await authURLFor(deps, () => connectURLFor(deps));
|
|
21771
|
-
|
|
21760
|
+
surfaceURL(deps, authURL.url, { log: !authURL.openingLogged });
|
|
21772
21761
|
const account = await pollForAccount(deps, "connect", (candidate) => {
|
|
21773
21762
|
if (candidate.status !== "connected") return false;
|
|
21774
21763
|
if (!connectedBefore.has(candidate.profileId)) return true;
|
|
@@ -21795,7 +21784,7 @@ const reconnectXAccount = async (deps, args) => {
|
|
|
21795
21784
|
account: args.account
|
|
21796
21785
|
}));
|
|
21797
21786
|
const authURL = await authURLFor(deps, () => connectURLFor(deps, existing.profileId));
|
|
21798
|
-
|
|
21787
|
+
surfaceURL(deps, authURL.url, { log: !authURL.openingLogged });
|
|
21799
21788
|
const account = await pollForAccount(deps, "reconnect", (candidate) => candidate.profileId === existing.profileId && candidate.status === "connected");
|
|
21800
21789
|
if (G.isError(account)) return G.err(account.error);
|
|
21801
21790
|
return G.ok({
|
|
@@ -23183,7 +23172,7 @@ function createEnv(opts) {
|
|
|
23183
23172
|
}
|
|
23184
23173
|
//#endregion
|
|
23185
23174
|
//#region package.json
|
|
23186
|
-
var version$1 = "0.11.
|
|
23175
|
+
var version$1 = "0.11.6";
|
|
23187
23176
|
//#endregion
|
|
23188
23177
|
//#region src/lib/env.ts
|
|
23189
23178
|
const URLWithTrailingSlash = url().transform(ensureTrailingSlash);
|
|
@@ -24378,22 +24367,21 @@ const createBillingCommand = (deps = {}) => {
|
|
|
24378
24367
|
const portal = defineCommand({
|
|
24379
24368
|
meta: commandMeta({
|
|
24380
24369
|
name: "portal",
|
|
24381
|
-
description: "
|
|
24370
|
+
description: "Print the hosted billing portal URL.",
|
|
24382
24371
|
capability: "read",
|
|
24383
24372
|
contract: readCLIContract({
|
|
24384
24373
|
outputSchema: BillingPortalOutput,
|
|
24385
24374
|
hazards: [{
|
|
24386
24375
|
code: "billing_portal_handoff",
|
|
24387
|
-
message: "Hosted billing portal handoff; billing changes happen outside the CLI after the URL
|
|
24376
|
+
message: "Hosted billing portal handoff; billing changes happen outside the CLI after the URL is opened."
|
|
24388
24377
|
}]
|
|
24389
24378
|
})
|
|
24390
24379
|
}),
|
|
24391
24380
|
run: async () => {
|
|
24392
24381
|
const { url } = await clientFrom(deps).billing.portalURL();
|
|
24393
|
-
const result = deps.isInteractiveTerminal?.() === true && deps.openURL !== void 0 ? await deps.openURL?.(url) : void 0;
|
|
24394
24382
|
await writeOutput$2(deps, {
|
|
24395
|
-
url
|
|
24396
|
-
opened:
|
|
24383
|
+
url,
|
|
24384
|
+
opened: false
|
|
24397
24385
|
});
|
|
24398
24386
|
}
|
|
24399
24387
|
});
|
|
@@ -30435,85 +30423,6 @@ const scopePhase = async (ctx) => {
|
|
|
30435
30423
|
};
|
|
30436
30424
|
};
|
|
30437
30425
|
//#endregion
|
|
30438
|
-
//#region src/lib/browser.ts
|
|
30439
|
-
const WSL_VERSION = /microsoft|wsl/i;
|
|
30440
|
-
const BROWSER_TARGET_TIMEOUT_MS = 5e3;
|
|
30441
|
-
const LOCAL_HOSTS = new Set([
|
|
30442
|
-
"localhost",
|
|
30443
|
-
"127.0.0.1",
|
|
30444
|
-
"::1"
|
|
30445
|
-
]);
|
|
30446
|
-
const displayURL = (url) => {
|
|
30447
|
-
try {
|
|
30448
|
-
const parsed = new URL(url);
|
|
30449
|
-
parsed.searchParams.delete("token");
|
|
30450
|
-
parsed.hash = "";
|
|
30451
|
-
return parsed.toString();
|
|
30452
|
-
} catch {
|
|
30453
|
-
return url;
|
|
30454
|
-
}
|
|
30455
|
-
};
|
|
30456
|
-
const hintFor = (url) => LOCAL_HOSTS.has(url.hostname) ? "Start the web app or local worker, or set SOCIAL_API_URL to your deployed social API." : "Check SOCIAL_API_URL and your connection, then try again.";
|
|
30457
|
-
const assertBrowserTargetReachable = async (url) => {
|
|
30458
|
-
let parsed;
|
|
30459
|
-
try {
|
|
30460
|
-
parsed = new URL(url);
|
|
30461
|
-
} catch {
|
|
30462
|
-
return;
|
|
30463
|
-
}
|
|
30464
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return;
|
|
30465
|
-
if (!LOCAL_HOSTS.has(parsed.hostname)) return;
|
|
30466
|
-
const controller = new AbortController();
|
|
30467
|
-
const timeout = setTimeout(() => controller.abort(), BROWSER_TARGET_TIMEOUT_MS);
|
|
30468
|
-
const target = parsed.origin;
|
|
30469
|
-
try {
|
|
30470
|
-
const response = await fetch(target, {
|
|
30471
|
-
method: "GET",
|
|
30472
|
-
redirect: "manual",
|
|
30473
|
-
signal: controller.signal
|
|
30474
|
-
});
|
|
30475
|
-
if (response.status >= 400) throw new Error(`social is reachable at ${displayURL(target)}, but it returned ${response.status}. Try again in a moment.`);
|
|
30476
|
-
} catch (error) {
|
|
30477
|
-
if (error instanceof Error && error.message.startsWith("social is reachable")) throw error;
|
|
30478
|
-
throw new Error(`Can't reach ${displayURL(target)}. ${hintFor(parsed)}`);
|
|
30479
|
-
} finally {
|
|
30480
|
-
clearTimeout(timeout);
|
|
30481
|
-
}
|
|
30482
|
-
};
|
|
30483
|
-
const runCommand = async (command) => await new Promise((resolve) => {
|
|
30484
|
-
const [file, ...args] = command;
|
|
30485
|
-
if (!file) {
|
|
30486
|
-
resolve(false);
|
|
30487
|
-
return;
|
|
30488
|
-
}
|
|
30489
|
-
const proc = spawn(file, args, { stdio: "ignore" });
|
|
30490
|
-
proc.once("error", () => resolve(false));
|
|
30491
|
-
proc.once("exit", (code) => resolve(code === 0));
|
|
30492
|
-
});
|
|
30493
|
-
const isWSL = async () => {
|
|
30494
|
-
if (env.WSL_DISTRO_NAME || env.WSL_INTEROP) return true;
|
|
30495
|
-
const version = await readFile("/proc/version", "utf8").catch(() => "");
|
|
30496
|
-
return WSL_VERSION.test(version);
|
|
30497
|
-
};
|
|
30498
|
-
const openBrowser = async (url) => {
|
|
30499
|
-
await assertBrowserTargetReachable(url);
|
|
30500
|
-
if (process.platform === "darwin") return await runCommand(["open", url]);
|
|
30501
|
-
if (process.platform === "win32") return await runCommand([
|
|
30502
|
-
"cmd",
|
|
30503
|
-
"/c",
|
|
30504
|
-
"start",
|
|
30505
|
-
url
|
|
30506
|
-
]);
|
|
30507
|
-
if (await isWSL()) return await runCommand(["wslview", url]) || await runCommand([
|
|
30508
|
-
"powershell.exe",
|
|
30509
|
-
"-NoProfile",
|
|
30510
|
-
"-Command",
|
|
30511
|
-
"Start-Process",
|
|
30512
|
-
url
|
|
30513
|
-
]);
|
|
30514
|
-
return await runCommand(["xdg-open", url]);
|
|
30515
|
-
};
|
|
30516
|
-
//#endregion
|
|
30517
30426
|
//#region src/login/phases/2.seat.ts
|
|
30518
30427
|
const SEAT_BILLING_DETAILS_URL = publicRouteURLs.pricing;
|
|
30519
30428
|
const SEAT_PRICING_MESSAGE = `${productBrand.name} costs $20/month/account + usage. Full details at ${SEAT_BILLING_DETAILS_URL}. Continue?`;
|
|
@@ -30535,7 +30444,6 @@ const pollForSeat = async (ctx, deps = {}) => {
|
|
|
30535
30444
|
throw new Error("Billing checkout did not complete within 30 minutes. Finish checkout in the browser, then run `social account login` again.");
|
|
30536
30445
|
};
|
|
30537
30446
|
const runSeatPhase = async (ctx, deps = {}) => {
|
|
30538
|
-
const openBrowser$1 = deps.openBrowser ?? openBrowser;
|
|
30539
30447
|
const status = await ctx.client.billing.status();
|
|
30540
30448
|
if (seatAvailable(status)) return {
|
|
30541
30449
|
status: "done",
|
|
@@ -30551,7 +30459,6 @@ const runSeatPhase = async (ctx, deps = {}) => {
|
|
|
30551
30459
|
};
|
|
30552
30460
|
const checkout = await ctx.client.billing.checkoutURL({ returnPath: SEAT_CHECKOUT_RETURN_PATH });
|
|
30553
30461
|
ctx.ui.info(checkout.url);
|
|
30554
|
-
await openBrowser$1(checkout.url);
|
|
30555
30462
|
return {
|
|
30556
30463
|
status: "done",
|
|
30557
30464
|
data: await ctx.ui.spinElapsed("Waiting for billing checkout", () => pollForSeat(ctx, deps), "Billing ready")
|
|
@@ -31329,7 +31236,7 @@ const lifecycleDepsWithTextLogs = (deps) => ({
|
|
|
31329
31236
|
spinner.start(authHandshakeMessage);
|
|
31330
31237
|
try {
|
|
31331
31238
|
const url = await operation();
|
|
31332
|
-
spinner.stop(`
|
|
31239
|
+
spinner.stop(`Open this URL: ${url}`);
|
|
31333
31240
|
return url;
|
|
31334
31241
|
} catch (error) {
|
|
31335
31242
|
spinner.error(authHandshakeMessage);
|
|
@@ -31338,7 +31245,7 @@ const lifecycleDepsWithTextLogs = (deps) => ({
|
|
|
31338
31245
|
}
|
|
31339
31246
|
writeText(deps, authHandshakeMessage);
|
|
31340
31247
|
const url = await operation();
|
|
31341
|
-
writeText(deps, `
|
|
31248
|
+
writeText(deps, `Open this URL: ${url}`);
|
|
31342
31249
|
return url;
|
|
31343
31250
|
}
|
|
31344
31251
|
});
|
|
@@ -31363,7 +31270,7 @@ const runConnectStep = async (deps, platform, listConnected, connectURL) => {
|
|
|
31363
31270
|
return;
|
|
31364
31271
|
}
|
|
31365
31272
|
const url = await connectURL();
|
|
31366
|
-
writeText(deps, `
|
|
31273
|
+
writeText(deps, `Open this URL: ${url}`);
|
|
31367
31274
|
await writeOutput(deps, {
|
|
31368
31275
|
status: "pending_approval",
|
|
31369
31276
|
platform,
|
|
@@ -36704,6 +36611,7 @@ const createCLIDeps = () => {
|
|
|
36704
36611
|
disconnectAccount: client.accounts.disconnect,
|
|
36705
36612
|
accounts: {
|
|
36706
36613
|
list: client.accounts.list,
|
|
36614
|
+
reconnectURL: client.accounts.reconnectURL,
|
|
36707
36615
|
disconnect: client.accounts.disconnect
|
|
36708
36616
|
},
|
|
36709
36617
|
billing: {
|
|
@@ -36794,17 +36702,8 @@ const createCLIDeps = () => {
|
|
|
36794
36702
|
providerId: profile.id
|
|
36795
36703
|
};
|
|
36796
36704
|
},
|
|
36797
|
-
openBrowser,
|
|
36798
36705
|
isInteractiveTerminal: () => process.stdout.isTTY === true,
|
|
36799
36706
|
webBaseURL: () => env.SOCIAL_WEB_URL,
|
|
36800
|
-
openURL: async (url) => {
|
|
36801
|
-
const opened = await openBrowser(url);
|
|
36802
|
-
if (!opened) console.error(url);
|
|
36803
|
-
return {
|
|
36804
|
-
opened,
|
|
36805
|
-
url
|
|
36806
|
-
};
|
|
36807
|
-
},
|
|
36808
36707
|
writeOutput: printData,
|
|
36809
36708
|
ui: createUI(process.stdout.isTTY === true)
|
|
36810
36709
|
};
|
|
@@ -37489,7 +37388,7 @@ const runSocial = async ({ command = createSocialCommand(), now = Date.now, rawA
|
|
|
37489
37388
|
try {
|
|
37490
37389
|
const loginArgError = loginExtraArgError(rawArgs, path);
|
|
37491
37390
|
if (loginArgError) throw loginArgError;
|
|
37492
|
-
await runCommand
|
|
37391
|
+
await runCommand(command, { rawArgs });
|
|
37493
37392
|
} catch (error) {
|
|
37494
37393
|
if (error instanceof SilentExit) return await finish(error.code, telemetry, now);
|
|
37495
37394
|
if (isCittyUsageError(error) && process.stderr.isTTY) await printUsage(command, rawArgs);
|