@slkiser/opencode-quota 4.9.0 → 4.10.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 +18 -16
- package/dist/lib/alibaba-token-plan.d.ts +88 -0
- package/dist/lib/alibaba-token-plan.d.ts.map +1 -0
- package/dist/lib/alibaba-token-plan.js +505 -0
- package/dist/lib/alibaba-token-plan.js.map +1 -0
- package/dist/lib/anthropic.d.ts +19 -2
- package/dist/lib/anthropic.d.ts.map +1 -1
- package/dist/lib/anthropic.js +62 -3
- package/dist/lib/anthropic.js.map +1 -1
- package/dist/lib/atomic-json.d.ts +3 -0
- package/dist/lib/atomic-json.d.ts.map +1 -1
- package/dist/lib/atomic-json.js +16 -4
- package/dist/lib/atomic-json.js.map +1 -1
- package/dist/lib/cli-show.d.ts.map +1 -1
- package/dist/lib/cli-show.js +6 -1
- package/dist/lib/cli-show.js.map +1 -1
- package/dist/lib/config-write-target.d.ts +28 -0
- package/dist/lib/config-write-target.d.ts.map +1 -0
- package/dist/lib/config-write-target.js +159 -0
- package/dist/lib/config-write-target.js.map +1 -0
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +34 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/cursor-usage.d.ts +1 -0
- package/dist/lib/cursor-usage.d.ts.map +1 -1
- package/dist/lib/cursor-usage.js +1 -0
- package/dist/lib/cursor-usage.js.map +1 -1
- package/dist/lib/entries.d.ts +17 -0
- package/dist/lib/entries.d.ts.map +1 -1
- package/dist/lib/entries.js +4 -0
- package/dist/lib/entries.js.map +1 -1
- package/dist/lib/format-utils.d.ts +11 -8
- package/dist/lib/format-utils.d.ts.map +1 -1
- package/dist/lib/format-utils.js +47 -6
- package/dist/lib/format-utils.js.map +1 -1
- package/dist/lib/format.d.ts +3 -0
- package/dist/lib/format.d.ts.map +1 -1
- package/dist/lib/format.js +66 -29
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/glm-coding-plan.d.ts.map +1 -1
- package/dist/lib/glm-coding-plan.js +12 -3
- package/dist/lib/glm-coding-plan.js.map +1 -1
- package/dist/lib/init-installer.d.ts.map +1 -1
- package/dist/lib/init-installer.js +2 -2
- package/dist/lib/init-installer.js.map +1 -1
- package/dist/lib/ollama-cloud.d.ts +2 -2
- package/dist/lib/ollama-cloud.d.ts.map +1 -1
- package/dist/lib/ollama-cloud.js +3 -45
- package/dist/lib/ollama-cloud.js.map +1 -1
- package/dist/lib/openai.d.ts +2 -0
- package/dist/lib/openai.d.ts.map +1 -1
- package/dist/lib/openai.js +29 -13
- package/dist/lib/openai.js.map +1 -1
- package/dist/lib/opencode-config-editor.d.ts +3 -0
- package/dist/lib/opencode-config-editor.d.ts.map +1 -1
- package/dist/lib/opencode-config-editor.js +71 -10
- package/dist/lib/opencode-config-editor.js.map +1 -1
- package/dist/lib/opencode-go.d.ts.map +1 -1
- package/dist/lib/opencode-go.js +25 -4
- package/dist/lib/opencode-go.js.map +1 -1
- package/dist/lib/openrouter.d.ts +2 -1
- package/dist/lib/openrouter.d.ts.map +1 -1
- package/dist/lib/openrouter.js +3 -4
- package/dist/lib/openrouter.js.map +1 -1
- package/dist/lib/provider-registration.d.ts +12 -1
- package/dist/lib/provider-registration.d.ts.map +1 -1
- package/dist/lib/provider-registration.js +13 -1
- package/dist/lib/provider-registration.js.map +1 -1
- package/dist/lib/quota-accounting-projection.d.ts +2 -0
- package/dist/lib/quota-accounting-projection.d.ts.map +1 -1
- package/dist/lib/quota-accounting-projection.js +16 -1
- package/dist/lib/quota-accounting-projection.js.map +1 -1
- package/dist/lib/quota-command-format.d.ts +2 -0
- package/dist/lib/quota-command-format.d.ts.map +1 -1
- package/dist/lib/quota-command-format.js +32 -36
- package/dist/lib/quota-command-format.js.map +1 -1
- package/dist/lib/quota-dialog-commands.d.ts.map +1 -1
- package/dist/lib/quota-dialog-commands.js +2 -0
- package/dist/lib/quota-dialog-commands.js.map +1 -1
- package/dist/lib/quota-exhaustion-projection.d.ts +11 -0
- package/dist/lib/quota-exhaustion-projection.d.ts.map +1 -0
- package/dist/lib/quota-exhaustion-projection.js +105 -0
- package/dist/lib/quota-exhaustion-projection.js.map +1 -0
- package/dist/lib/quota-providers-local.d.ts.map +1 -1
- package/dist/lib/quota-providers-local.js +14 -0
- package/dist/lib/quota-providers-local.js.map +1 -1
- package/dist/lib/quota-providers-remote.d.ts.map +1 -1
- package/dist/lib/quota-providers-remote.js +6 -2
- package/dist/lib/quota-providers-remote.js.map +1 -1
- package/dist/lib/quota-render-data.d.ts.map +1 -1
- package/dist/lib/quota-render-data.js +7 -3
- package/dist/lib/quota-render-data.js.map +1 -1
- package/dist/lib/quota-state-codec.d.ts +1 -1
- package/dist/lib/quota-state-codec.d.ts.map +1 -1
- package/dist/lib/quota-state-codec.js +6 -3
- package/dist/lib/quota-state-codec.js.map +1 -1
- package/dist/lib/quota-status.d.ts.map +1 -1
- package/dist/lib/quota-status.js +19 -0
- package/dist/lib/quota-status.js.map +1 -1
- package/dist/lib/quota-toast-runtime.d.ts.map +1 -1
- package/dist/lib/quota-toast-runtime.js +9 -0
- package/dist/lib/quota-toast-runtime.js.map +1 -1
- package/dist/lib/qwen-local-quota.d.ts +2 -0
- package/dist/lib/qwen-local-quota.d.ts.map +1 -1
- package/dist/lib/qwen-local-quota.js +17 -1
- package/dist/lib/qwen-local-quota.js.map +1 -1
- package/dist/lib/scoped-update.d.ts +2 -0
- package/dist/lib/scoped-update.d.ts.map +1 -1
- package/dist/lib/scoped-update.js +43 -7
- package/dist/lib/scoped-update.js.map +1 -1
- package/dist/lib/synthetic.d.ts.map +1 -1
- package/dist/lib/synthetic.js +9 -0
- package/dist/lib/synthetic.js.map +1 -1
- package/dist/lib/toast-format-grouped.d.ts +3 -0
- package/dist/lib/toast-format-grouped.d.ts.map +1 -1
- package/dist/lib/toast-format-grouped.js +34 -20
- package/dist/lib/toast-format-grouped.js.map +1 -1
- package/dist/lib/tui-compact-format.d.ts +1 -0
- package/dist/lib/tui-compact-format.d.ts.map +1 -1
- package/dist/lib/tui-compact-format.js +176 -20
- package/dist/lib/tui-compact-format.js.map +1 -1
- package/dist/lib/tui-panel-state.d.ts +8 -0
- package/dist/lib/tui-panel-state.d.ts.map +1 -1
- package/dist/lib/tui-panel-state.js.map +1 -1
- package/dist/lib/tui-prompt-bar-format.d.ts +25 -0
- package/dist/lib/tui-prompt-bar-format.d.ts.map +1 -0
- package/dist/lib/tui-prompt-bar-format.js +206 -0
- package/dist/lib/tui-prompt-bar-format.js.map +1 -0
- package/dist/lib/tui-runtime.d.ts.map +1 -1
- package/dist/lib/tui-runtime.js +18 -81
- package/dist/lib/tui-runtime.js.map +1 -1
- package/dist/lib/tui-sidebar-content.d.ts +32 -0
- package/dist/lib/tui-sidebar-content.d.ts.map +1 -0
- package/dist/lib/tui-sidebar-content.js +52 -0
- package/dist/lib/tui-sidebar-content.js.map +1 -0
- package/dist/lib/tui-sidebar-format.d.ts +1 -1
- package/dist/lib/tui-sidebar-format.d.ts.map +1 -1
- package/dist/lib/tui-sidebar-format.js +3 -0
- package/dist/lib/tui-sidebar-format.js.map +1 -1
- package/dist/lib/types.d.ts +14 -12
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +1 -0
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/xai.d.ts +2 -0
- package/dist/lib/xai.d.ts.map +1 -1
- package/dist/lib/xai.js +19 -3
- package/dist/lib/xai.js.map +1 -1
- package/dist/providers/alibaba-token-plan.d.ts +3 -0
- package/dist/providers/alibaba-token-plan.d.ts.map +1 -0
- package/dist/providers/alibaba-token-plan.js +89 -0
- package/dist/providers/alibaba-token-plan.js.map +1 -0
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +36 -0
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/cache-policies.d.ts +3 -0
- package/dist/providers/cache-policies.d.ts.map +1 -1
- package/dist/providers/cache-policies.js +1 -0
- package/dist/providers/cache-policies.js.map +1 -1
- package/dist/providers/cursor.d.ts.map +1 -1
- package/dist/providers/cursor.js +12 -0
- package/dist/providers/cursor.js.map +1 -1
- package/dist/providers/ollama-cloud.d.ts +1 -2
- package/dist/providers/ollama-cloud.d.ts.map +1 -1
- package/dist/providers/ollama-cloud.js +1 -20
- package/dist/providers/ollama-cloud.js.map +1 -1
- package/dist/providers/opencode-go.d.ts +1 -0
- package/dist/providers/opencode-go.d.ts.map +1 -1
- package/dist/providers/opencode-go.js +29 -1
- package/dist/providers/opencode-go.js.map +1 -1
- package/dist/providers/openrouter.d.ts.map +1 -1
- package/dist/providers/openrouter.js +11 -4
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/qwen-code.d.ts.map +1 -1
- package/dist/providers/qwen-code.js +1 -0
- package/dist/providers/qwen-code.js.map +1 -1
- package/dist/providers/registry.d.ts.map +1 -1
- package/dist/providers/registry.js +2 -0
- package/dist/providers/registry.js.map +1 -1
- package/dist/providers/result-helpers.d.ts +2 -1
- package/dist/providers/result-helpers.d.ts.map +1 -1
- package/dist/providers/result-helpers.js +1 -0
- package/dist/providers/result-helpers.js.map +1 -1
- package/dist/providers/xai.d.ts.map +1 -1
- package/dist/providers/xai.js +3 -0
- package/dist/providers/xai.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +229 -217
- package/dist/tui.tsx +70 -42
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<a href="https://github.com/slkiser/opencode-quota">
|
|
3
3
|
<picture>
|
|
4
|
-
<source srcset="opencode-quota-logo-dark.svg" media="(prefers-color-scheme: dark)">
|
|
5
|
-
<source srcset="opencode-quota-logo-light.svg" media="(prefers-color-scheme: light)">
|
|
6
|
-
<img src="opencode-quota-logo-light.svg" alt="OpenCode Quota logo">
|
|
4
|
+
<source srcset="https://shawnkiser.com/opencode-quota/opencode-quota-logo-dark.svg" media="(prefers-color-scheme: dark)">
|
|
5
|
+
<source srcset="https://shawnkiser.com/opencode-quota/opencode-quota-logo-light.svg" media="(prefers-color-scheme: light)">
|
|
6
|
+
<img src="https://shawnkiser.com/opencode-quota/opencode-quota-logo-light.svg" alt="OpenCode Quota logo">
|
|
7
7
|
</picture>
|
|
8
8
|
</a>
|
|
9
9
|
</p>
|
|
@@ -89,7 +89,8 @@ More ways to use it:
|
|
|
89
89
|
|
|
90
90
|
- Check quota anywhere: use `opencode-quota show` in a terminal or the same slash commands in the TUI, Web, and Desktop.
|
|
91
91
|
- Automate quota checks with JSON output for scripts, status bars, and CI. Optional OpenTelemetry metrics support monitoring tools.
|
|
92
|
-
- Customize the display with [`tuiPromptBar.enabled`](docs/readme/configuration.md#tui-settings), OpenCode Go's preferred collapsed-sidebar window, reset precision, and [`accountingDetail`](docs/readme/configuration.md#show-accounting-detail).
|
|
92
|
+
- Customize the display with [`tuiPromptBar.enabled`](docs/readme/configuration.md#tui-settings), OpenCode Go's preferred collapsed-sidebar window, spaced reset countdowns by default with a `resetTimeSpaced: false` dense opt-out, decimal reset precision, bare percent labels, and [`accountingDetail`](docs/readme/configuration.md#show-accounting-detail).
|
|
93
|
+
- Optionally estimate **Runs out ≈ 1h 50m** for supported fixed windows with [`quotaProjection: "runway"`](docs/readme/configuration.md#estimate-when-fixed-quota-runs-out). It is off by default and leaves JSON output unchanged.
|
|
93
94
|
- Choose current-session or descendant-tree token totals. Get reset popups for selected windows with [`resetNotifications`](docs/readme/configuration.md#notify-when-quota-becomes-available-again).
|
|
94
95
|
- Troubleshoot authentication, quota sources, pricing, and maintainer notices.
|
|
95
96
|
|
|
@@ -183,17 +184,18 @@ Gemini CLI quota support is deprecated for new installs. Existing v4 configurati
|
|
|
183
184
|
<details open>
|
|
184
185
|
<summary><strong>Personal</strong></summary>
|
|
185
186
|
|
|
186
|
-
| Provider
|
|
187
|
-
|
|
|
188
|
-
| Alibaba Coding Plan
|
|
189
|
-
|
|
|
190
|
-
|
|
|
191
|
-
|
|
|
192
|
-
| MiniMax Token Plan
|
|
193
|
-
|
|
|
194
|
-
|
|
|
195
|
-
|
|
|
196
|
-
|
|
|
187
|
+
| Provider | Auth/setup | Data from | Reports |
|
|
188
|
+
| ----------------------------- | ---------------------------------------------------------------------------- | -------------- | ------------------ |
|
|
189
|
+
| Alibaba Coding Plan | Automatic | Local estimate | Quota |
|
|
190
|
+
| Alibaba Personal Token Plan | [Needs setup](docs/readme/providers.md#alibaba-personal-token-plan) | Official CLI | Quota |
|
|
191
|
+
| DeepSeek | Automatic | Remote API | Balance and status |
|
|
192
|
+
| Kimi Code | Automatic | Remote API | Quota |
|
|
193
|
+
| MiniMax Token Plan | Automatic | Remote API | Quota |
|
|
194
|
+
| MiniMax Token Plan (CN) | Automatic | Remote API | Quota |
|
|
195
|
+
| Qwen Code | [Needs setup](docs/readme/providers.md#qwen-code) | Local estimate | Quota |
|
|
196
|
+
| Xiaomi MiMo | [Needs setup](docs/readme/providers.md#xiaomi-mimo) | Dashboard API | Quota and balance |
|
|
197
|
+
| Z.ai Coding Plan | Automatic | Remote API | Quota |
|
|
198
|
+
| Zhipu Coding Plan | Automatic | Remote API | Quota |
|
|
197
199
|
|
|
198
200
|
</details>
|
|
199
201
|
|
|
@@ -268,4 +270,4 @@ OpenCode Quota is not built by the OpenCode team and is not affiliated with Open
|
|
|
268
270
|
|
|
269
271
|
## Star history
|
|
270
272
|
|
|
271
|
-
](https://www.star-history.com/#slkiser/opencode-quota&Date)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alibaba Personal Token Plan quota from the official `bl` CLI.
|
|
3
|
+
*
|
|
4
|
+
* Production invocation is exactly:
|
|
5
|
+
* bl usage token-plan --output json
|
|
6
|
+
*
|
|
7
|
+
* Console authentication belongs to the CLI. This module never installs `bl`,
|
|
8
|
+
* never runs login, never reads credential stores, and never uses a shell.
|
|
9
|
+
*/
|
|
10
|
+
export declare const ALIBABA_TOKEN_PLAN_COMMAND = "bl";
|
|
11
|
+
export declare const ALIBABA_TOKEN_PLAN_ARGS: readonly ["usage", "token-plan", "--output", "json"];
|
|
12
|
+
export declare const ALIBABA_TOKEN_PLAN_STDOUT_LIMIT_BYTES: number;
|
|
13
|
+
export declare const ALIBABA_TOKEN_PLAN_STDERR_LIMIT_BYTES: number;
|
|
14
|
+
export declare const ALIBABA_TOKEN_PLAN_KILL_GRACE_MS = 250;
|
|
15
|
+
export declare const ALIBABA_TOKEN_PLAN_AUTH_EXIT_CODE = 3;
|
|
16
|
+
export type AlibabaTokenPlanWindow = {
|
|
17
|
+
percentRemaining: number;
|
|
18
|
+
resetTimeIso?: string;
|
|
19
|
+
};
|
|
20
|
+
export type AlibabaTokenPlanErrorKind = "unsupported_platform" | "executable_not_found" | "shell_launcher_rejected" | "workspace_path_rejected" | "spawn_failed" | "timeout" | "output_truncated" | "nonzero_exit" | "not_authenticated" | "invalid_json" | "invalid_schema" | "no_data";
|
|
21
|
+
export type AlibabaTokenPlanError = {
|
|
22
|
+
kind: AlibabaTokenPlanErrorKind;
|
|
23
|
+
message: string;
|
|
24
|
+
retryable?: boolean;
|
|
25
|
+
};
|
|
26
|
+
export type AlibabaTokenPlanClosedResult = {
|
|
27
|
+
ok: true;
|
|
28
|
+
fiveHour?: AlibabaTokenPlanWindow;
|
|
29
|
+
weekly?: AlibabaTokenPlanWindow;
|
|
30
|
+
} | {
|
|
31
|
+
ok: false;
|
|
32
|
+
error: AlibabaTokenPlanError;
|
|
33
|
+
};
|
|
34
|
+
export type AlibabaTokenPlanSpawnRequest = {
|
|
35
|
+
file: string;
|
|
36
|
+
args: readonly string[];
|
|
37
|
+
cwd: string;
|
|
38
|
+
env: NodeJS.ProcessEnv;
|
|
39
|
+
timeoutMs: number;
|
|
40
|
+
stdoutLimitBytes: number;
|
|
41
|
+
stderrLimitBytes: number;
|
|
42
|
+
stdin: "ignore";
|
|
43
|
+
shell: false;
|
|
44
|
+
platform: NodeJS.Platform;
|
|
45
|
+
killGraceMs: number;
|
|
46
|
+
};
|
|
47
|
+
export type AlibabaTokenPlanSpawnResult = {
|
|
48
|
+
code: number | null;
|
|
49
|
+
stdout: Buffer;
|
|
50
|
+
stderr: Buffer;
|
|
51
|
+
timedOut: boolean;
|
|
52
|
+
truncated: boolean;
|
|
53
|
+
spawnErrorCode?: string;
|
|
54
|
+
};
|
|
55
|
+
export type AlibabaTokenPlanExecutableResolution = {
|
|
56
|
+
ok: true;
|
|
57
|
+
file: string;
|
|
58
|
+
} | {
|
|
59
|
+
ok: false;
|
|
60
|
+
error: AlibabaTokenPlanError;
|
|
61
|
+
};
|
|
62
|
+
export type AlibabaTokenPlanRuntime = {
|
|
63
|
+
platform?: NodeJS.Platform;
|
|
64
|
+
pathEnv?: string;
|
|
65
|
+
cwd?: string;
|
|
66
|
+
tmpdir?: string;
|
|
67
|
+
homedir?: string;
|
|
68
|
+
env?: NodeJS.ProcessEnv;
|
|
69
|
+
spawn?: (request: AlibabaTokenPlanSpawnRequest) => Promise<AlibabaTokenPlanSpawnResult>;
|
|
70
|
+
};
|
|
71
|
+
export declare function isAlibabaTokenPlanSupportedPlatform(platform?: NodeJS.Platform): boolean;
|
|
72
|
+
export declare function listTrustedPathDirectories(params: {
|
|
73
|
+
pathEnv: string | undefined;
|
|
74
|
+
cwd: string;
|
|
75
|
+
platform: NodeJS.Platform;
|
|
76
|
+
}): string[];
|
|
77
|
+
export declare function resolveAlibabaTokenPlanExecutable(params: {
|
|
78
|
+
pathEnv?: string;
|
|
79
|
+
cwd?: string;
|
|
80
|
+
platform?: NodeJS.Platform;
|
|
81
|
+
}): Promise<AlibabaTokenPlanExecutableResolution>;
|
|
82
|
+
export declare function parseAlibabaTokenPlanUsageJson(text: string): AlibabaTokenPlanClosedResult;
|
|
83
|
+
export declare function runAlibabaTokenPlanProcess(request: AlibabaTokenPlanSpawnRequest): Promise<AlibabaTokenPlanSpawnResult>;
|
|
84
|
+
export declare function queryAlibabaTokenPlanQuota(options?: {
|
|
85
|
+
requestTimeoutMs?: number;
|
|
86
|
+
runtime?: AlibabaTokenPlanRuntime;
|
|
87
|
+
}): Promise<AlibabaTokenPlanClosedResult>;
|
|
88
|
+
//# sourceMappingURL=alibaba-token-plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alibaba-token-plan.d.ts","sourceRoot":"","sources":["../../src/lib/alibaba-token-plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAC/C,eAAO,MAAM,uBAAuB,YAAI,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,CAAU,CAAC;AAC5F,eAAO,MAAM,qCAAqC,QAAa,CAAC;AAChE,eAAO,MAAM,qCAAqC,QAAY,CAAC;AAC/D,eAAO,MAAM,gCAAgC,MAAM,CAAC;AACpD,eAAO,MAAM,iCAAiC,IAAI,CAAC;AAoCnD,MAAM,MAAM,sBAAsB,GAAG;IACnC,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GACjC,sBAAsB,GACtB,sBAAsB,GACtB,yBAAyB,GACzB,yBAAyB,GACzB,cAAc,GACd,SAAS,GACT,kBAAkB,GAClB,cAAc,GACd,mBAAmB,GACnB,cAAc,GACd,gBAAgB,GAChB,SAAS,CAAC;AAEd,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,yBAAyB,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,4BAA4B,GACpC;IACE,EAAE,EAAE,IAAI,CAAC;IACT,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,MAAM,CAAC,EAAE,sBAAsB,CAAC;CACjC,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,qBAAqB,CAAC;CAC9B,CAAC;AAEN,MAAM,MAAM,4BAA4B,GAAG;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,QAAQ,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAC5C;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC1B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,qBAAqB,CAAA;CAAE,CAAC;AAEhD,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,4BAA4B,KAAK,OAAO,CAAC,2BAA2B,CAAC,CAAC;CACzF,CAAC;AA6CF,wBAAgB,mCAAmC,CACjD,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAET;AA0BD,wBAAgB,0BAA0B,CAAC,MAAM,EAAE;IACjD,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC3B,GAAG,MAAM,EAAE,CAoBX;AA0BD,wBAAsB,iCAAiC,CAAC,MAAM,EAAE;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC5B,GAAG,OAAO,CAAC,oCAAoC,CAAC,CAgDhD;AAyDD,wBAAgB,8BAA8B,CAAC,IAAI,EAAE,MAAM,GAAG,4BAA4B,CAqBzF;AAqED,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,2BAA2B,CAAC,CA6HtC;AAYD,wBAAsB,0BAA0B,CAC9C,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,uBAAuB,CAAA;CAAO,GAC7E,OAAO,CAAC,4BAA4B,CAAC,CAgDvC"}
|
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alibaba Personal Token Plan quota from the official `bl` CLI.
|
|
3
|
+
*
|
|
4
|
+
* Production invocation is exactly:
|
|
5
|
+
* bl usage token-plan --output json
|
|
6
|
+
*
|
|
7
|
+
* Console authentication belongs to the CLI. This module never installs `bl`,
|
|
8
|
+
* never runs login, never reads credential stores, and never uses a shell.
|
|
9
|
+
*/
|
|
10
|
+
import { spawn } from "node:child_process";
|
|
11
|
+
import { lstat, realpath } from "node:fs/promises";
|
|
12
|
+
import { homedir, tmpdir } from "node:os";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { REQUEST_TIMEOUT_MS } from "./types.js";
|
|
15
|
+
export const ALIBABA_TOKEN_PLAN_COMMAND = "bl";
|
|
16
|
+
export const ALIBABA_TOKEN_PLAN_ARGS = ["usage", "token-plan", "--output", "json"];
|
|
17
|
+
export const ALIBABA_TOKEN_PLAN_STDOUT_LIMIT_BYTES = 256 * 1024;
|
|
18
|
+
export const ALIBABA_TOKEN_PLAN_STDERR_LIMIT_BYTES = 16 * 1024;
|
|
19
|
+
export const ALIBABA_TOKEN_PLAN_KILL_GRACE_MS = 250;
|
|
20
|
+
export const ALIBABA_TOKEN_PLAN_AUTH_EXIT_CODE = 3;
|
|
21
|
+
const SUPPORTED_PLATFORMS = new Set(["darwin", "linux"]);
|
|
22
|
+
const MAX_RESET_TIME_MS = 4_102_444_800_000;
|
|
23
|
+
const SHELL_LAUNCHER_NAMES = new Set([
|
|
24
|
+
"bash",
|
|
25
|
+
"bash.exe",
|
|
26
|
+
"cmd",
|
|
27
|
+
"cmd.exe",
|
|
28
|
+
"command.com",
|
|
29
|
+
"cscript.exe",
|
|
30
|
+
"csh",
|
|
31
|
+
"dash",
|
|
32
|
+
"fish",
|
|
33
|
+
"ksh",
|
|
34
|
+
"powershell",
|
|
35
|
+
"powershell.exe",
|
|
36
|
+
"pwsh",
|
|
37
|
+
"pwsh.exe",
|
|
38
|
+
"sh",
|
|
39
|
+
"sh.exe",
|
|
40
|
+
"tcsh",
|
|
41
|
+
"wscript.exe",
|
|
42
|
+
"zsh",
|
|
43
|
+
]);
|
|
44
|
+
const SHELL_LAUNCHER_EXTENSIONS = new Set([
|
|
45
|
+
".bash",
|
|
46
|
+
".bat",
|
|
47
|
+
".cmd",
|
|
48
|
+
".com",
|
|
49
|
+
".fish",
|
|
50
|
+
".ps1",
|
|
51
|
+
".sh",
|
|
52
|
+
".zsh",
|
|
53
|
+
]);
|
|
54
|
+
const ERROR_MESSAGES = {
|
|
55
|
+
unsupported_platform: "Alibaba Personal Token Plan is not supported on this platform.",
|
|
56
|
+
executable_not_found: "Alibaba Cloud CLI (bl) was not found on the trusted PATH.",
|
|
57
|
+
shell_launcher_rejected: "Alibaba Personal Token Plan refused a shell launcher for bl.",
|
|
58
|
+
workspace_path_rejected: "Alibaba Personal Token Plan ignored workspace and relative PATH entries.",
|
|
59
|
+
spawn_failed: "Could not start Alibaba Cloud CLI.",
|
|
60
|
+
timeout: "Timed out while running the Alibaba Cloud CLI.",
|
|
61
|
+
output_truncated: "Alibaba Cloud CLI output exceeded the bounded size limit.",
|
|
62
|
+
nonzero_exit: "Could not read Alibaba Personal Token Plan quota.",
|
|
63
|
+
not_authenticated: "Alibaba Cloud console session is missing or expired. Run `bl auth login --console`.",
|
|
64
|
+
invalid_json: "Alibaba Cloud CLI did not return valid JSON.",
|
|
65
|
+
invalid_schema: "Alibaba Cloud CLI returned an invalid Personal Token Plan payload.",
|
|
66
|
+
no_data: "Alibaba Personal Token Plan usage returned no quota windows.",
|
|
67
|
+
};
|
|
68
|
+
function fail(kind, retryable) {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
error: {
|
|
72
|
+
kind,
|
|
73
|
+
message: ERROR_MESSAGES[kind],
|
|
74
|
+
...(retryable ? { retryable: true } : {}),
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function executableResolutionError(kind) {
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
error: {
|
|
82
|
+
kind,
|
|
83
|
+
message: ERROR_MESSAGES[kind],
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export function isAlibabaTokenPlanSupportedPlatform(platform = process.platform) {
|
|
88
|
+
return SUPPORTED_PLATFORMS.has(platform);
|
|
89
|
+
}
|
|
90
|
+
function runtimePath(platform) {
|
|
91
|
+
return platform === "win32" ? path.win32 : path.posix;
|
|
92
|
+
}
|
|
93
|
+
function isPathInside(parent, candidate, paths) {
|
|
94
|
+
const relative = paths.relative(parent, candidate);
|
|
95
|
+
return (relative === "" ||
|
|
96
|
+
(relative !== ".." && !relative.startsWith(`..${paths.sep}`) && !paths.isAbsolute(relative)));
|
|
97
|
+
}
|
|
98
|
+
function stripPathQuotes(value) {
|
|
99
|
+
const trimmed = value.trim();
|
|
100
|
+
if (trimmed.length >= 2 &&
|
|
101
|
+
((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
102
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'")))) {
|
|
103
|
+
return trimmed.slice(1, -1);
|
|
104
|
+
}
|
|
105
|
+
return trimmed;
|
|
106
|
+
}
|
|
107
|
+
export function listTrustedPathDirectories(params) {
|
|
108
|
+
const paths = runtimePath(params.platform);
|
|
109
|
+
const delimiter = paths.delimiter;
|
|
110
|
+
const cwd = paths.resolve(params.cwd);
|
|
111
|
+
const trusted = [];
|
|
112
|
+
const seen = new Set();
|
|
113
|
+
for (const rawEntry of (params.pathEnv ?? "").split(delimiter)) {
|
|
114
|
+
const entry = stripPathQuotes(rawEntry);
|
|
115
|
+
if (!entry || entry === "." || entry === "..")
|
|
116
|
+
continue;
|
|
117
|
+
if (!paths.isAbsolute(entry))
|
|
118
|
+
continue;
|
|
119
|
+
const resolved = paths.resolve(entry);
|
|
120
|
+
if (isPathInside(cwd, resolved, paths))
|
|
121
|
+
continue;
|
|
122
|
+
if (seen.has(resolved))
|
|
123
|
+
continue;
|
|
124
|
+
seen.add(resolved);
|
|
125
|
+
trusted.push(resolved);
|
|
126
|
+
}
|
|
127
|
+
return trusted;
|
|
128
|
+
}
|
|
129
|
+
function isShellLauncherPath(file) {
|
|
130
|
+
const extension = path.posix.extname(file).toLowerCase();
|
|
131
|
+
if (SHELL_LAUNCHER_EXTENSIONS.has(extension))
|
|
132
|
+
return true;
|
|
133
|
+
return SHELL_LAUNCHER_NAMES.has(path.posix.basename(file).toLowerCase());
|
|
134
|
+
}
|
|
135
|
+
function candidateExecutableNames() {
|
|
136
|
+
return [ALIBABA_TOKEN_PLAN_COMMAND, "bl.cmd", "bl.bat"];
|
|
137
|
+
}
|
|
138
|
+
async function fileLooksLikeExecutable(file) {
|
|
139
|
+
try {
|
|
140
|
+
const stats = await lstat(file);
|
|
141
|
+
if (stats.isSymbolicLink()) {
|
|
142
|
+
const target = await realpath(file);
|
|
143
|
+
const targetStats = await lstat(target);
|
|
144
|
+
return targetStats.isFile() || targetStats.isSymbolicLink();
|
|
145
|
+
}
|
|
146
|
+
return stats.isFile();
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
export async function resolveAlibabaTokenPlanExecutable(params) {
|
|
153
|
+
const platform = params.platform ?? process.platform;
|
|
154
|
+
if (!isAlibabaTokenPlanSupportedPlatform(platform)) {
|
|
155
|
+
return executableResolutionError("unsupported_platform");
|
|
156
|
+
}
|
|
157
|
+
const paths = runtimePath(platform);
|
|
158
|
+
const cwd = paths.resolve(params.cwd ?? process.cwd());
|
|
159
|
+
const trusted = listTrustedPathDirectories({
|
|
160
|
+
pathEnv: params.pathEnv ?? process.env.PATH,
|
|
161
|
+
cwd,
|
|
162
|
+
platform,
|
|
163
|
+
});
|
|
164
|
+
if (trusted.length === 0) {
|
|
165
|
+
return executableResolutionError("executable_not_found");
|
|
166
|
+
}
|
|
167
|
+
let sawShellLauncher = false;
|
|
168
|
+
for (const directory of trusted) {
|
|
169
|
+
for (const name of candidateExecutableNames()) {
|
|
170
|
+
const candidate = paths.join(directory, name);
|
|
171
|
+
if (!(await fileLooksLikeExecutable(candidate)))
|
|
172
|
+
continue;
|
|
173
|
+
if (isShellLauncherPath(candidate)) {
|
|
174
|
+
sawShellLauncher = true;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
let resolved = candidate;
|
|
178
|
+
try {
|
|
179
|
+
resolved = await realpath(candidate);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
resolved = candidate;
|
|
183
|
+
}
|
|
184
|
+
if (isShellLauncherPath(resolved)) {
|
|
185
|
+
sawShellLauncher = true;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (isPathInside(cwd, resolved, paths)) {
|
|
189
|
+
return executableResolutionError("workspace_path_rejected");
|
|
190
|
+
}
|
|
191
|
+
return { ok: true, file: resolved };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (sawShellLauncher) {
|
|
195
|
+
return executableResolutionError("shell_launcher_rejected");
|
|
196
|
+
}
|
|
197
|
+
return executableResolutionError("executable_not_found");
|
|
198
|
+
}
|
|
199
|
+
function asRecord(value) {
|
|
200
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
201
|
+
? value
|
|
202
|
+
: null;
|
|
203
|
+
}
|
|
204
|
+
function hasOwn(record, key) {
|
|
205
|
+
return Object.hasOwn(record, key);
|
|
206
|
+
}
|
|
207
|
+
function usedFractionToPercentRemaining(used) {
|
|
208
|
+
return 100 - used * 100;
|
|
209
|
+
}
|
|
210
|
+
function parseResetTimeIso(value) {
|
|
211
|
+
if (value === undefined)
|
|
212
|
+
return undefined;
|
|
213
|
+
if (typeof value !== "number" ||
|
|
214
|
+
!Number.isFinite(value) ||
|
|
215
|
+
value <= 0 ||
|
|
216
|
+
value > MAX_RESET_TIME_MS) {
|
|
217
|
+
return "invalid";
|
|
218
|
+
}
|
|
219
|
+
const iso = new Date(value).toISOString();
|
|
220
|
+
if (iso === "Invalid Date")
|
|
221
|
+
return "invalid";
|
|
222
|
+
return iso;
|
|
223
|
+
}
|
|
224
|
+
function parseWindow(payload, prefix) {
|
|
225
|
+
const percentageKey = `${prefix}Percentage`;
|
|
226
|
+
const resetKey = `${prefix}ResetTime`;
|
|
227
|
+
const percentagePresent = hasOwn(payload, percentageKey);
|
|
228
|
+
const resetPresent = hasOwn(payload, resetKey);
|
|
229
|
+
if (!percentagePresent && !resetPresent)
|
|
230
|
+
return undefined;
|
|
231
|
+
if (!percentagePresent)
|
|
232
|
+
return "invalid";
|
|
233
|
+
const used = payload[percentageKey];
|
|
234
|
+
if (typeof used !== "number" || !Number.isFinite(used) || used < 0 || used > 1) {
|
|
235
|
+
return "invalid";
|
|
236
|
+
}
|
|
237
|
+
if (!resetPresent) {
|
|
238
|
+
return { percentRemaining: usedFractionToPercentRemaining(used) };
|
|
239
|
+
}
|
|
240
|
+
const resetTimeIso = parseResetTimeIso(payload[resetKey]);
|
|
241
|
+
if (resetTimeIso === "invalid")
|
|
242
|
+
return "invalid";
|
|
243
|
+
return {
|
|
244
|
+
percentRemaining: usedFractionToPercentRemaining(used),
|
|
245
|
+
...(resetTimeIso ? { resetTimeIso } : {}),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
export function parseAlibabaTokenPlanUsageJson(text) {
|
|
249
|
+
let payload;
|
|
250
|
+
try {
|
|
251
|
+
payload = JSON.parse(text);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
return fail("invalid_json");
|
|
255
|
+
}
|
|
256
|
+
const record = asRecord(payload);
|
|
257
|
+
if (!record)
|
|
258
|
+
return fail("invalid_schema");
|
|
259
|
+
const fiveHour = parseWindow(record, "per5Hour");
|
|
260
|
+
if (fiveHour === "invalid")
|
|
261
|
+
return fail("invalid_schema");
|
|
262
|
+
const weekly = parseWindow(record, "per1Week");
|
|
263
|
+
if (weekly === "invalid")
|
|
264
|
+
return fail("invalid_schema");
|
|
265
|
+
if (!fiveHour && !weekly)
|
|
266
|
+
return fail("no_data");
|
|
267
|
+
return {
|
|
268
|
+
ok: true,
|
|
269
|
+
...(fiveHour ? { fiveHour } : {}),
|
|
270
|
+
...(weekly ? { weekly } : {}),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function resolveNonWorkspaceCwd(params) {
|
|
274
|
+
const paths = runtimePath(params.platform);
|
|
275
|
+
const workspace = paths.resolve(params.cwd);
|
|
276
|
+
for (const candidate of [
|
|
277
|
+
params.tmpdir,
|
|
278
|
+
params.homedir,
|
|
279
|
+
params.platform === "win32" ? "C:\\" : "/",
|
|
280
|
+
]) {
|
|
281
|
+
const resolved = paths.resolve(candidate);
|
|
282
|
+
if (!isPathInside(workspace, resolved, paths))
|
|
283
|
+
return resolved;
|
|
284
|
+
}
|
|
285
|
+
return params.platform === "win32" ? "C:\\" : "/";
|
|
286
|
+
}
|
|
287
|
+
function trustedPathEnv(params) {
|
|
288
|
+
return params.trustedDirectories.join(runtimePath(params.platform).delimiter);
|
|
289
|
+
}
|
|
290
|
+
function takeBoundedChunk(chunks, currentSize, chunk, limit) {
|
|
291
|
+
const remaining = limit - currentSize;
|
|
292
|
+
if (remaining <= 0)
|
|
293
|
+
return { size: currentSize, overflowed: true };
|
|
294
|
+
if (chunk.length <= remaining) {
|
|
295
|
+
chunks.push(chunk);
|
|
296
|
+
return { size: currentSize + chunk.length, overflowed: false };
|
|
297
|
+
}
|
|
298
|
+
chunks.push(chunk.subarray(0, remaining));
|
|
299
|
+
return { size: limit, overflowed: true };
|
|
300
|
+
}
|
|
301
|
+
function signalProcessGroup(child, platform, signal) {
|
|
302
|
+
if (!child.pid)
|
|
303
|
+
return;
|
|
304
|
+
if (platform === "win32") {
|
|
305
|
+
try {
|
|
306
|
+
child.kill(signal);
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
// already exited
|
|
310
|
+
}
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
try {
|
|
314
|
+
process.kill(-child.pid, signal);
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
try {
|
|
318
|
+
child.kill(signal);
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
// already exited
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
export async function runAlibabaTokenPlanProcess(request) {
|
|
326
|
+
return await new Promise((resolve) => {
|
|
327
|
+
let child;
|
|
328
|
+
try {
|
|
329
|
+
child = spawn(request.file, [...request.args], {
|
|
330
|
+
cwd: request.cwd,
|
|
331
|
+
env: request.env,
|
|
332
|
+
argv0: request.file,
|
|
333
|
+
shell: false,
|
|
334
|
+
windowsHide: true,
|
|
335
|
+
detached: request.platform !== "win32",
|
|
336
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
const spawnErrorCode = error && typeof error === "object" && "code" in error
|
|
341
|
+
? String(error.code)
|
|
342
|
+
: undefined;
|
|
343
|
+
resolve({
|
|
344
|
+
code: null,
|
|
345
|
+
stdout: Buffer.alloc(0),
|
|
346
|
+
stderr: Buffer.alloc(0),
|
|
347
|
+
timedOut: false,
|
|
348
|
+
truncated: false,
|
|
349
|
+
spawnErrorCode,
|
|
350
|
+
});
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const stdoutChunks = [];
|
|
354
|
+
const stderrChunks = [];
|
|
355
|
+
let stdoutSize = 0;
|
|
356
|
+
let stderrSize = 0;
|
|
357
|
+
let timedOut = false;
|
|
358
|
+
let truncated = false;
|
|
359
|
+
let killTimer;
|
|
360
|
+
let timeout;
|
|
361
|
+
let settled = false;
|
|
362
|
+
let escalated = false;
|
|
363
|
+
function clearKillTimer() {
|
|
364
|
+
if (killTimer === undefined)
|
|
365
|
+
return;
|
|
366
|
+
clearTimeout(killTimer);
|
|
367
|
+
killTimer = undefined;
|
|
368
|
+
}
|
|
369
|
+
function escalate() {
|
|
370
|
+
if (!escalated) {
|
|
371
|
+
escalated = true;
|
|
372
|
+
signalProcessGroup(child, request.platform, "SIGTERM");
|
|
373
|
+
}
|
|
374
|
+
clearKillTimer();
|
|
375
|
+
killTimer = setTimeout(() => {
|
|
376
|
+
killTimer = undefined;
|
|
377
|
+
signalProcessGroup(child, request.platform, "SIGKILL");
|
|
378
|
+
}, request.killGraceMs);
|
|
379
|
+
}
|
|
380
|
+
function onStdoutData(chunk) {
|
|
381
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
382
|
+
const next = takeBoundedChunk(stdoutChunks, stdoutSize, buf, request.stdoutLimitBytes);
|
|
383
|
+
stdoutSize = next.size;
|
|
384
|
+
if (next.overflowed && !truncated) {
|
|
385
|
+
truncated = true;
|
|
386
|
+
escalate();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function onStderrData(chunk) {
|
|
390
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
391
|
+
const next = takeBoundedChunk(stderrChunks, stderrSize, buf, request.stderrLimitBytes);
|
|
392
|
+
stderrSize = next.size;
|
|
393
|
+
if (next.overflowed && !truncated) {
|
|
394
|
+
truncated = true;
|
|
395
|
+
escalate();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function spawnErrorCodeFrom(error) {
|
|
399
|
+
return error && typeof error === "object" && "code" in error
|
|
400
|
+
? String(error.code)
|
|
401
|
+
: undefined;
|
|
402
|
+
}
|
|
403
|
+
function removeListeners() {
|
|
404
|
+
child.stdout?.removeListener("data", onStdoutData);
|
|
405
|
+
child.stderr?.removeListener("data", onStderrData);
|
|
406
|
+
child.removeListener("error", onError);
|
|
407
|
+
child.removeListener("close", onClose);
|
|
408
|
+
}
|
|
409
|
+
function finish(code, spawnErrorCode) {
|
|
410
|
+
if (settled)
|
|
411
|
+
return;
|
|
412
|
+
settled = true;
|
|
413
|
+
if (timeout !== undefined)
|
|
414
|
+
clearTimeout(timeout);
|
|
415
|
+
clearKillTimer();
|
|
416
|
+
removeListeners();
|
|
417
|
+
resolve({
|
|
418
|
+
code,
|
|
419
|
+
stdout: Buffer.concat(stdoutChunks),
|
|
420
|
+
stderr: Buffer.concat(stderrChunks),
|
|
421
|
+
timedOut,
|
|
422
|
+
truncated,
|
|
423
|
+
...(spawnErrorCode ? { spawnErrorCode } : {}),
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
function onError(error) {
|
|
427
|
+
finish(null, spawnErrorCodeFrom(error));
|
|
428
|
+
}
|
|
429
|
+
function onClose(code) {
|
|
430
|
+
finish(code);
|
|
431
|
+
}
|
|
432
|
+
timeout = setTimeout(() => {
|
|
433
|
+
timedOut = true;
|
|
434
|
+
escalate();
|
|
435
|
+
}, request.timeoutMs);
|
|
436
|
+
child.stdout?.on("data", onStdoutData);
|
|
437
|
+
child.stderr?.on("data", onStderrData);
|
|
438
|
+
child.on("error", onError);
|
|
439
|
+
child.on("close", onClose);
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
function mapSpawnFailure(result) {
|
|
443
|
+
if (result.truncated)
|
|
444
|
+
return fail("output_truncated");
|
|
445
|
+
if (result.timedOut)
|
|
446
|
+
return fail("timeout", true);
|
|
447
|
+
if (result.spawnErrorCode === "ENOENT")
|
|
448
|
+
return fail("executable_not_found");
|
|
449
|
+
if (result.spawnErrorCode)
|
|
450
|
+
return fail("spawn_failed", true);
|
|
451
|
+
if (result.code === ALIBABA_TOKEN_PLAN_AUTH_EXIT_CODE)
|
|
452
|
+
return fail("not_authenticated");
|
|
453
|
+
if (result.code !== 0)
|
|
454
|
+
return fail("nonzero_exit");
|
|
455
|
+
return parseAlibabaTokenPlanUsageJson(result.stdout.toString("utf8"));
|
|
456
|
+
}
|
|
457
|
+
export async function queryAlibabaTokenPlanQuota(options = {}) {
|
|
458
|
+
const runtime = options.runtime ?? {};
|
|
459
|
+
const platform = runtime.platform ?? process.platform;
|
|
460
|
+
if (!isAlibabaTokenPlanSupportedPlatform(platform)) {
|
|
461
|
+
return fail("unsupported_platform");
|
|
462
|
+
}
|
|
463
|
+
const cwd = runtimePath(platform).resolve(runtime.cwd ?? process.cwd());
|
|
464
|
+
const pathEnv = runtime.pathEnv ?? runtime.env?.PATH ?? process.env.PATH;
|
|
465
|
+
const resolved = await resolveAlibabaTokenPlanExecutable({
|
|
466
|
+
pathEnv,
|
|
467
|
+
cwd,
|
|
468
|
+
platform,
|
|
469
|
+
});
|
|
470
|
+
if (!resolved.ok) {
|
|
471
|
+
return { ok: false, error: resolved.error };
|
|
472
|
+
}
|
|
473
|
+
const trustedDirectories = listTrustedPathDirectories({ pathEnv, cwd, platform });
|
|
474
|
+
const processCwd = resolveNonWorkspaceCwd({
|
|
475
|
+
cwd,
|
|
476
|
+
tmpdir: runtime.tmpdir ?? tmpdir(),
|
|
477
|
+
homedir: runtime.homedir ?? homedir(),
|
|
478
|
+
platform,
|
|
479
|
+
});
|
|
480
|
+
const env = {
|
|
481
|
+
...(runtime.env ?? process.env),
|
|
482
|
+
PATH: trustedPathEnv({ trustedDirectories, platform }),
|
|
483
|
+
};
|
|
484
|
+
const request = {
|
|
485
|
+
file: resolved.file,
|
|
486
|
+
args: ALIBABA_TOKEN_PLAN_ARGS,
|
|
487
|
+
cwd: processCwd,
|
|
488
|
+
env,
|
|
489
|
+
timeoutMs: options.requestTimeoutMs ?? REQUEST_TIMEOUT_MS,
|
|
490
|
+
stdoutLimitBytes: ALIBABA_TOKEN_PLAN_STDOUT_LIMIT_BYTES,
|
|
491
|
+
stderrLimitBytes: ALIBABA_TOKEN_PLAN_STDERR_LIMIT_BYTES,
|
|
492
|
+
stdin: "ignore",
|
|
493
|
+
shell: false,
|
|
494
|
+
platform,
|
|
495
|
+
killGraceMs: ALIBABA_TOKEN_PLAN_KILL_GRACE_MS,
|
|
496
|
+
};
|
|
497
|
+
const run = runtime.spawn ?? runAlibabaTokenPlanProcess;
|
|
498
|
+
try {
|
|
499
|
+
return mapSpawnFailure(await run(request));
|
|
500
|
+
}
|
|
501
|
+
catch {
|
|
502
|
+
return fail("spawn_failed", true);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
//# sourceMappingURL=alibaba-token-plan.js.map
|