@solgridai/cli 0.2.0-beta.0 → 0.2.0-beta.1
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 +13 -0
- package/dist/index.js +99 -12
- package/package.json +1 -1
- package/skills/solgrid-cli/SKILL.md +10 -1
package/README.md
CHANGED
|
@@ -13,6 +13,19 @@ npm install -g @solgridai/cli
|
|
|
13
13
|
solgrid --version
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
### 测试版
|
|
17
|
+
|
|
18
|
+
新版本正式发布前,会先以测试版(例如 `0.2.0-beta.0`)发布到 npm 的 `beta` 标签,用于提前试用新功能。直接执行 `npm install -g @solgridai/cli` 不会装到测试版。
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g @solgridai/cli@beta # 安装最新的测试版
|
|
22
|
+
npm install -g @solgridai/cli@0.2.0-beta.0 # 或指定版本
|
|
23
|
+
solgrid --version
|
|
24
|
+
npm install -g @solgridai/cli # 装回正式版
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
测试版的新功能可能依赖还没上线到生产环境的接口。这时请按发布说明,用 `SOLGRID_API_BASE_URL` 指向对应的环境,并使用该环境的 API Key(见[配置](#配置))。
|
|
28
|
+
|
|
16
29
|
## 快速开始
|
|
17
30
|
|
|
18
31
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -2075,7 +2075,7 @@ function useColor() {
|
|
|
2075
2075
|
// node_modules/commander/index.js
|
|
2076
2076
|
var program = new Command;
|
|
2077
2077
|
// src/build-info.ts
|
|
2078
|
-
var buildInfo = { packageName: "@solgridai/cli", version: "0.2.0-beta.
|
|
2078
|
+
var buildInfo = { packageName: "@solgridai/cli", version: "0.2.0-beta.1" };
|
|
2079
2079
|
|
|
2080
2080
|
// src/core/errors.ts
|
|
2081
2081
|
class CliError extends Error {
|
|
@@ -2104,6 +2104,7 @@ class CliError extends Error {
|
|
|
2104
2104
|
class ApiError extends CliError {
|
|
2105
2105
|
status;
|
|
2106
2106
|
key;
|
|
2107
|
+
keyDetail;
|
|
2107
2108
|
routeMissing;
|
|
2108
2109
|
apiKeySentTo;
|
|
2109
2110
|
signedUrl;
|
|
@@ -2112,6 +2113,7 @@ class ApiError extends CliError {
|
|
|
2112
2113
|
this.name = "ApiError";
|
|
2113
2114
|
this.status = options.status;
|
|
2114
2115
|
this.key = options.key;
|
|
2116
|
+
this.keyDetail = options.keyDetail;
|
|
2115
2117
|
this.routeMissing = options.routeMissing === true;
|
|
2116
2118
|
this.apiKeySentTo = options.apiKeySentTo;
|
|
2117
2119
|
this.signedUrl = options.signedUrl === true;
|
|
@@ -2120,6 +2122,7 @@ class ApiError extends CliError {
|
|
|
2120
2122
|
return new ApiError(this.code, this.message, {
|
|
2121
2123
|
...this.status !== undefined ? { status: this.status } : {},
|
|
2122
2124
|
...this.key ? { key: this.key } : {},
|
|
2125
|
+
...this.keyDetail ? { keyDetail: this.keyDetail } : {},
|
|
2123
2126
|
routeMissing: this.routeMissing,
|
|
2124
2127
|
...this.apiKeySentTo ? { apiKeySentTo: this.apiKeySentTo } : {},
|
|
2125
2128
|
signedUrl: this.signedUrl,
|
|
@@ -2541,6 +2544,41 @@ function formatAmount(amount) {
|
|
|
2541
2544
|
const sign = negative ? "-" : "";
|
|
2542
2545
|
return amount.currency === "USD" ? `${sign}$${number}` : `${sign}${number} ${amount.currency}`;
|
|
2543
2546
|
}
|
|
2547
|
+
function plainDecimal(value) {
|
|
2548
|
+
if (!Number.isFinite(value))
|
|
2549
|
+
return;
|
|
2550
|
+
const text = String(value);
|
|
2551
|
+
const match = /^(-?)(\d+)(?:\.(\d+))?e([+-]\d+)$/.exec(text);
|
|
2552
|
+
if (!match)
|
|
2553
|
+
return text;
|
|
2554
|
+
const [, sign = "", whole = "", fraction = "", exponent = "0"] = match;
|
|
2555
|
+
const digits = whole + fraction;
|
|
2556
|
+
const point = whole.length + Number(exponent);
|
|
2557
|
+
if (point <= 0)
|
|
2558
|
+
return `${sign}0.${"0".repeat(-point)}${digits}`;
|
|
2559
|
+
if (point >= digits.length)
|
|
2560
|
+
return `${sign}${digits}${"0".repeat(point - digits.length)}`;
|
|
2561
|
+
return `${sign}${digits.slice(0, point)}.${digits.slice(point)}`;
|
|
2562
|
+
}
|
|
2563
|
+
function ceilToSixDecimals(value) {
|
|
2564
|
+
const decimal = typeof value === "number" ? plainDecimal(value) : value.trim();
|
|
2565
|
+
if (decimal === undefined || !/^\d+(\.\d+)?$/.test(decimal))
|
|
2566
|
+
return;
|
|
2567
|
+
const [whole = "0", fraction = ""] = decimal.split(".");
|
|
2568
|
+
let micros = BigInt(whole + fraction.slice(0, 6).padEnd(6, "0"));
|
|
2569
|
+
if (/[1-9]/.test(fraction.slice(6)))
|
|
2570
|
+
micros += 1n;
|
|
2571
|
+
const text = micros.toString().padStart(7, "0");
|
|
2572
|
+
return `${text.slice(0, -6)}.${text.slice(-6)}`;
|
|
2573
|
+
}
|
|
2574
|
+
function formatUnitPrice(amount) {
|
|
2575
|
+
if (!amount || typeof amount.value !== "number" && typeof amount.value !== "string")
|
|
2576
|
+
return "-";
|
|
2577
|
+
const decimal = ceilToSixDecimals(amount.value);
|
|
2578
|
+
if (decimal === undefined)
|
|
2579
|
+
return formatAmount(amount);
|
|
2580
|
+
return amount.currency === "USD" ? `$${decimal}` : `${decimal} ${amount.currency}`;
|
|
2581
|
+
}
|
|
2544
2582
|
|
|
2545
2583
|
// src/core/api/client.ts
|
|
2546
2584
|
var DEFAULT_TIMEOUT_MS = 30000;
|
|
@@ -2664,6 +2702,7 @@ function envelopeError(response, envelope, text, url, auth) {
|
|
|
2664
2702
|
const status = bodyCode ?? (response.ok ? 400 : response.status);
|
|
2665
2703
|
const serverMessage = typeof envelope?.message === "string" && envelope.message ? envelope.message : undefined;
|
|
2666
2704
|
const key = errorKeyOf(envelope);
|
|
2705
|
+
const keyDetail = keyDetailOf(envelope);
|
|
2667
2706
|
const details = [
|
|
2668
2707
|
...validationDetails(envelope?.errorData, key),
|
|
2669
2708
|
...frameworkErrorDetails(envelope?.errors)
|
|
@@ -2673,6 +2712,7 @@ function envelopeError(response, envelope, text, url, auth) {
|
|
|
2673
2712
|
return new ApiError(errorCodeForStatus(status), message, {
|
|
2674
2713
|
status,
|
|
2675
2714
|
...key ? { key } : {},
|
|
2715
|
+
...keyDetail ? { keyDetail } : {},
|
|
2676
2716
|
...auth,
|
|
2677
2717
|
...serverJsonHint ? { hint: serverJsonHint } : {},
|
|
2678
2718
|
details
|
|
@@ -2688,10 +2728,20 @@ function hasSerializerErrors(errors) {
|
|
|
2688
2728
|
const value = errors?.serializerErrors;
|
|
2689
2729
|
return Array.isArray(value) ? value.length > 0 : typeof value === "string" && value.length > 0;
|
|
2690
2730
|
}
|
|
2731
|
+
function keyItemOf(envelope) {
|
|
2732
|
+
const items = Array.isArray(envelope?.errorData) ? envelope.errorData : [];
|
|
2733
|
+
return items.find((entry) => entry && typeof entry === "object" && !entry.propertyName && typeof entry.errorCode === "string" && ERROR_KEY.test(entry.errorCode));
|
|
2734
|
+
}
|
|
2735
|
+
function keyDetailOf(envelope) {
|
|
2736
|
+
const item = keyItemOf(envelope);
|
|
2737
|
+
if (!item)
|
|
2738
|
+
return;
|
|
2739
|
+
const { errorCode: _code, errorMessage: _message, propertyName: _property, ...rest } = item;
|
|
2740
|
+
return Object.keys(rest).length > 0 ? rest : undefined;
|
|
2741
|
+
}
|
|
2691
2742
|
var ERROR_KEY = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$/;
|
|
2692
2743
|
function errorKeyOf(envelope) {
|
|
2693
|
-
const
|
|
2694
|
-
const item = items.find((entry) => entry && !entry.propertyName && typeof entry.errorCode === "string" && ERROR_KEY.test(entry.errorCode));
|
|
2744
|
+
const item = keyItemOf(envelope);
|
|
2695
2745
|
if (item?.errorCode)
|
|
2696
2746
|
return item.errorCode;
|
|
2697
2747
|
const message = envelope?.message;
|
|
@@ -2849,7 +2899,44 @@ function presentApiError(error) {
|
|
|
2849
2899
|
`) || undefined;
|
|
2850
2900
|
return { message: base.message, hint };
|
|
2851
2901
|
}
|
|
2902
|
+
function isAmount(value) {
|
|
2903
|
+
const amount = value;
|
|
2904
|
+
return typeof amount === "object" && amount !== null && (typeof amount.value === "string" || typeof amount.value === "number");
|
|
2905
|
+
}
|
|
2906
|
+
var RAISE_BUDGET = `Raise or disable the daily budget at ${CONSOLE_URLS.budgets}`;
|
|
2907
|
+
function explainDailyBudget(detail) {
|
|
2908
|
+
const { reason, spent, held, requestedReservation, dailyLimit, remaining } = detail ?? {};
|
|
2909
|
+
const amounts = [spent, held, requestedReservation, dailyLimit, remaining];
|
|
2910
|
+
if (!amounts.every(isAmount))
|
|
2911
|
+
return;
|
|
2912
|
+
const [spentText, heldText, needText, limitText, leftText] = amounts.map((amount) => formatAmount(amount));
|
|
2913
|
+
switch (reason) {
|
|
2914
|
+
case "daily_spend_limit_reached":
|
|
2915
|
+
return {
|
|
2916
|
+
message: `Today's spending (${spentText}) has reached the daily budget of ${limitText}.`,
|
|
2917
|
+
hint: `${RAISE_BUDGET}, or try again tomorrow (the budget resets at 00:00 UTC).`
|
|
2918
|
+
};
|
|
2919
|
+
case "requested_reservation_exceeds_remaining_budget":
|
|
2920
|
+
return {
|
|
2921
|
+
message: `This run requires a reservation of ${needText}, more than the ${leftText} left of today's ${limitText} budget (${spentText} spent).`,
|
|
2922
|
+
hint: RAISE_BUDGET
|
|
2923
|
+
};
|
|
2924
|
+
case "reservation_exceeds_remaining_budget":
|
|
2925
|
+
return {
|
|
2926
|
+
message: `Runs still in progress hold ${heldText} of today's ${limitText} budget, leaving ${leftText}; this run requires a reservation of ${needText}.`,
|
|
2927
|
+
hint: `Wait for those runs to finish (what they do not use is released), then retry with the same idempotency key, or submit runs one at a time.
|
|
2928
|
+
${RAISE_BUDGET}`
|
|
2929
|
+
};
|
|
2930
|
+
default:
|
|
2931
|
+
return;
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2852
2934
|
function explain(error) {
|
|
2935
|
+
if (error.key === "daily_budget_exceeded") {
|
|
2936
|
+
const budget = explainDailyBudget(error.keyDetail);
|
|
2937
|
+
if (budget)
|
|
2938
|
+
return budget;
|
|
2939
|
+
}
|
|
2853
2940
|
const known = error.key ? KEY_PRESENTATIONS[error.key] : undefined;
|
|
2854
2941
|
if (known)
|
|
2855
2942
|
return known;
|
|
@@ -2977,6 +3064,7 @@ function createOutput(ctx, json) {
|
|
|
2977
3064
|
message,
|
|
2978
3065
|
...cliError instanceof ApiError && cliError.status !== undefined ? { status: cliError.status } : {},
|
|
2979
3066
|
...cliError instanceof ApiError && cliError.key ? { key: cliError.key } : {},
|
|
3067
|
+
...cliError instanceof ApiError && cliError.keyDetail ? { detail: cliError.keyDetail } : {},
|
|
2980
3068
|
...cliError.details.length > 0 ? { details: cliError.details } : {},
|
|
2981
3069
|
...hint ? { hint } : {}
|
|
2982
3070
|
},
|
|
@@ -4988,8 +5076,8 @@ function withUnit(amount, unit) {
|
|
|
4988
5076
|
return unit ? `${amount} ${unit}` : amount;
|
|
4989
5077
|
}
|
|
4990
5078
|
function describeLeaf(leaf) {
|
|
4991
|
-
const text = withUnit(
|
|
4992
|
-
return leaf.flatFee ? `${text} + ${
|
|
5079
|
+
const text = withUnit(formatUnitPrice(leaf.amount), unitText(leaf));
|
|
5080
|
+
return leaf.flatFee ? `${text} + ${formatUnitPrice(leaf.flatFee)} flat` : text;
|
|
4993
5081
|
}
|
|
4994
5082
|
var METER_UNITS = {
|
|
4995
5083
|
Calls: ["call", "calls"],
|
|
@@ -5013,8 +5101,7 @@ function meterText(meter, size = 1) {
|
|
|
5013
5101
|
return size === 1 ? `per ${one}` : `per ${countText(size)} ${many}`;
|
|
5014
5102
|
}
|
|
5015
5103
|
function rateAmount(rate) {
|
|
5016
|
-
|
|
5017
|
-
return formatAmount({ value: (micros / 1e6).toFixed(6), currency: "USD" });
|
|
5104
|
+
return formatUnitPrice({ value: rate.unitPriceUsd, currency: "USD" });
|
|
5018
5105
|
}
|
|
5019
5106
|
function describeRate(rate) {
|
|
5020
5107
|
return `${rateAmount(rate)} ${meterText(rate.meter, rate.unitSize)}`;
|
|
@@ -5034,7 +5121,7 @@ function hasConditions(rate) {
|
|
|
5034
5121
|
function summarizeRates(price, rates) {
|
|
5035
5122
|
const only = rates.length === 1 ? rates[0] : undefined;
|
|
5036
5123
|
if (only) {
|
|
5037
|
-
const amount = price.amount ?
|
|
5124
|
+
const amount = price.amount ? formatUnitPrice(price.amount) : rateAmount(only);
|
|
5038
5125
|
const text = `${amount} ${meterText(only.meter, only.unitSize)}`;
|
|
5039
5126
|
return hasConditions(only) ? `${text} (${conditionsText(only)})` : text;
|
|
5040
5127
|
}
|
|
@@ -5069,20 +5156,20 @@ function summarizePrice(price, fallback) {
|
|
|
5069
5156
|
switch (price.type) {
|
|
5070
5157
|
case "PER_UNIT_MATRIX": {
|
|
5071
5158
|
const first = price.variants?.[0]?.price;
|
|
5072
|
-
text = `from ${withUnit(
|
|
5159
|
+
text = `from ${withUnit(formatUnitPrice(price.amount), first ? unitText(first) : "")}`;
|
|
5073
5160
|
break;
|
|
5074
5161
|
}
|
|
5075
5162
|
case "TIERED":
|
|
5076
5163
|
text = price.default ? describeLeaf(price.default) : "varies";
|
|
5077
5164
|
break;
|
|
5078
5165
|
case "BY_PERIOD":
|
|
5079
|
-
text = withUnit(
|
|
5166
|
+
text = withUnit(formatUnitPrice(price.amount), price.period ? periodText(price.period) : "");
|
|
5080
5167
|
break;
|
|
5081
5168
|
default:
|
|
5082
|
-
text = withUnit(
|
|
5169
|
+
text = withUnit(formatUnitPrice(price.amount), unitText(price));
|
|
5083
5170
|
}
|
|
5084
5171
|
if (price.flatFee)
|
|
5085
|
-
text += ` + ${
|
|
5172
|
+
text += ` + ${formatUnitPrice(price.flatFee)} flat`;
|
|
5086
5173
|
if (price.type === "TIERED" && price.tiers?.length)
|
|
5087
5174
|
text += " + add-ons";
|
|
5088
5175
|
return text;
|
package/package.json
CHANGED
|
@@ -172,9 +172,18 @@ with the same key, or look the run up first.
|
|
|
172
172
|
| `error.key` | Meaning |
|
|
173
173
|
|---|---|
|
|
174
174
|
| `wallet_balance_insufficient` | Balance too low; the user can top up on the console's Wallet page |
|
|
175
|
-
| `daily_budget_exceeded` | Daily budget reached; adjustable on the console's Budgets page |
|
|
175
|
+
| `daily_budget_exceeded` | Daily budget reached; adjustable on the console's Budgets page (but see below) |
|
|
176
176
|
| `per_call_limit_exceeded` | The run costs more than the per-call limit; adjustable on the console's Budgets page |
|
|
177
177
|
|
|
178
|
+
- The daily budget counts today's spending plus what runs still in progress
|
|
179
|
+
hold. If `daily_budget_exceeded` comes with
|
|
180
|
+
`error.detail.reason: "reservation_exceeds_remaining_budget"`, those holds
|
|
181
|
+
are the cause: wait for the user's runs in progress to finish, then retry
|
|
182
|
+
with the **same** idempotency key (a refused run was never started), or
|
|
183
|
+
submit runs one at a time. `error.detail` also has `spent`, `held`,
|
|
184
|
+
`requestedReservation`, `dailyLimit` and `remaining`.
|
|
185
|
+
- The budget is checked when a run starts; a run's final cost can exceed what
|
|
186
|
+
it held, so a day's spending can end slightly over the budget.
|
|
178
187
|
- Never top up the wallet or change budgets on the user's behalf.
|
|
179
188
|
|
|
180
189
|
## Using local files as input
|