apiblaze 0.4.4 → 0.4.7
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/index.js +71 -54
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25,10 +25,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
|
|
26
26
|
// src/index.ts
|
|
27
27
|
var import_commander = require("commander");
|
|
28
|
-
var
|
|
28
|
+
var import_chalk27 = __toESM(require("chalk"));
|
|
29
29
|
|
|
30
30
|
// package.json
|
|
31
|
-
var version = "0.4.
|
|
31
|
+
var version = "0.4.7";
|
|
32
32
|
|
|
33
33
|
// src/types.ts
|
|
34
34
|
var ApiError = class extends Error {
|
|
@@ -1516,12 +1516,12 @@ function openBrowser2(url) {
|
|
|
1516
1516
|
} catch {
|
|
1517
1517
|
}
|
|
1518
1518
|
}
|
|
1519
|
-
async function deviceLogin(clientId, scope, onPrompt) {
|
|
1519
|
+
async function deviceLogin(clientId, scope, onPrompt, resource) {
|
|
1520
1520
|
const { verifier, challenge } = pkce();
|
|
1521
1521
|
const startRes = await fetch(`${AUTH_BASE}/device_authorization`, {
|
|
1522
1522
|
method: "POST",
|
|
1523
1523
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1524
|
-
body: new URLSearchParams({ client_id: clientId, scope, code_challenge: challenge, code_challenge_method: "S256" })
|
|
1524
|
+
body: new URLSearchParams({ client_id: clientId, scope, code_challenge: challenge, code_challenge_method: "S256", ...resource ? { resource } : {} })
|
|
1525
1525
|
});
|
|
1526
1526
|
const start = await startRes.json().catch(() => ({}));
|
|
1527
1527
|
if (!startRes.ok) {
|
|
@@ -1541,7 +1541,11 @@ async function deviceLogin(clientId, scope, onPrompt) {
|
|
|
1541
1541
|
const tokRes = await fetch(`${AUTH_BASE}/token`, {
|
|
1542
1542
|
method: "POST",
|
|
1543
1543
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1544
|
-
|
|
1544
|
+
// RFC 8707 resource indicator: sets the access token's `aud` to the portal
|
|
1545
|
+
// resource so the apikeys plane (which derives accepted auds as
|
|
1546
|
+
// https://{tenant}.portal.apiblaze.com/{ver}) accepts this token. Without it the
|
|
1547
|
+
// device grant falls back to aud=https://auth.apiblaze.com/device/callback → 401.
|
|
1548
|
+
body: new URLSearchParams({ grant_type: DEVICE_GRANT, device_code: deviceCode, code_verifier: verifier, ...resource ? { resource } : {} })
|
|
1545
1549
|
});
|
|
1546
1550
|
const tok = await tokRes.json().catch(() => ({}));
|
|
1547
1551
|
if (tokRes.ok && tok.access_token) {
|
|
@@ -2012,7 +2016,7 @@ function renderTrace() {
|
|
|
2012
2016
|
console.log(import_chalk15.default.dim("\n" + "\u2500".repeat(64)));
|
|
2013
2017
|
console.log(import_chalk15.default.bold(`--verbose: ${entries.length} API call${entries.length === 1 ? "" : "s"} this command made`));
|
|
2014
2018
|
console.log(
|
|
2015
|
-
import_chalk15.default.dim("The same thing on the official API \u2014 copy/paste with your control-plane key\n(get one
|
|
2019
|
+
import_chalk15.default.dim("The same thing on the official API \u2014 copy/paste with your control-plane key\n(get one from the Developers section of dashboard.apiblaze.com, then\n`export APIBLAZE_CONTROLPLANE_APIKEY=sk_...`):\n")
|
|
2016
2020
|
);
|
|
2017
2021
|
entries.forEach((e, i) => {
|
|
2018
2022
|
const n = entries.length > 1 ? import_chalk15.default.bold(`${i + 1}. `) : "";
|
|
@@ -2021,7 +2025,7 @@ function renderTrace() {
|
|
|
2021
2025
|
const masked = maskBody(e.body);
|
|
2022
2026
|
const hasBody = e.method !== "GET" && masked !== void 0;
|
|
2023
2027
|
console.log(import_chalk15.default.green(` curl -sS -X ${e.method} ${url}` + (hasBody ? " \\" : "")));
|
|
2024
|
-
console.log(import_chalk15.default.green(' -H "X-API-Key: $
|
|
2028
|
+
console.log(import_chalk15.default.green(' -H "X-API-Key: $APIBLAZE_CONTROLPLANE_APIKEY"' + (hasBody ? " \\" : "")));
|
|
2025
2029
|
if (hasBody) {
|
|
2026
2030
|
console.log(import_chalk15.default.green(" -H 'Content-Type: application/json' \\"));
|
|
2027
2031
|
console.log(import_chalk15.default.green(` -d '${masked}'`));
|
|
@@ -2566,10 +2570,11 @@ async function runSpecSet(project, opts) {
|
|
|
2566
2570
|
}
|
|
2567
2571
|
|
|
2568
2572
|
// src/commands/agent.ts
|
|
2569
|
-
var
|
|
2573
|
+
var import_chalk25 = __toESM(require("chalk"));
|
|
2570
2574
|
var import_ora11 = __toESM(require("ora"));
|
|
2571
2575
|
|
|
2572
2576
|
// src/lib/tools.ts
|
|
2577
|
+
var import_chalk24 = __toESM(require("chalk"));
|
|
2573
2578
|
async function proj(teamId, name, version2) {
|
|
2574
2579
|
return resolveProject(teamId, name, version2);
|
|
2575
2580
|
}
|
|
@@ -2586,7 +2591,16 @@ var TOOLS = [
|
|
|
2586
2591
|
const key = keys.dev ?? Object.values(keys)[0];
|
|
2587
2592
|
const url = `https://${a.name}.apiblaze.com/${version2}/dev`;
|
|
2588
2593
|
const tryIt = buildTryItCurl(url, auth, key);
|
|
2589
|
-
|
|
2594
|
+
const lines = [` ${import_chalk24.default.dim("Proxy URL:")} ${import_chalk24.default.bold(url)}`];
|
|
2595
|
+
if (res.devPortal) lines.push(` ${import_chalk24.default.dim("Dev portal:")} ${res.devPortal}`);
|
|
2596
|
+
const envs = Object.keys(keys);
|
|
2597
|
+
if (envs.length) {
|
|
2598
|
+
lines.push("", ` ${import_chalk24.default.bold("API keys")} ${import_chalk24.default.dim("(bootstrapped \u2014 send as the X-API-Key header; shown once):")}`);
|
|
2599
|
+
const w = Math.max(...envs.map((e) => e.length));
|
|
2600
|
+
for (const env of envs) lines.push(` ${import_chalk24.default.cyan(env.padEnd(w))} ${import_chalk24.default.green(keys[env])}`);
|
|
2601
|
+
}
|
|
2602
|
+
if (tryIt) lines.push("", ` ${import_chalk24.default.dim("Try it:")}`, ` ${import_chalk24.default.cyan(tryIt)}`);
|
|
2603
|
+
return { ...res, proxy_url: url, keys, ...tryIt ? { try_it: tryIt } : {}, display: lines.join("\n") };
|
|
2590
2604
|
}
|
|
2591
2605
|
},
|
|
2592
2606
|
{
|
|
@@ -2739,17 +2753,17 @@ function truncate(value, max = 1500) {
|
|
|
2739
2753
|
}
|
|
2740
2754
|
function printCost(llm) {
|
|
2741
2755
|
const usd = llm.cost > 0 ? `$${llm.cost.toFixed(4)}` : "<$0.0001";
|
|
2742
|
-
console.log(
|
|
2756
|
+
console.log(import_chalk25.default.magenta(` \u{1F4B3} ${usd}`) + import_chalk25.default.dim(` (${llm.model}, ${llm.total_tokens} tok)`));
|
|
2743
2757
|
}
|
|
2744
2758
|
async function runAgent(opts) {
|
|
2745
2759
|
requireAuth();
|
|
2746
2760
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
2747
2761
|
const { default: inquirer2 } = await import("inquirer");
|
|
2748
|
-
console.log(
|
|
2749
|
-
console.log(
|
|
2762
|
+
console.log(import_chalk25.default.bold("APIblaze agent") + import_chalk25.default.dim(` \xB7 team ${teamName ?? teamId}`));
|
|
2763
|
+
console.log(import_chalk25.default.dim('Ask me to create/delete/configure proxies, tenants, keys, domains, specs. Type "exit" to quit.\n'));
|
|
2750
2764
|
const history = [];
|
|
2751
2765
|
while (true) {
|
|
2752
|
-
const { input } = await inquirer2.prompt([{ type: "input", name: "input", message:
|
|
2766
|
+
const { input } = await inquirer2.prompt([{ type: "input", name: "input", message: import_chalk25.default.cyan("you") + " \u203A" }]);
|
|
2753
2767
|
const text = (input ?? "").trim();
|
|
2754
2768
|
if (!text) continue;
|
|
2755
2769
|
if (["exit", "quit", ":q"].includes(text.toLowerCase())) break;
|
|
@@ -2763,14 +2777,14 @@ async function runAgent(opts) {
|
|
|
2763
2777
|
} catch (err) {
|
|
2764
2778
|
spinner.stop();
|
|
2765
2779
|
if (err instanceof ApiError && err.status === 402) {
|
|
2766
|
-
console.log(
|
|
2780
|
+
console.log(import_chalk25.default.yellow(" Insufficient credits \u2014 top up to keep using the agent."));
|
|
2767
2781
|
break;
|
|
2768
2782
|
}
|
|
2769
2783
|
throw err;
|
|
2770
2784
|
}
|
|
2771
2785
|
history.push({ role: "assistant", content: resp.raw });
|
|
2772
2786
|
printCost(resp.llm);
|
|
2773
|
-
if (resp.reply) console.log(
|
|
2787
|
+
if (resp.reply) console.log(import_chalk25.default.green("agent") + " \u203A " + resp.reply);
|
|
2774
2788
|
if (!resp.action) break;
|
|
2775
2789
|
const tool = findTool(resp.action.tool);
|
|
2776
2790
|
if (!tool) {
|
|
@@ -2781,7 +2795,13 @@ async function runAgent(opts) {
|
|
|
2781
2795
|
try {
|
|
2782
2796
|
const result = await tool.run(resp.action.args, { teamId });
|
|
2783
2797
|
runSpinner.succeed(`${tool.name} \u2713`);
|
|
2784
|
-
|
|
2798
|
+
let forHistory = result;
|
|
2799
|
+
if (result && typeof result === "object" && typeof result.display === "string") {
|
|
2800
|
+
console.log("\n" + result.display + "\n");
|
|
2801
|
+
const { display, ...rest } = result;
|
|
2802
|
+
forHistory = { ...rest, _note: "Details + keys already shown to the user verbatim; do not repeat the keys." };
|
|
2803
|
+
}
|
|
2804
|
+
history.push({ role: "user", content: `TOOL_RESULT ${tool.name}: ${truncate(forHistory)}` });
|
|
2785
2805
|
} catch (err) {
|
|
2786
2806
|
runSpinner.fail(`${tool.name} failed`);
|
|
2787
2807
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -2789,25 +2809,21 @@ async function runAgent(opts) {
|
|
|
2789
2809
|
}
|
|
2790
2810
|
renderTrace();
|
|
2791
2811
|
if (step === MAX_TOOL_STEPS - 1) {
|
|
2792
|
-
console.log(
|
|
2812
|
+
console.log(import_chalk25.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
|
|
2793
2813
|
}
|
|
2794
2814
|
}
|
|
2795
2815
|
}
|
|
2796
|
-
console.log(
|
|
2816
|
+
console.log(import_chalk25.default.dim("\nBye."));
|
|
2797
2817
|
}
|
|
2798
2818
|
|
|
2799
2819
|
// src/commands/consumer.ts
|
|
2800
|
-
var
|
|
2820
|
+
var import_chalk26 = __toESM(require("chalk"));
|
|
2801
2821
|
var import_ora12 = __toESM(require("ora"));
|
|
2802
|
-
var APIKEYS_VER = "1.0.0";
|
|
2803
2822
|
var DEFAULT_SCOPE = "openid email profile offline_access";
|
|
2804
|
-
|
|
2805
|
-
const tmpl = process.env.APIBLAZE_APIKEYS_BASE_TMPL;
|
|
2806
|
-
return tmpl ? tmpl.replace("{tenant}", tenant2) : `https://${tenant2}.apikeys.apiblaze.com`;
|
|
2807
|
-
}
|
|
2823
|
+
var APIKEYS_BASE = process.env.APIBLAZE_APIKEYS_BASE || "https://apikeys.apiblaze.com";
|
|
2808
2824
|
async function consumerFetch(creds, suffix, init) {
|
|
2809
2825
|
const fresh = await validConsumerToken(creds) ?? creds;
|
|
2810
|
-
const res = await fetch(`${
|
|
2826
|
+
const res = await fetch(`${APIKEYS_BASE}${suffix}`, {
|
|
2811
2827
|
...init,
|
|
2812
2828
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${fresh.accessToken}`, ...init?.headers ?? {} }
|
|
2813
2829
|
});
|
|
@@ -2821,7 +2837,7 @@ async function consumerFetch(creds, suffix, init) {
|
|
|
2821
2837
|
function requireConsumer() {
|
|
2822
2838
|
const c = loadConsumer();
|
|
2823
2839
|
if (!c) {
|
|
2824
|
-
console.error(
|
|
2840
|
+
console.error(import_chalk26.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
|
|
2825
2841
|
process.exit(1);
|
|
2826
2842
|
}
|
|
2827
2843
|
return c;
|
|
@@ -2832,7 +2848,7 @@ async function runConsumerLogin(opts) {
|
|
|
2832
2848
|
let clientId = opts.client;
|
|
2833
2849
|
if (clientId) {
|
|
2834
2850
|
if (!tenant2) {
|
|
2835
|
-
console.error(
|
|
2851
|
+
console.error(import_chalk26.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
|
|
2836
2852
|
process.exit(1);
|
|
2837
2853
|
}
|
|
2838
2854
|
} else {
|
|
@@ -2845,7 +2861,7 @@ async function runConsumerLogin(opts) {
|
|
|
2845
2861
|
(t) => typeof t === "string" ? { tenant_name: t } : t
|
|
2846
2862
|
);
|
|
2847
2863
|
if (!tenants.length) {
|
|
2848
|
-
console.error(
|
|
2864
|
+
console.error(import_chalk26.default.red("This team has no tenants. Create one with `apiblaze tenant create`."));
|
|
2849
2865
|
process.exit(1);
|
|
2850
2866
|
}
|
|
2851
2867
|
if (!tenant2) {
|
|
@@ -2861,19 +2877,20 @@ async function runConsumerLogin(opts) {
|
|
|
2861
2877
|
const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
|
|
2862
2878
|
const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
|
|
2863
2879
|
if (!pick2) {
|
|
2864
|
-
console.error(
|
|
2880
|
+
console.error(import_chalk26.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
|
|
2865
2881
|
process.exit(1);
|
|
2866
2882
|
}
|
|
2867
2883
|
clientId = pick2.client_id ?? pick2.clientId;
|
|
2868
2884
|
}
|
|
2869
|
-
|
|
2885
|
+
const portalResource = `https://${tenant2}.portal.apiblaze.com/1.0.0`;
|
|
2886
|
+
console.log(`${import_chalk26.default.cyan("\u2192")} Logging in to ${import_chalk26.default.bold(tenant2)} as a consumer...`);
|
|
2870
2887
|
const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
|
|
2871
2888
|
console.log(`
|
|
2872
|
-
Open: ${
|
|
2873
|
-
console.log(` Code: ${
|
|
2889
|
+
Open: ${import_chalk26.default.underline(verificationUri)}`);
|
|
2890
|
+
console.log(` Code: ${import_chalk26.default.bold(userCode)}
|
|
2874
2891
|
`);
|
|
2875
|
-
console.log(
|
|
2876
|
-
});
|
|
2892
|
+
console.log(import_chalk26.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
|
|
2893
|
+
}, portalResource);
|
|
2877
2894
|
const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
|
|
2878
2895
|
const creds = {
|
|
2879
2896
|
tenant: tenant2,
|
|
@@ -2887,7 +2904,7 @@ async function runConsumerLogin(opts) {
|
|
|
2887
2904
|
obtainedAt: Date.now()
|
|
2888
2905
|
};
|
|
2889
2906
|
saveConsumer(creds);
|
|
2890
|
-
console.log(
|
|
2907
|
+
console.log(import_chalk26.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
|
|
2891
2908
|
}
|
|
2892
2909
|
async function runConsumerTokens(opts) {
|
|
2893
2910
|
const creds = requireConsumer();
|
|
@@ -2900,18 +2917,18 @@ async function runConsumerTokens(opts) {
|
|
|
2900
2917
|
console.log(JSON.stringify({ tenant: fresh.tenant, access_token: fresh.accessToken, refresh_token: fresh.refreshToken, id_token: fresh.idToken, expires_at: new Date(fresh.expiresAt).toISOString() }, null, 2));
|
|
2901
2918
|
return;
|
|
2902
2919
|
}
|
|
2903
|
-
console.log(`${
|
|
2920
|
+
console.log(`${import_chalk26.default.cyan("Consumer")} ${import_chalk26.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk26.default.bold(fresh.tenant)}
|
|
2904
2921
|
`);
|
|
2905
|
-
console.log(`${
|
|
2922
|
+
console.log(`${import_chalk26.default.bold("access_token")} ${import_chalk26.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
|
|
2906
2923
|
${fresh.accessToken}
|
|
2907
2924
|
`);
|
|
2908
|
-
if (fresh.idToken) console.log(`${
|
|
2925
|
+
if (fresh.idToken) console.log(`${import_chalk26.default.bold("id_token")} ${import_chalk26.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
|
|
2909
2926
|
${fresh.idToken}
|
|
2910
2927
|
`);
|
|
2911
|
-
if (fresh.refreshToken) console.log(`${
|
|
2928
|
+
if (fresh.refreshToken) console.log(`${import_chalk26.default.bold("refresh_token")}
|
|
2912
2929
|
${fresh.refreshToken}
|
|
2913
2930
|
`);
|
|
2914
|
-
console.log(
|
|
2931
|
+
console.log(import_chalk26.default.dim("These are your own tokens \u2014 keep them secret."));
|
|
2915
2932
|
}
|
|
2916
2933
|
async function runConsumerApikeys(opts) {
|
|
2917
2934
|
const creds = requireConsumer();
|
|
@@ -2921,8 +2938,8 @@ async function runConsumerApikeys(opts) {
|
|
|
2921
2938
|
const revealed = await consumerFetch(list.creds, "/apikeys/reveal").catch(() => ({ status: 0, data: null, creds: list.creds }));
|
|
2922
2939
|
spinner.stop();
|
|
2923
2940
|
if (list.status >= 400) {
|
|
2924
|
-
console.error(
|
|
2925
|
-
if (list.status === 401) console.error(
|
|
2941
|
+
console.error(import_chalk26.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
|
|
2942
|
+
if (list.status === 401) console.error(import_chalk26.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
|
|
2926
2943
|
process.exit(1);
|
|
2927
2944
|
}
|
|
2928
2945
|
const keys = list.data?.keys ?? [];
|
|
@@ -2930,16 +2947,16 @@ async function runConsumerApikeys(opts) {
|
|
|
2930
2947
|
if (opts.json) {
|
|
2931
2948
|
console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
|
|
2932
2949
|
} else if (!keys.length) {
|
|
2933
|
-
console.log(
|
|
2950
|
+
console.log(import_chalk26.default.yellow("No API keys yet."));
|
|
2934
2951
|
} else {
|
|
2935
2952
|
for (const k of keys) {
|
|
2936
2953
|
const clear = revealMap[k.environment]?.key;
|
|
2937
|
-
const shown = clear ?
|
|
2938
|
-
const exp = k.expires_at ?
|
|
2939
|
-
console.log(` ${
|
|
2954
|
+
const shown = clear ? import_chalk26.default.green(clear) : import_chalk26.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
|
|
2955
|
+
const exp = k.expires_at ? import_chalk26.default.dim(`exp ${k.expires_at}`) : import_chalk26.default.dim("no expiry");
|
|
2956
|
+
console.log(` ${import_chalk26.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk26.default.dim(k.description ?? "")}`);
|
|
2940
2957
|
}
|
|
2941
2958
|
if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
|
|
2942
|
-
console.log(
|
|
2959
|
+
console.log(import_chalk26.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
|
|
2943
2960
|
}
|
|
2944
2961
|
}
|
|
2945
2962
|
if (opts.json) return;
|
|
@@ -2961,8 +2978,8 @@ async function runConsumerApikeys(opts) {
|
|
|
2961
2978
|
}
|
|
2962
2979
|
s2.succeed("Key created.");
|
|
2963
2980
|
const key = created.data?.key ?? created.data?.fullKey;
|
|
2964
|
-
if (key) console.log(` ${
|
|
2965
|
-
else console.log(
|
|
2981
|
+
if (key) console.log(` ${import_chalk26.default.green(key)} ${import_chalk26.default.dim("(shown once \u2014 store it now)")}`);
|
|
2982
|
+
else console.log(import_chalk26.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
|
|
2966
2983
|
}
|
|
2967
2984
|
|
|
2968
2985
|
// src/index.ts
|
|
@@ -3008,7 +3025,7 @@ program.command("dev").description("Put your localhost behind a public URL (dev
|
|
|
3008
3025
|
try {
|
|
3009
3026
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
3010
3027
|
if (Number.isNaN(resolved)) {
|
|
3011
|
-
console.error(
|
|
3028
|
+
console.error(import_chalk27.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
3012
3029
|
process.exit(1);
|
|
3013
3030
|
}
|
|
3014
3031
|
await runDev({ port: resolved, captureFile: opts.captureFile });
|
|
@@ -3093,7 +3110,7 @@ function groupedCommandHelp() {
|
|
|
3093
3110
|
const c = byName.get(n);
|
|
3094
3111
|
return c ? ` ${n.padEnd(width)}${c.description()}` : "";
|
|
3095
3112
|
}).filter(Boolean).join("\n");
|
|
3096
|
-
return `${
|
|
3113
|
+
return `${import_chalk27.default.bold(g.title)}
|
|
3097
3114
|
${rows}`;
|
|
3098
3115
|
}).join("\n\n");
|
|
3099
3116
|
}
|
|
@@ -3119,13 +3136,13 @@ Examples:
|
|
|
3119
3136
|
`);
|
|
3120
3137
|
function printError(err) {
|
|
3121
3138
|
if (err instanceof ApiError) {
|
|
3122
|
-
console.error(
|
|
3139
|
+
console.error(import_chalk27.default.red(`
|
|
3123
3140
|
API error (${err.status}): ${err.message}`));
|
|
3124
3141
|
} else if (err instanceof Error) {
|
|
3125
|
-
console.error(
|
|
3142
|
+
console.error(import_chalk27.default.red(`
|
|
3126
3143
|
Error: ${err.message}`));
|
|
3127
3144
|
} else {
|
|
3128
|
-
console.error(
|
|
3145
|
+
console.error(import_chalk27.default.red("\nUnknown error"));
|
|
3129
3146
|
}
|
|
3130
3147
|
}
|
|
3131
3148
|
program.parse(process.argv);
|