pi-provider-qoder-cn 0.2.1 → 0.2.3
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 +54 -11
- package/dist/index.js +201 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -64,14 +64,18 @@ export QODERCN_PERSONAL_ACCESS_TOKEN="<your PAT>"
|
|
|
64
64
|
|
|
65
65
|
### `/qoder-cn.usage`
|
|
66
66
|
|
|
67
|
-
Shows the plan allowance and any purchased add-on credits.
|
|
67
|
+
Shows the plan allowance and any purchased add-on credits as one aligned grid.
|
|
68
|
+
In a terminal the gauge is coloured by how much of the allowance is gone —
|
|
69
|
+
green under 60%, yellow to 85%, red above that, and bold red once the quota is
|
|
70
|
+
exceeded.
|
|
68
71
|
|
|
69
72
|
```text
|
|
70
73
|
Qoder CN Plan (personal_standard)
|
|
71
|
-
[
|
|
72
|
-
[
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
Overall [███░░░░░░░░░░░░░░░░░░] 12.5% used
|
|
75
|
+
Plan quota [███░░░░░░░░░░░░░░░░░░] 250 / 2000 credits 12.5% 1750 left
|
|
76
|
+
Add-on [░░░░░░░░░░░░░░░░░░░░░] 0 / 700 credits 0% 700 left
|
|
77
|
+
Resets never
|
|
78
|
+
Manage https://qoder.com.cn/account/usage
|
|
75
79
|
```
|
|
76
80
|
|
|
77
81
|
Append `json` to print the untouched API payload — useful when a field you need
|
|
@@ -81,13 +85,24 @@ is not in the formatted view:
|
|
|
81
85
|
/qoder-cn.usage json
|
|
82
86
|
```
|
|
83
87
|
|
|
88
|
+
Append `plain` for the same grid without the colour codes, e.g. when pasting it
|
|
89
|
+
into an issue:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
/qoder-cn.usage plain
|
|
93
|
+
```
|
|
94
|
+
|
|
84
95
|
Notes:
|
|
85
96
|
|
|
86
|
-
- The plan allowance (`Plan quota`) and top-up credits (`Add-on
|
|
97
|
+
- The plan allowance (`Plan quota`) and top-up credits (`Add-on`) are
|
|
87
98
|
independent buckets with independent expiry, so both are listed when present.
|
|
88
|
-
|
|
99
|
+
Columns are sized to the rows that came back, so a single-bucket plan does
|
|
100
|
+
not reserve space for one it does not have.
|
|
101
|
+
- Colour is suppressed automatically for a `NO_COLOR` environment, a dumb
|
|
102
|
+
terminal, and any non-TTY output that is not the interactive UI.
|
|
103
|
+
- `Resets never` means the API reported the year-9999 sentinel, i.e. the
|
|
89
104
|
allowance does not reset on a schedule.
|
|
90
|
-
- `
|
|
105
|
+
- `Note plan quota is prorated` appears after a mid-cycle plan change.
|
|
91
106
|
- The command uses the same stored token as chat requests, refreshing it first,
|
|
92
107
|
so it reports the same quota the model is actually billed against.
|
|
93
108
|
|
|
@@ -123,11 +138,39 @@ at startup is:
|
|
|
123
138
|
|
|
124
139
|
1. `QODER_VPC_ENDPOINT` / `QODERCN_VPC_ENDPOINT`
|
|
125
140
|
2. `~/.pi/agent/qoder-cn-settings.json`
|
|
126
|
-
|
|
127
|
-
|
|
141
|
+
|
|
142
|
+
Only these two count as chosen by you. This extension deliberately ignores
|
|
143
|
+
endpoint values written by other Qoder clients — pi is one client among several,
|
|
144
|
+
and another client's endpoint must not silently redirect pi's traffic.
|
|
128
145
|
|
|
129
146
|
> The token and the endpoint must belong to the same instance. Pointing an
|
|
130
|
-
> enterprise endpoint at a public-gateway token fails authentication
|
|
147
|
+
> enterprise endpoint at a public-gateway token fails authentication, which is
|
|
148
|
+
> why `/login` settles the endpoint before it asks for the PAT.
|
|
149
|
+
|
|
150
|
+
### Endpoint choice during `/login`
|
|
151
|
+
|
|
152
|
+
`/login qoder-cn` asks which endpoint to use **before** prompting for the PAT,
|
|
153
|
+
because the enterprise gateway also serves the token exchange. The list shows
|
|
154
|
+
the currently saved endpoint and defaults to the personal account:
|
|
155
|
+
|
|
156
|
+
```text
|
|
157
|
+
Choose the Qoder CN endpoint (default: personal account)
|
|
158
|
+
→ Personal account — public gateway (qoder.com.cn)
|
|
159
|
+
Enterprise account — use a VPC endpoint
|
|
160
|
+
Enterprise account — <detected value> (detected: stored credential)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
- **Personal account** uses the public gateway and pins that choice, so a value
|
|
164
|
+
left behind by another client cannot resurface. If `QODER_VPC_ENDPOINT` /
|
|
165
|
+
`QODERCN_VPC_ENDPOINT` is set, it still wins — `/login` says so and warns
|
|
166
|
+
that a personal pick reverts on restart until the variable is removed.
|
|
167
|
+
- **Enterprise account** then prompts for the instance name or domain.
|
|
168
|
+
- Switching back and forth is expected; re-running `/login` presents the saved
|
|
169
|
+
endpoint so you can change it.
|
|
170
|
+
|
|
171
|
+
A `vpc_endpoint` found in `~/.pi/agent/auth.json` is offered as the third option
|
|
172
|
+
but never applied on its own. `/qoder-endpoint` with no argument also reports it
|
|
173
|
+
under “Detected (not applied)”.
|
|
131
174
|
|
|
132
175
|
## Models
|
|
133
176
|
|
package/dist/index.js
CHANGED
|
@@ -268,21 +268,14 @@ function readVpcEndpointFromAuth() {
|
|
|
268
268
|
}
|
|
269
269
|
return void 0;
|
|
270
270
|
}
|
|
271
|
-
function
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (!existsSync2(path)) return void 0;
|
|
275
|
-
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
276
|
-
const value = parsed?.vpc_endpoint ?? parsed?.vpcEndpoint ?? parsed?.vpcInstanceName;
|
|
277
|
-
if (value !== void 0 && value !== "") return String(value);
|
|
278
|
-
} catch {
|
|
279
|
-
}
|
|
280
|
-
return void 0;
|
|
271
|
+
function readInheritedVpcEndpoint() {
|
|
272
|
+
const value = readVpcEndpointFromAuth();
|
|
273
|
+
return value ? { value, source: "stored credential" } : void 0;
|
|
281
274
|
}
|
|
282
275
|
function readQoderVpcEndpoint() {
|
|
283
276
|
const fromEnv = process.env.QODER_VPC_ENDPOINT || process.env.QODERCN_VPC_ENDPOINT;
|
|
284
277
|
if (fromEnv) return fromEnv;
|
|
285
|
-
return readVpcEndpointFromSettings() ??
|
|
278
|
+
return readVpcEndpointFromSettings() ?? "";
|
|
286
279
|
}
|
|
287
280
|
function isResetValue(value) {
|
|
288
281
|
const lowered = value.toLowerCase();
|
|
@@ -1217,6 +1210,15 @@ import crypto2 from "node:crypto";
|
|
|
1217
1210
|
function getPrompt(callbacks) {
|
|
1218
1211
|
return callbacks.onPrompt;
|
|
1219
1212
|
}
|
|
1213
|
+
function getSelect(callbacks) {
|
|
1214
|
+
const fn = callbacks.onSelect;
|
|
1215
|
+
return typeof fn === "function" ? fn : void 0;
|
|
1216
|
+
}
|
|
1217
|
+
function isLoginCancellation(error, callbacks) {
|
|
1218
|
+
if (getSignal(callbacks)?.aborted) return true;
|
|
1219
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1220
|
+
return message.toLowerCase().includes("cancel");
|
|
1221
|
+
}
|
|
1220
1222
|
function getProgress(callbacks) {
|
|
1221
1223
|
return callbacks.onProgress;
|
|
1222
1224
|
}
|
|
@@ -1240,8 +1242,90 @@ function parseExpiresAt(s, expiresInSeconds) {
|
|
|
1240
1242
|
}
|
|
1241
1243
|
return Date.now() + 30 * 24 * 60 * 60 * 1e3;
|
|
1242
1244
|
}
|
|
1245
|
+
async function confirmCnEndpoint(callbacks, mode) {
|
|
1246
|
+
if (mode !== "cn") return;
|
|
1247
|
+
const current = getQoderCNEndpoints();
|
|
1248
|
+
const inherited = readInheritedVpcEndpoint();
|
|
1249
|
+
const envNames = ["QODER_VPC_ENDPOINT", "QODERCN_VPC_ENDPOINT"].filter((name) => process.env[name]);
|
|
1250
|
+
const personalId = "personal";
|
|
1251
|
+
const enterpriseId = "enterprise";
|
|
1252
|
+
const inheritedId = "enterprise-inherited";
|
|
1253
|
+
const options = [
|
|
1254
|
+
{ id: personalId, label: "Personal account \u2014 public gateway (qoder.com.cn)" },
|
|
1255
|
+
{ id: enterpriseId, label: "Enterprise account \u2014 use a VPC endpoint" }
|
|
1256
|
+
];
|
|
1257
|
+
if (inherited) {
|
|
1258
|
+
options.push({
|
|
1259
|
+
id: inheritedId,
|
|
1260
|
+
label: `Enterprise account \u2014 ${inherited.value} (detected: ${inherited.source})`
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
let message = current.isDefault ? "Choose the Qoder CN endpoint to log in against (default: personal account)" : `Choose the Qoder CN endpoint to log in against (current: ${current.raw})`;
|
|
1264
|
+
if (envNames.length > 0) {
|
|
1265
|
+
message += `
|
|
1266
|
+
(current value comes from ${envNames.join(" / ")}; it still wins after a restart)`;
|
|
1267
|
+
}
|
|
1268
|
+
const choice = await askEndpointChoice(callbacks, message, options);
|
|
1269
|
+
if (choice === personalId) {
|
|
1270
|
+
setQoderCNEndpoint("");
|
|
1271
|
+
if (envNames.length > 0) {
|
|
1272
|
+
getProgress(callbacks)?.(
|
|
1273
|
+
`Personal gateway selected. However, ${envNames.join(" / ")} takes precedence and restores the enterprise endpoint after a restart; remove the variable, then log in again.`
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
if (choice === inheritedId && inherited) {
|
|
1279
|
+
setQoderCNEndpoint(inherited.value);
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
if (choice === enterpriseId) {
|
|
1283
|
+
const prompt = getPrompt(callbacks);
|
|
1284
|
+
const raw = await prompt({
|
|
1285
|
+
message: "Enterprise VPC endpoint (instance name or domain)",
|
|
1286
|
+
placeholder: "acme",
|
|
1287
|
+
allowEmpty: true
|
|
1288
|
+
});
|
|
1289
|
+
if (getSignal(callbacks)?.aborted) throw new Error("Login cancelled");
|
|
1290
|
+
const value = raw?.trim();
|
|
1291
|
+
if (!value) return;
|
|
1292
|
+
setQoderCNEndpoint(value);
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
async function askEndpointChoice(callbacks, message, options) {
|
|
1297
|
+
const select = getSelect(callbacks);
|
|
1298
|
+
if (select) {
|
|
1299
|
+
try {
|
|
1300
|
+
const picked = await select({ message, options });
|
|
1301
|
+
if (picked) return picked;
|
|
1302
|
+
return void 0;
|
|
1303
|
+
} catch (error) {
|
|
1304
|
+
if (isLoginCancellation(error, callbacks)) throw error;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
const prompt = getPrompt(callbacks);
|
|
1308
|
+
const lines = options.map((option, index) => `${index + 1}. ${option.label}`);
|
|
1309
|
+
const raw = await prompt({
|
|
1310
|
+
message: `${message}
|
|
1311
|
+
${lines.join("\n")}
|
|
1312
|
+
Enter a number (Enter = 1, personal account)`,
|
|
1313
|
+
placeholder: "1",
|
|
1314
|
+
allowEmpty: true
|
|
1315
|
+
});
|
|
1316
|
+
if (getSignal(callbacks)?.aborted) throw new Error("Login cancelled");
|
|
1317
|
+
const trimmed = raw?.trim();
|
|
1318
|
+
if (!trimmed) return options[0]?.id;
|
|
1319
|
+
const byIndex = Number.parseInt(trimmed, 10);
|
|
1320
|
+
if (!Number.isNaN(byIndex) && byIndex >= 1 && byIndex <= options.length) {
|
|
1321
|
+
return options[byIndex - 1]?.id;
|
|
1322
|
+
}
|
|
1323
|
+
const byId = options.find((option) => option.id === trimmed || option.label === trimmed);
|
|
1324
|
+
return byId?.id ?? options[0]?.id;
|
|
1325
|
+
}
|
|
1243
1326
|
async function interactiveLogin(callbacks, mode) {
|
|
1244
1327
|
const region = getQoderRegionConfig(mode);
|
|
1328
|
+
await confirmCnEndpoint(callbacks, mode);
|
|
1245
1329
|
const prompt = getPrompt(callbacks);
|
|
1246
1330
|
const pat = await prompt({
|
|
1247
1331
|
message: !region.supportsBrowserLogin ? "Paste a Qoder CN Personal Access Token, or leave empty to cancel" : "Paste a Qoder Personal Access Token (pt-...), or leave empty for browser login",
|
|
@@ -1377,6 +1461,7 @@ var init_login = __esm({
|
|
|
1377
1461
|
"use strict";
|
|
1378
1462
|
init_cosy();
|
|
1379
1463
|
init_region();
|
|
1464
|
+
init_vpc();
|
|
1380
1465
|
init_pat();
|
|
1381
1466
|
}
|
|
1382
1467
|
});
|
|
@@ -1685,10 +1770,13 @@ function describeEndpoint() {
|
|
|
1685
1770
|
async function runEndpointCommand(args, ctx) {
|
|
1686
1771
|
const input = (args || "").trim();
|
|
1687
1772
|
if (!input) {
|
|
1773
|
+
const inherited = readInheritedVpcEndpoint();
|
|
1774
|
+
const inheritedHint = inherited ? `
|
|
1775
|
+
Detected (not applied): ${inherited.value} from ${inherited.source} \u2014 run /qoder-endpoint ${inherited.value} to use it.` : "";
|
|
1688
1776
|
ctx?.ui?.notify(
|
|
1689
1777
|
`Current Qoder CN endpoint: ${describeEndpoint()}
|
|
1690
1778
|
To set: /qoder-endpoint <domain>
|
|
1691
|
-
To reset: /qoder-endpoint default`,
|
|
1779
|
+
To reset: /qoder-endpoint default${inheritedHint}`,
|
|
1692
1780
|
"info"
|
|
1693
1781
|
);
|
|
1694
1782
|
return;
|
|
@@ -1719,11 +1807,37 @@ To reset: /qoder-endpoint default`,
|
|
|
1719
1807
|
|
|
1720
1808
|
// src/commands/usage.ts
|
|
1721
1809
|
init_region();
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1810
|
+
var ANSI = {
|
|
1811
|
+
reset: "\x1B[0m",
|
|
1812
|
+
bold: "\x1B[1m",
|
|
1813
|
+
dim: "\x1B[2m",
|
|
1814
|
+
red: "\x1B[31m",
|
|
1815
|
+
green: "\x1B[32m",
|
|
1816
|
+
yellow: "\x1B[33m"
|
|
1817
|
+
};
|
|
1818
|
+
var FILLED_CHAR = "\u2588";
|
|
1819
|
+
var EMPTY_CHAR = "\u2591";
|
|
1820
|
+
var UNKNOWN_CHAR = "?";
|
|
1821
|
+
var BAR_WIDTH = 20;
|
|
1822
|
+
var COLUMN_GAP = " ";
|
|
1823
|
+
function paint(text, code) {
|
|
1824
|
+
return code && text ? `${code}${text}${ANSI.reset}` : text;
|
|
1825
|
+
}
|
|
1826
|
+
function usageColor(percentage, danger = false) {
|
|
1827
|
+
if (danger) return `${ANSI.red}${ANSI.bold}`;
|
|
1828
|
+
if (typeof percentage !== "number" || !Number.isFinite(percentage)) return void 0;
|
|
1829
|
+
if (percentage >= 85) return `${ANSI.red}${ANSI.bold}`;
|
|
1830
|
+
if (percentage >= 60) return ANSI.yellow;
|
|
1831
|
+
return ANSI.green;
|
|
1832
|
+
}
|
|
1833
|
+
function usageBar(percentage, options = {}) {
|
|
1834
|
+
const width = options.width ?? BAR_WIDTH;
|
|
1835
|
+
const known = typeof percentage === "number" && Number.isFinite(percentage);
|
|
1836
|
+
const filled = known ? Math.max(0, Math.min(width, Math.round(percentage / 100 * width))) : 0;
|
|
1837
|
+
const fill = known ? FILLED_CHAR.repeat(filled) : "";
|
|
1838
|
+
const rest = known ? EMPTY_CHAR.repeat(width - filled) : UNKNOWN_CHAR.repeat(width);
|
|
1839
|
+
if (!options.color) return `[${fill}${rest}]`;
|
|
1840
|
+
return paint("[", ANSI.dim) + paint(fill, usageColor(percentage, options.danger)) + paint(rest, ANSI.dim) + paint("]", ANSI.dim);
|
|
1727
1841
|
}
|
|
1728
1842
|
function formatAmount(value, unit) {
|
|
1729
1843
|
if (typeof value !== "number" || !Number.isFinite(value)) return "?";
|
|
@@ -1748,41 +1862,82 @@ function formatResetTime(expiresAt, now = Date.now()) {
|
|
|
1748
1862
|
const relative = days > 0 ? `${days}d ${hours}h` : hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
|
|
1749
1863
|
return `${new Date(timestamp).toISOString().slice(0, 10)} (in ${relative})`;
|
|
1750
1864
|
}
|
|
1751
|
-
function
|
|
1752
|
-
|
|
1865
|
+
function isBucketRow(row) {
|
|
1866
|
+
return typeof row.used === "number" || typeof row.total === "number";
|
|
1867
|
+
}
|
|
1868
|
+
function rightCell(text, width, code) {
|
|
1869
|
+
return " ".repeat(Math.max(0, width - text.length)) + paint(text, code);
|
|
1870
|
+
}
|
|
1871
|
+
function renderUsageRows(rows, notes, color) {
|
|
1872
|
+
const buckets = rows.filter(isBucketRow);
|
|
1873
|
+
const usedWidth = Math.max(0, ...buckets.map((row) => formatAmount(row.used).length));
|
|
1874
|
+
const totalWidth = Math.max(0, ...buckets.map((row) => formatAmount(row.total).length));
|
|
1875
|
+
const unitWidth = Math.max(0, ...buckets.map((row) => (row.unit ?? "").length));
|
|
1876
|
+
const remainWidth = Math.max(0, ...buckets.map((row) => formatAmount(row.remaining).length));
|
|
1877
|
+
const labelWidth = Math.max(0, ...rows.map((row) => row.label.length), ...notes.map((note) => note.label.length));
|
|
1878
|
+
const percentWidth = Math.max(0, ...rows.map((row) => row.percent === void 0 ? 1 : `${row.percent}%`.length));
|
|
1879
|
+
const amountWidth = buckets.length > 0 ? usedWidth + 3 + totalWidth + 1 + unitWidth : 0;
|
|
1880
|
+
const remainder = (row) => isBucketRow(row) ? `${formatAmount(row.remaining).padStart(remainWidth)} left` : row.tail ?? "";
|
|
1881
|
+
const remainderWidth = Math.max(0, ...rows.map((row) => remainder(row).length));
|
|
1882
|
+
const lines = rows.map((row) => {
|
|
1883
|
+
const amount = isBucketRow(row) ? `${formatAmount(row.used).padStart(usedWidth)} / ${formatAmount(row.total).padStart(totalWidth)} ${(row.unit ?? "").padEnd(unitWidth)}` : " ".repeat(amountWidth);
|
|
1884
|
+
const percent = row.percent === void 0 ? UNKNOWN_CHAR : `${row.percent}%`;
|
|
1885
|
+
return [
|
|
1886
|
+
row.label.padEnd(labelWidth),
|
|
1887
|
+
usageBar(row.percent, { color, danger: row.danger }),
|
|
1888
|
+
amount,
|
|
1889
|
+
rightCell(percent, percentWidth, color ? usageColor(row.percent, row.danger) : void 0),
|
|
1890
|
+
remainder(row).padEnd(remainderWidth)
|
|
1891
|
+
].join(COLUMN_GAP);
|
|
1892
|
+
});
|
|
1893
|
+
for (const note of notes) {
|
|
1894
|
+
lines.push(`${note.label.padEnd(labelWidth)}${COLUMN_GAP}${color ? paint(note.value, note.color) : note.value}`);
|
|
1895
|
+
}
|
|
1896
|
+
return lines.map((line) => line.replace(/ +$/, ""));
|
|
1897
|
+
}
|
|
1898
|
+
function bucketRow(label, bucket, danger) {
|
|
1753
1899
|
const rawPercent = bucket?.percentage;
|
|
1754
1900
|
const derivedPercent = typeof bucket?.used === "number" && typeof bucket?.total === "number" && bucket.total > 0 ? bucket.used / bucket.total * 100 : void 0;
|
|
1755
1901
|
const percent = formatPercent(
|
|
1756
1902
|
typeof rawPercent === "number" && Number.isFinite(rawPercent) ? rawPercent : derivedPercent
|
|
1757
1903
|
);
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1904
|
+
return {
|
|
1905
|
+
label,
|
|
1906
|
+
percent,
|
|
1907
|
+
used: bucket?.used,
|
|
1908
|
+
total: bucket?.total,
|
|
1909
|
+
remaining: bucket?.remaining,
|
|
1910
|
+
unit: bucket?.unit || "credits",
|
|
1911
|
+
danger
|
|
1912
|
+
};
|
|
1763
1913
|
}
|
|
1764
1914
|
function hasBucket(bucket) {
|
|
1765
1915
|
if (!bucket) return false;
|
|
1766
1916
|
return (bucket.total ?? 0) > 0 || (bucket.used ?? 0) > 0;
|
|
1767
1917
|
}
|
|
1768
|
-
function formatQoderUsage(raw, mode, now = Date.now()) {
|
|
1918
|
+
function formatQoderUsage(raw, mode, now = Date.now(), options = {}) {
|
|
1919
|
+
const color = options.color ?? false;
|
|
1769
1920
|
const region = getQoderRegionConfig(mode);
|
|
1770
|
-
const
|
|
1921
|
+
const danger = Boolean(raw?.isQuotaExceeded);
|
|
1771
1922
|
const userType = raw?.userType ? ` (${raw.userType})` : "";
|
|
1772
1923
|
const overallPercent = formatPercent(raw?.totalUsagePercentage);
|
|
1773
|
-
|
|
1774
|
-
lines.push(`${usageBar(overallPercent)} ${overallPercent === void 0 ? "?" : `${overallPercent}%`} used`);
|
|
1775
|
-
if (raw?.isQuotaExceeded) lines.push(" ! quota exceeded");
|
|
1924
|
+
const rows = [{ label: "Overall", percent: overallPercent, tail: "used", danger }];
|
|
1776
1925
|
const showUserQuota = hasBucket(raw?.userQuota);
|
|
1777
1926
|
const showAddOnQuota = hasBucket(raw?.addOnQuota);
|
|
1778
|
-
if (showUserQuota)
|
|
1779
|
-
if (showAddOnQuota)
|
|
1780
|
-
|
|
1781
|
-
if (
|
|
1782
|
-
|
|
1927
|
+
if (showUserQuota) rows.push(bucketRow("Plan quota", raw.userQuota, danger));
|
|
1928
|
+
if (showAddOnQuota) rows.push(bucketRow("Add-on", raw.addOnQuota, danger));
|
|
1929
|
+
const notes = [];
|
|
1930
|
+
if (danger) notes.push({ label: "Status", value: "quota exceeded", color: ANSI.red });
|
|
1931
|
+
if (!showUserQuota && !showAddOnQuota) notes.push({ label: "Buckets", value: "none returned" });
|
|
1932
|
+
if (raw?.isPlanQuotaProrated) notes.push({ label: "Note", value: "plan quota is prorated" });
|
|
1933
|
+
notes.push({ label: "Resets", value: formatResetTime(raw?.expiresAt, now) });
|
|
1783
1934
|
const manageUrl = raw?.addOnQuota?.detailUrl || raw?.upgradeUrl || region.manageUrl;
|
|
1784
|
-
if (manageUrl)
|
|
1785
|
-
|
|
1935
|
+
if (manageUrl) notes.push({ label: "Manage", value: manageUrl });
|
|
1936
|
+
const title = `${region.usageTitle}${userType}`;
|
|
1937
|
+
return {
|
|
1938
|
+
title: region.usageTitle,
|
|
1939
|
+
lines: [paint(title, color ? ANSI.bold : void 0), ...renderUsageRows(rows, notes, color)]
|
|
1940
|
+
};
|
|
1786
1941
|
}
|
|
1787
1942
|
async function fetchQoderQuota(accessToken, mode) {
|
|
1788
1943
|
const response = await fetch(getQoderUsageURL(mode), {
|
|
@@ -1819,6 +1974,14 @@ async function resolveAccessToken(providerID, ctx) {
|
|
|
1819
1974
|
return getCachedCredentials2("", providerID)?.access || void 0;
|
|
1820
1975
|
}
|
|
1821
1976
|
var FORMAT_ARGS = /* @__PURE__ */ new Set(["json", "raw", "debug"]);
|
|
1977
|
+
var PLAIN_ARGS = /* @__PURE__ */ new Set(["plain", "no-color", "nocolor"]);
|
|
1978
|
+
function shouldColorize(args, options = {}) {
|
|
1979
|
+
if (PLAIN_ARGS.has((args || "").trim().toLowerCase())) return false;
|
|
1980
|
+
const env = options.env ?? process.env;
|
|
1981
|
+
if (env.NO_COLOR || env.TERM === "dumb" || env.FORCE_COLOR === "0") return false;
|
|
1982
|
+
if (options.hasUI) return true;
|
|
1983
|
+
return options.isTTY ?? Boolean(process.stdout?.isTTY);
|
|
1984
|
+
}
|
|
1822
1985
|
async function runUsageCommand(mode, args, ctx) {
|
|
1823
1986
|
const region = getQoderRegionConfig(mode);
|
|
1824
1987
|
const providerID = region.providerID;
|
|
@@ -1830,7 +1993,8 @@ async function runUsageCommand(mode, args, ctx) {
|
|
|
1830
1993
|
return;
|
|
1831
1994
|
}
|
|
1832
1995
|
const raw = await fetchQoderQuota(accessToken, mode);
|
|
1833
|
-
const
|
|
1996
|
+
const color = !wantsRaw && shouldColorize(args, { hasUI: Boolean(ctx?.ui) });
|
|
1997
|
+
const output = wantsRaw ? JSON.stringify(raw, null, 2) : formatQoderUsage(raw, mode, Date.now(), { color }).lines.join("\n");
|
|
1834
1998
|
ctx?.ui?.notify(output, "info");
|
|
1835
1999
|
if (!ctx?.ui) console.log(output);
|
|
1836
2000
|
} catch (error) {
|
package/package.json
CHANGED