@ekworp/opencode-mystatus 1.2.7 → 1.2.10
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 +22 -7
- package/README.zh-CN.md +23 -8
- package/dist/plugin/lib/config.d.ts +10 -0
- package/dist/plugin/lib/config.js +36 -0
- package/dist/plugin/lib/copilot.d.ts +0 -1
- package/dist/plugin/lib/deepseek.d.ts +3 -0
- package/dist/plugin/lib/deepseek.js +48 -0
- package/dist/plugin/lib/google.d.ts +0 -1
- package/dist/plugin/lib/google.js +1 -1
- package/dist/plugin/lib/i18n.d.ts +12 -3
- package/dist/plugin/lib/i18n.js +14 -4
- package/dist/plugin/lib/openai.d.ts +0 -1
- package/dist/plugin/lib/opencode-auth.d.ts +9 -0
- package/dist/plugin/lib/opencode-auth.js +48 -0
- package/dist/plugin/lib/types.d.ts +6 -1
- package/dist/plugin/lib/utils.d.ts +2 -1
- package/dist/plugin/lib/utils.js +7 -0
- package/dist/plugin/lib/zhipu.d.ts +0 -1
- package/dist/plugin/mystatus.d.ts +2 -3
- package/dist/plugin/mystatus.js +70 -19
- package/package.json +18 -13
package/README.md
CHANGED
|
@@ -14,10 +14,11 @@ An [OpenCode](https://opencode.ai) plugin to query account quota usage for multi
|
|
|
14
14
|
|
|
15
15
|
| Platform | Account Type | Data Source |
|
|
16
16
|
| ------------ | ----------------- | ---------------------------------------------- |
|
|
17
|
-
| OpenAI | Plus / Team / Pro | `~/.local/share/opencode/
|
|
18
|
-
| Zhipu AI | Coding Plan | `~/.local/share/opencode/
|
|
19
|
-
| Z.ai | Coding Plan | `~/.local/share/opencode/
|
|
20
|
-
|
|
|
17
|
+
| OpenAI | Plus / Team / Pro | `~/.local/share/opencode/opencode.db` |
|
|
18
|
+
| Zhipu AI | Coding Plan | `~/.local/share/opencode/opencode.db` |
|
|
19
|
+
| Z.ai | Coding Plan | `~/.local/share/opencode/opencode.db` |
|
|
20
|
+
| DeepSeek | API balance | `~/.local/share/opencode/opencode.db` |
|
|
21
|
+
| GitHub Copilot | Individual / Business | `~/.local/share/opencode/opencode.db` |
|
|
21
22
|
| Google Cloud | Antigravity | `~/.config/opencode/antigravity-accounts.json` |
|
|
22
23
|
|
|
23
24
|
## Installation
|
|
@@ -27,7 +28,7 @@ An [OpenCode](https://opencode.ai) plugin to query account quota usage for multi
|
|
|
27
28
|
Paste this into any LLM agent (Claude Code, OpenCode, Cursor, etc.):
|
|
28
29
|
|
|
29
30
|
```
|
|
30
|
-
Install the opencode-mystatus plugin by following: https://raw.githubusercontent.com/
|
|
31
|
+
Install the opencode-mystatus plugin by following: https://raw.githubusercontent.com/ekworp/opencode-mystatus/main/README.md
|
|
31
32
|
```
|
|
32
33
|
|
|
33
34
|
<details>
|
|
@@ -164,9 +165,23 @@ Claude 2d 9h ░░░░░░░░░░░░░░░░░░░
|
|
|
164
165
|
|
|
165
166
|
No additional configuration required. The plugin automatically reads credentials from:
|
|
166
167
|
|
|
167
|
-
- **OpenAI, Zhipu AI, Z.ai & GitHub Copilot**: `~/.local/share/opencode/
|
|
168
|
+
- **OpenAI, Zhipu AI, Z.ai, DeepSeek & GitHub Copilot**: `~/.local/share/opencode/opencode.db`
|
|
168
169
|
- **Google Cloud**: `~/.config/opencode/antigravity-accounts.json`
|
|
169
170
|
|
|
171
|
+
### Disable providers
|
|
172
|
+
|
|
173
|
+
Create `~/.config/opencode/mystatus.json` to skip selected providers:
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"disabledProviders": ["deepseek", "google"],
|
|
178
|
+
"useOpenCodeProxy": true
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Available names: `openai`, `zhipuai-coding-plan`, `zai-coding-plan`, `deepseek`, `google`, `github-copilot`.
|
|
183
|
+
`useOpenCodeProxy` defaults to `true` and uses the proxy environment configured for the OpenCode service.
|
|
184
|
+
|
|
170
185
|
### Google Cloud Setup
|
|
171
186
|
|
|
172
187
|
To query Google Cloud (Antigravity) account quota, you need to install the [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) plugin first to authenticate your Google account.
|
|
@@ -177,7 +192,7 @@ This plugin is safe to use:
|
|
|
177
192
|
|
|
178
193
|
**Local Files Accessed (read-only):**
|
|
179
194
|
|
|
180
|
-
- `~/.local/share/opencode/
|
|
195
|
+
- `~/.local/share/opencode/opencode.db` - OpenCode V2's official credential storage
|
|
181
196
|
- `~/.config/opencode/antigravity-accounts.json` - Antigravity plugin's account storage
|
|
182
197
|
|
|
183
198
|
**API Endpoints (all official):**
|
package/README.zh-CN.md
CHANGED
|
@@ -14,10 +14,11 @@
|
|
|
14
14
|
|
|
15
15
|
| 平台 | 账号类型 | 数据来源 |
|
|
16
16
|
| ------------ | ----------------- | ---------------------------------------------- |
|
|
17
|
-
| OpenAI | Plus / Team / Pro | `~/.local/share/opencode/
|
|
18
|
-
| 智谱 AI | Coding Plan | `~/.local/share/opencode/
|
|
19
|
-
| Z.ai | Coding Plan | `~/.local/share/opencode/
|
|
20
|
-
|
|
|
17
|
+
| OpenAI | Plus / Team / Pro | `~/.local/share/opencode/opencode.db` |
|
|
18
|
+
| 智谱 AI | Coding Plan | `~/.local/share/opencode/opencode.db` |
|
|
19
|
+
| Z.ai | Coding Plan | `~/.local/share/opencode/opencode.db` |
|
|
20
|
+
| DeepSeek | API 余额 | `~/.local/share/opencode/opencode.db` |
|
|
21
|
+
| GitHub Copilot | Individual / Business | `~/.local/share/opencode/opencode.db` |
|
|
21
22
|
| Google Cloud | Antigravity | `~/.config/opencode/antigravity-accounts.json` |
|
|
22
23
|
|
|
23
24
|
## 安装
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
将以下内容粘贴到任意 AI 代理(Claude Code、OpenCode、Cursor 等):
|
|
28
29
|
|
|
29
30
|
```
|
|
30
|
-
Install the opencode-mystatus plugin by following: https://raw.githubusercontent.com/
|
|
31
|
+
Install the opencode-mystatus plugin by following: https://raw.githubusercontent.com/ekworp/opencode-mystatus/main/README.md
|
|
31
32
|
```
|
|
32
33
|
|
|
33
34
|
<details>
|
|
@@ -161,11 +162,25 @@ Claude 2d 9h ░░░░░░░░░░░░░░░░░░░
|
|
|
161
162
|
|
|
162
163
|
## 配置
|
|
163
164
|
|
|
164
|
-
|
|
165
|
+
插件自动从以下位置读取认证信息:
|
|
165
166
|
|
|
166
|
-
- **OpenAI、智谱 AI、Z.ai 和 GitHub Copilot**: `~/.local/share/opencode/
|
|
167
|
+
- **OpenAI、智谱 AI、Z.ai、DeepSeek 和 GitHub Copilot**: `~/.local/share/opencode/opencode.db`
|
|
167
168
|
- **Google Cloud**: `~/.config/opencode/antigravity-accounts.json`
|
|
168
169
|
|
|
170
|
+
### 禁用平台
|
|
171
|
+
|
|
172
|
+
可创建 `~/.config/opencode/mystatus.json`,过滤不需要查询的平台:
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
{
|
|
176
|
+
"disabledProviders": ["deepseek", "google"],
|
|
177
|
+
"useOpenCodeProxy": true
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
可用名称:`openai`、`zhipuai-coding-plan`、`zai-coding-plan`、`deepseek`、`google`、`github-copilot`。
|
|
182
|
+
`useOpenCodeProxy` 默认为 `true`,会使用 OpenCode 服务配置的代理环境变量。
|
|
183
|
+
|
|
169
184
|
### Google Cloud 设置
|
|
170
185
|
|
|
171
186
|
如需查询 Google Cloud (Antigravity) 账号额度,需要先安装 [opencode-antigravity-auth](https://github.com/NoeFabris/opencode-antigravity-auth) 插件来完成 Google 账号认证。
|
|
@@ -176,7 +191,7 @@ Claude 2d 9h ░░░░░░░░░░░░░░░░░░░
|
|
|
176
191
|
|
|
177
192
|
**读取的本地文件(只读):**
|
|
178
193
|
|
|
179
|
-
- `~/.local/share/opencode/
|
|
194
|
+
- `~/.local/share/opencode/opencode.db` - OpenCode V2 官方认证存储
|
|
180
195
|
- `~/.config/opencode/antigravity-accounts.json` - Antigravity 插件的账号存储
|
|
181
196
|
|
|
182
197
|
**请求的 API 接口(均为官方接口):**
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type ProviderId = "openai" | "zhipuai-coding-plan" | "zai-coding-plan" | "deepseek" | "google" | "github-copilot";
|
|
2
|
+
export interface MystatusConfig {
|
|
3
|
+
disabledProviders: Set<ProviderId>;
|
|
4
|
+
useOpenCodeProxy: boolean;
|
|
5
|
+
}
|
|
6
|
+
declare const CONFIG_PATH: string;
|
|
7
|
+
/** Read optional provider filters. Invalid or missing config means enabled all. */
|
|
8
|
+
export declare function readMystatusConfig(): Promise<MystatusConfig>;
|
|
9
|
+
export declare function isProviderEnabled(disabledProviders: Set<ProviderId>, provider: ProviderId): boolean;
|
|
10
|
+
export { CONFIG_PATH };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const CONFIG_PATH = join(homedir(), ".config/opencode/mystatus.json");
|
|
5
|
+
/** Read optional provider filters. Invalid or missing config means enabled all. */
|
|
6
|
+
export async function readMystatusConfig() {
|
|
7
|
+
try {
|
|
8
|
+
const content = await readFile(CONFIG_PATH, "utf8");
|
|
9
|
+
const config = JSON.parse(content);
|
|
10
|
+
if (!Array.isArray(config.disabledProviders)) {
|
|
11
|
+
return {
|
|
12
|
+
disabledProviders: new Set(),
|
|
13
|
+
useOpenCodeProxy: config.useOpenCodeProxy !== false,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const validProviders = [
|
|
17
|
+
"openai",
|
|
18
|
+
"zhipuai-coding-plan",
|
|
19
|
+
"zai-coding-plan",
|
|
20
|
+
"deepseek",
|
|
21
|
+
"google",
|
|
22
|
+
"github-copilot",
|
|
23
|
+
];
|
|
24
|
+
return {
|
|
25
|
+
disabledProviders: new Set(config.disabledProviders.filter((provider) => validProviders.includes(provider))),
|
|
26
|
+
useOpenCodeProxy: config.useOpenCodeProxy !== false,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return { disabledProviders: new Set(), useOpenCodeProxy: true };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function isProviderEnabled(disabledProviders, provider) {
|
|
34
|
+
return !disabledProviders.has(provider);
|
|
35
|
+
}
|
|
36
|
+
export { CONFIG_PATH };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** DeepSeek account balance query module. */
|
|
2
|
+
import { t } from "./i18n";
|
|
3
|
+
import { fetchWithTimeout, maskString } from "./utils";
|
|
4
|
+
const DEEPSEEK_BALANCE_URL = "https://api.deepseek.com/user/balance";
|
|
5
|
+
async function fetchBalance(apiKey) {
|
|
6
|
+
const response = await fetchWithTimeout(DEEPSEEK_BALANCE_URL, {
|
|
7
|
+
headers: {
|
|
8
|
+
Authorization: `Bearer ${apiKey}`,
|
|
9
|
+
"Content-Type": "application/json",
|
|
10
|
+
"User-Agent": "OpenCode-Status-Plugin/1.0",
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
const errorText = await response.text();
|
|
15
|
+
throw new Error(t.deepseekApiError(response.status, errorText));
|
|
16
|
+
}
|
|
17
|
+
return (await response.json());
|
|
18
|
+
}
|
|
19
|
+
function formatBalance(data, apiKey) {
|
|
20
|
+
const lines = [
|
|
21
|
+
`${t.account} ${maskString(apiKey)} (${t.deepseekAccountName})`,
|
|
22
|
+
"",
|
|
23
|
+
];
|
|
24
|
+
if (!data.is_available || !data.balance_infos?.length) {
|
|
25
|
+
lines.push(t.deepseekNoBalance);
|
|
26
|
+
return lines.join("\n");
|
|
27
|
+
}
|
|
28
|
+
for (const balance of data.balance_infos) {
|
|
29
|
+
lines.push(`${t.deepseekBalance}: ${balance.total_balance} ${balance.currency}`);
|
|
30
|
+
}
|
|
31
|
+
return lines.join("\n");
|
|
32
|
+
}
|
|
33
|
+
export async function queryDeepSeekUsage(authData) {
|
|
34
|
+
// OpenCode stores API-key credentials with type "key" in V2.
|
|
35
|
+
// Keep accepting the older "api" shape for compatibility.
|
|
36
|
+
if (!authData?.key || !["key", "api"].includes(authData.type))
|
|
37
|
+
return null;
|
|
38
|
+
try {
|
|
39
|
+
const balance = await fetchBalance(authData.key);
|
|
40
|
+
return { success: true, output: formatBalance(balance, authData.key) };
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
return {
|
|
44
|
+
success: false,
|
|
45
|
+
error: err instanceof Error ? err.message : String(err),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -102,7 +102,7 @@ async function refreshAccessToken(refreshToken) {
|
|
|
102
102
|
refresh_token: refreshToken,
|
|
103
103
|
grant_type: "refresh_token",
|
|
104
104
|
});
|
|
105
|
-
const response = await
|
|
105
|
+
const response = await fetchWithTimeout(GOOGLE_TOKEN_REFRESH_URL, {
|
|
106
106
|
method: "POST",
|
|
107
107
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
108
108
|
body: params,
|
|
@@ -26,17 +26,22 @@ export declare const t: {
|
|
|
26
26
|
readonly apiError: (status: number, text: string) => string;
|
|
27
27
|
readonly timeoutError: (seconds: number) => string;
|
|
28
28
|
readonly tokenExpired: "⚠️ OAuth 授权已过期,请在 OpenCode 中使用一次 OpenAI 模型以刷新授权。";
|
|
29
|
-
readonly noAccounts: "未找到任何已配置的账号。\n\n支持的账号类型:\n- OpenAI (Plus/Team/Pro 订阅用户)\n- 智谱 AI (Coding Plan)\n- Z.ai (Coding Plan)\n- Google Cloud (Antigravity)";
|
|
29
|
+
readonly noAccounts: "未找到任何已配置的账号。\n\n支持的账号类型:\n- OpenAI (Plus/Team/Pro 订阅用户)\n- 智谱 AI (Coding Plan)\n- Z.ai (Coding Plan)\n- DeepSeek\n- Google Cloud (Antigravity)";
|
|
30
30
|
readonly queryFailed: "❌ 查询失败的账号:\n";
|
|
31
31
|
readonly openaiTitle: "## OpenAI 账号额度";
|
|
32
32
|
readonly zhipuTitle: "## 智谱 AI 账号额度";
|
|
33
33
|
readonly zaiTitle: "## Z.ai 账号额度";
|
|
34
|
+
readonly deepseekTitle: "## DeepSeek 账户余额";
|
|
34
35
|
readonly zhipuApiError: (status: number, text: string) => string;
|
|
35
36
|
readonly zaiApiError: (status: number, text: string) => string;
|
|
36
37
|
readonly zhipuTokensLimit: "5 小时 Token 限额";
|
|
37
38
|
readonly zhipuMcpLimit: "MCP 月度配额";
|
|
38
39
|
readonly zhipuAccountName: "Coding Plan";
|
|
39
40
|
readonly zaiAccountName: "Z.ai";
|
|
41
|
+
readonly deepseekApiError: (status: number, text: string) => string;
|
|
42
|
+
readonly deepseekAccountName: "API 账户";
|
|
43
|
+
readonly deepseekBalance: "账户余额";
|
|
44
|
+
readonly deepseekNoBalance: "暂无余额数据";
|
|
40
45
|
readonly noQuotaData: "暂无配额数据";
|
|
41
46
|
readonly googleTitle: "## Google Cloud 账号额度";
|
|
42
47
|
readonly googleApiError: (status: number, text: string) => string;
|
|
@@ -70,17 +75,22 @@ export declare const t: {
|
|
|
70
75
|
readonly apiError: (status: number, text: string) => string;
|
|
71
76
|
readonly timeoutError: (seconds: number) => string;
|
|
72
77
|
readonly tokenExpired: "⚠️ OAuth token expired. Please use an OpenAI model in OpenCode to refresh authorization.";
|
|
73
|
-
readonly noAccounts: "No configured accounts found.\n\nSupported account types:\n- OpenAI (Plus/Team/Pro subscribers)\n- Zhipu AI (Coding Plan)\n- Z.ai (Coding Plan)\n- Google Cloud (Antigravity)";
|
|
78
|
+
readonly noAccounts: "No configured accounts found.\n\nSupported account types:\n- OpenAI (Plus/Team/Pro subscribers)\n- Zhipu AI (Coding Plan)\n- Z.ai (Coding Plan)\n- DeepSeek\n- Google Cloud (Antigravity)";
|
|
74
79
|
readonly queryFailed: "❌ Failed to query accounts:\n";
|
|
75
80
|
readonly openaiTitle: "## OpenAI Account Quota";
|
|
76
81
|
readonly zhipuTitle: "## Zhipu AI Account Quota";
|
|
77
82
|
readonly zaiTitle: "## Z.ai Account Quota";
|
|
83
|
+
readonly deepseekTitle: "## DeepSeek Account Balance";
|
|
78
84
|
readonly zhipuApiError: (status: number, text: string) => string;
|
|
79
85
|
readonly zaiApiError: (status: number, text: string) => string;
|
|
80
86
|
readonly zhipuTokensLimit: "5-hour token limit";
|
|
81
87
|
readonly zhipuMcpLimit: "MCP monthly quota";
|
|
82
88
|
readonly zhipuAccountName: "Coding Plan";
|
|
83
89
|
readonly zaiAccountName: "Z.ai";
|
|
90
|
+
readonly deepseekApiError: (status: number, text: string) => string;
|
|
91
|
+
readonly deepseekAccountName: "API account";
|
|
92
|
+
readonly deepseekBalance: "Account balance";
|
|
93
|
+
readonly deepseekNoBalance: "No balance data available";
|
|
84
94
|
readonly noQuotaData: "No quota data available";
|
|
85
95
|
readonly googleTitle: "## Google Cloud Account Quota";
|
|
86
96
|
readonly googleApiError: (status: number, text: string) => string;
|
|
@@ -99,4 +109,3 @@ export declare const t: {
|
|
|
99
109
|
readonly copilotQuotaUnavailable: string;
|
|
100
110
|
readonly copilotQuotaWorkaround: string;
|
|
101
111
|
};
|
|
102
|
-
//# sourceMappingURL=i18n.d.ts.map
|
package/dist/plugin/lib/i18n.js
CHANGED
|
@@ -50,16 +50,17 @@ const translations = {
|
|
|
50
50
|
unknown: "未知",
|
|
51
51
|
used: "已用",
|
|
52
52
|
// 错误信息
|
|
53
|
-
authError: (path, err) => `❌
|
|
53
|
+
authError: (path, err) => `❌ 无法读取 OpenCode 凭证数据库: ${path}\n错误: ${err}`,
|
|
54
54
|
apiError: (status, text) => `OpenAI API 请求失败 (${status}): ${text}`,
|
|
55
55
|
timeoutError: (seconds) => `请求超时 (${seconds}秒)`,
|
|
56
56
|
tokenExpired: "⚠️ OAuth 授权已过期,请在 OpenCode 中使用一次 OpenAI 模型以刷新授权。",
|
|
57
|
-
noAccounts: "未找到任何已配置的账号。\n\n支持的账号类型:\n- OpenAI (Plus/Team/Pro 订阅用户)\n- 智谱 AI (Coding Plan)\n- Z.ai (Coding Plan)\n- Google Cloud (Antigravity)",
|
|
57
|
+
noAccounts: "未找到任何已配置的账号。\n\n支持的账号类型:\n- OpenAI (Plus/Team/Pro 订阅用户)\n- 智谱 AI (Coding Plan)\n- Z.ai (Coding Plan)\n- DeepSeek\n- Google Cloud (Antigravity)",
|
|
58
58
|
queryFailed: "❌ 查询失败的账号:\n",
|
|
59
59
|
// 平台标题
|
|
60
60
|
openaiTitle: "## OpenAI 账号额度",
|
|
61
61
|
zhipuTitle: "## 智谱 AI 账号额度",
|
|
62
62
|
zaiTitle: "## Z.ai 账号额度",
|
|
63
|
+
deepseekTitle: "## DeepSeek 账户余额",
|
|
63
64
|
// 智谱 AI 相关
|
|
64
65
|
zhipuApiError: (status, text) => `智谱 API 请求失败 (${status}): ${text}`,
|
|
65
66
|
zaiApiError: (status, text) => `Z.ai API 请求失败 (${status}): ${text}`,
|
|
@@ -67,6 +68,10 @@ const translations = {
|
|
|
67
68
|
zhipuMcpLimit: "MCP 月度配额",
|
|
68
69
|
zhipuAccountName: "Coding Plan",
|
|
69
70
|
zaiAccountName: "Z.ai",
|
|
71
|
+
deepseekApiError: (status, text) => `DeepSeek API 请求失败 (${status}): ${text}`,
|
|
72
|
+
deepseekAccountName: "API 账户",
|
|
73
|
+
deepseekBalance: "账户余额",
|
|
74
|
+
deepseekNoBalance: "暂无余额数据",
|
|
70
75
|
noQuotaData: "暂无配额数据",
|
|
71
76
|
// Google 相关
|
|
72
77
|
googleTitle: "## Google Cloud 账号额度",
|
|
@@ -111,16 +116,17 @@ const translations = {
|
|
|
111
116
|
unknown: "unknown",
|
|
112
117
|
used: "Used",
|
|
113
118
|
// 错误信息
|
|
114
|
-
authError: (path, err) => `❌ Failed to read
|
|
119
|
+
authError: (path, err) => `❌ Failed to read OpenCode credential database: ${path}\nError: ${err}`,
|
|
115
120
|
apiError: (status, text) => `OpenAI API request failed (${status}): ${text}`,
|
|
116
121
|
timeoutError: (seconds) => `Request timeout (${seconds}s)`,
|
|
117
122
|
tokenExpired: "⚠️ OAuth token expired. Please use an OpenAI model in OpenCode to refresh authorization.",
|
|
118
|
-
noAccounts: "No configured accounts found.\n\nSupported account types:\n- OpenAI (Plus/Team/Pro subscribers)\n- Zhipu AI (Coding Plan)\n- Z.ai (Coding Plan)\n- Google Cloud (Antigravity)",
|
|
123
|
+
noAccounts: "No configured accounts found.\n\nSupported account types:\n- OpenAI (Plus/Team/Pro subscribers)\n- Zhipu AI (Coding Plan)\n- Z.ai (Coding Plan)\n- DeepSeek\n- Google Cloud (Antigravity)",
|
|
119
124
|
queryFailed: "❌ Failed to query accounts:\n",
|
|
120
125
|
// 平台标题
|
|
121
126
|
openaiTitle: "## OpenAI Account Quota",
|
|
122
127
|
zhipuTitle: "## Zhipu AI Account Quota",
|
|
123
128
|
zaiTitle: "## Z.ai Account Quota",
|
|
129
|
+
deepseekTitle: "## DeepSeek Account Balance",
|
|
124
130
|
// 智谱 AI 相关
|
|
125
131
|
zhipuApiError: (status, text) => `Zhipu API request failed (${status}): ${text}`,
|
|
126
132
|
zaiApiError: (status, text) => `Z.ai API request failed (${status}): ${text}`,
|
|
@@ -128,6 +134,10 @@ const translations = {
|
|
|
128
134
|
zhipuMcpLimit: "MCP monthly quota",
|
|
129
135
|
zhipuAccountName: "Coding Plan",
|
|
130
136
|
zaiAccountName: "Z.ai",
|
|
137
|
+
deepseekApiError: (status, text) => `DeepSeek API request failed (${status}): ${text}`,
|
|
138
|
+
deepseekAccountName: "API account",
|
|
139
|
+
deepseekBalance: "Account balance",
|
|
140
|
+
deepseekNoBalance: "No balance data available",
|
|
131
141
|
noQuotaData: "No quota data available",
|
|
132
142
|
// Google 相关
|
|
133
143
|
googleTitle: "## Google Cloud Account Quota",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read credentials from OpenCode V2's SQLite database.
|
|
3
|
+
*
|
|
4
|
+
* The sqlite3 CLI is used instead of adding a native Node SQLite dependency.
|
|
5
|
+
*/
|
|
6
|
+
import type { ProviderId } from "./config";
|
|
7
|
+
import type { AuthData } from "./types";
|
|
8
|
+
/** Read active V2 credentials without modifying the database. */
|
|
9
|
+
export declare function readV2AuthData(databasePath?: string, disabledProviders?: Set<ProviderId>): Promise<AuthData>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read credentials from OpenCode V2's SQLite database.
|
|
3
|
+
*
|
|
4
|
+
* The sqlite3 CLI is used instead of adding a native Node SQLite dependency.
|
|
5
|
+
*/
|
|
6
|
+
import { execFile } from "node:child_process";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { promisify } from "node:util";
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
const DATABASE_PATH = `${homedir()}/.local/share/opencode/opencode.db`;
|
|
11
|
+
/** Read active V2 credentials without modifying the database. */
|
|
12
|
+
export async function readV2AuthData(databasePath = DATABASE_PATH, disabledProviders = new Set()) {
|
|
13
|
+
const providerIds = [
|
|
14
|
+
"openai",
|
|
15
|
+
"zhipuai-coding-plan",
|
|
16
|
+
"zai-coding-plan",
|
|
17
|
+
"github-copilot",
|
|
18
|
+
"deepseek",
|
|
19
|
+
].filter((provider) => !disabledProviders.has(provider));
|
|
20
|
+
if (providerIds.length === 0)
|
|
21
|
+
return {};
|
|
22
|
+
const sql = `SELECT integration_id, value FROM credential WHERE (active IS NULL OR active != 0) AND integration_id IN (${providerIds.map((provider) => `'${provider}'`).join(", ")}) ORDER BY time_updated DESC;`;
|
|
23
|
+
try {
|
|
24
|
+
const { stdout } = await execFileAsync("sqlite3", ["-json", `file:${databasePath}?mode=ro`, sql], { maxBuffer: 1024 * 1024, windowsHide: true });
|
|
25
|
+
const rows = JSON.parse(stdout);
|
|
26
|
+
const authData = {};
|
|
27
|
+
for (const row of rows) {
|
|
28
|
+
if (!row.integration_id || typeof row.value !== "string")
|
|
29
|
+
continue;
|
|
30
|
+
try {
|
|
31
|
+
const value = JSON.parse(row.value);
|
|
32
|
+
if (value &&
|
|
33
|
+
typeof value === "object" &&
|
|
34
|
+
!(row.integration_id in authData)) {
|
|
35
|
+
// Integration IDs are the same provider IDs used by auth.json.
|
|
36
|
+
authData[row.integration_id] = value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Ignore malformed rows and continue reading other providers.
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return authData;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -28,6 +28,11 @@ export interface ZhipuAuthData {
|
|
|
28
28
|
type: string;
|
|
29
29
|
key?: string;
|
|
30
30
|
}
|
|
31
|
+
/** DeepSeek API key authentication data */
|
|
32
|
+
export interface DeepSeekAuthData {
|
|
33
|
+
type: string;
|
|
34
|
+
key?: string;
|
|
35
|
+
}
|
|
31
36
|
/**
|
|
32
37
|
* GitHub Copilot 认证数据
|
|
33
38
|
*/
|
|
@@ -81,6 +86,7 @@ export interface AntigravityAccountsFile {
|
|
|
81
86
|
*/
|
|
82
87
|
export interface AuthData {
|
|
83
88
|
openai?: OpenAIAuthData;
|
|
89
|
+
deepseek?: DeepSeekAuthData;
|
|
84
90
|
"zhipuai-coding-plan"?: ZhipuAuthData;
|
|
85
91
|
"zai-coding-plan"?: ZhipuAuthData;
|
|
86
92
|
"github-copilot"?: CopilotAuthData;
|
|
@@ -89,4 +95,3 @@ export interface AuthData {
|
|
|
89
95
|
export declare const HIGH_USAGE_THRESHOLD = 80;
|
|
90
96
|
/** API 请求超时时间(毫秒) */
|
|
91
97
|
export declare const REQUEST_TIMEOUT_MS = 10000;
|
|
92
|
-
//# sourceMappingURL=types.d.ts.map
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* [定位]: 被所有平台模块共享使用的工具函数
|
|
5
5
|
* [同步]: openai.ts, zhipu.ts, google.ts
|
|
6
6
|
*/
|
|
7
|
+
/** Use the proxy environment inherited from the OpenCode service by default. */
|
|
8
|
+
export declare function configureOpenCodeProxy(enabled: boolean): void;
|
|
7
9
|
/**
|
|
8
10
|
* 将秒数转换为人类可读的时间格式
|
|
9
11
|
*/
|
|
@@ -41,4 +43,3 @@ export declare function safeMax(arr: number[]): number;
|
|
|
41
43
|
* @param showChars 前后各显示的字符数,默认 4
|
|
42
44
|
*/
|
|
43
45
|
export declare function maskString(str: string, showChars?: number): string;
|
|
44
|
-
//# sourceMappingURL=utils.d.ts.map
|
package/dist/plugin/lib/utils.js
CHANGED
|
@@ -4,8 +4,14 @@
|
|
|
4
4
|
* [定位]: 被所有平台模块共享使用的工具函数
|
|
5
5
|
* [同步]: openai.ts, zhipu.ts, google.ts
|
|
6
6
|
*/
|
|
7
|
+
import { Agent, EnvHttpProxyAgent } from "undici";
|
|
7
8
|
import { currentLang, t } from "./i18n";
|
|
8
9
|
import { REQUEST_TIMEOUT_MS } from "./types";
|
|
10
|
+
let httpDispatcher = new EnvHttpProxyAgent();
|
|
11
|
+
/** Use the proxy environment inherited from the OpenCode service by default. */
|
|
12
|
+
export function configureOpenCodeProxy(enabled) {
|
|
13
|
+
httpDispatcher = enabled ? new EnvHttpProxyAgent() : new Agent();
|
|
14
|
+
}
|
|
9
15
|
// ============================================================================
|
|
10
16
|
// 时间格式化
|
|
11
17
|
// ============================================================================
|
|
@@ -74,6 +80,7 @@ export async function fetchWithTimeout(url, options, timeoutMs = REQUEST_TIMEOUT
|
|
|
74
80
|
const response = await fetch(url, {
|
|
75
81
|
...options,
|
|
76
82
|
signal: controller.signal,
|
|
83
|
+
dispatcher: httpDispatcher,
|
|
77
84
|
});
|
|
78
85
|
return response;
|
|
79
86
|
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenCode 额度状态查询插件
|
|
3
3
|
*
|
|
4
|
-
* [输入]:
|
|
4
|
+
* [输入]: OpenCode V2 SQLite 凭证和 ~/.config/opencode/antigravity-accounts.json 中的认证信息
|
|
5
5
|
* [输出]: 带进度条的额度使用情况展示
|
|
6
6
|
* [定位]: 通过 mystatus 工具查询各账号额度
|
|
7
|
-
* [同步]: lib/openai.ts, lib/zhipu.ts, lib/google.ts, lib/types.ts, lib/i18n.ts
|
|
7
|
+
* [同步]: lib/openai.ts, lib/zhipu.ts, lib/deepseek.ts, lib/google.ts, lib/types.ts, lib/i18n.ts
|
|
8
8
|
*/
|
|
9
9
|
import { Plugin } from "@opencode/plugin";
|
|
10
10
|
declare const _default: Plugin.Plugin;
|
|
11
11
|
export default _default;
|
|
12
|
-
//# sourceMappingURL=mystatus.d.ts.map
|
package/dist/plugin/mystatus.js
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenCode 额度状态查询插件
|
|
3
3
|
*
|
|
4
|
-
* [输入]:
|
|
4
|
+
* [输入]: OpenCode V2 SQLite 凭证和 ~/.config/opencode/antigravity-accounts.json 中的认证信息
|
|
5
5
|
* [输出]: 带进度条的额度使用情况展示
|
|
6
6
|
* [定位]: 通过 mystatus 工具查询各账号额度
|
|
7
|
-
* [同步]: lib/openai.ts, lib/zhipu.ts, lib/google.ts, lib/types.ts, lib/i18n.ts
|
|
7
|
+
* [同步]: lib/openai.ts, lib/zhipu.ts, lib/deepseek.ts, lib/google.ts, lib/types.ts, lib/i18n.ts
|
|
8
8
|
*/
|
|
9
|
-
import { readFile } from "node:fs/promises";
|
|
10
9
|
import { homedir } from "node:os";
|
|
11
10
|
import { join } from "node:path";
|
|
12
11
|
import { Plugin } from "@opencode/plugin";
|
|
12
|
+
import { isProviderEnabled, readMystatusConfig, } from "./lib/config";
|
|
13
13
|
import { queryCopilotUsage } from "./lib/copilot";
|
|
14
|
+
import { queryDeepSeekUsage } from "./lib/deepseek";
|
|
14
15
|
import { queryGoogleUsage } from "./lib/google";
|
|
15
16
|
import { t } from "./lib/i18n";
|
|
16
17
|
import { queryOpenAIUsage } from "./lib/openai";
|
|
18
|
+
import { readV2AuthData } from "./lib/opencode-auth";
|
|
19
|
+
import { configureOpenCodeProxy } from "./lib/utils";
|
|
17
20
|
import { queryZaiUsage, queryZhipuUsage } from "./lib/zhipu";
|
|
18
21
|
// ============================================================================
|
|
19
22
|
// V2 插件导出
|
|
@@ -24,32 +27,56 @@ export default Plugin.define({
|
|
|
24
27
|
await ctx.tool.transform((editor) => {
|
|
25
28
|
editor.add({
|
|
26
29
|
name: "mystatus",
|
|
27
|
-
description: "Query account quota usage for all configured AI platforms. Returns remaining quota percentages, usage stats, and reset countdowns
|
|
30
|
+
description: "Query account quota usage for all configured AI platforms. Returns remaining quota percentages, usage stats, balances, and reset countdowns. Currently supports OpenAI (ChatGPT/Codex), Zhipu AI, Z.ai, DeepSeek, Google Antigravity, and GitHub Copilot.",
|
|
28
31
|
input: {
|
|
29
32
|
type: "object",
|
|
30
33
|
properties: {},
|
|
31
34
|
additionalProperties: false,
|
|
32
35
|
},
|
|
33
36
|
async execute() {
|
|
34
|
-
//
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
// V2 stores credentials and refreshed OAuth tokens in SQLite.
|
|
38
|
+
const databasePath = join(homedir(), ".local/share/opencode/opencode.db");
|
|
39
|
+
const config = await readMystatusConfig();
|
|
40
|
+
const { disabledProviders } = config;
|
|
41
|
+
configureOpenCodeProxy(config.useOpenCodeProxy);
|
|
42
|
+
const authData = await readV2AuthData(databasePath, disabledProviders);
|
|
43
|
+
const providers = [
|
|
44
|
+
"openai",
|
|
45
|
+
"zhipuai-coding-plan",
|
|
46
|
+
"zai-coding-plan",
|
|
47
|
+
"deepseek",
|
|
48
|
+
"google",
|
|
49
|
+
"github-copilot",
|
|
50
|
+
];
|
|
51
|
+
const hasEnabledProvider = providers.some((provider) => isProviderEnabled(disabledProviders, provider));
|
|
52
|
+
if (!hasEnabledProvider)
|
|
53
|
+
return { content: t.noAccounts };
|
|
54
|
+
if (Object.keys(authData).length === 0 &&
|
|
55
|
+
!isProviderEnabled(disabledProviders, "google")) {
|
|
42
56
|
return {
|
|
43
|
-
content: t.authError(
|
|
57
|
+
content: t.authError(databasePath, "OpenCode V2 credentials were not found in the credential table"),
|
|
44
58
|
};
|
|
45
59
|
}
|
|
46
60
|
// 2. 并行查询所有平台(Google 不依赖 authData)
|
|
47
|
-
const [openaiResult, zhipuResult, zaiResult, googleResult, copilotResult,] = await Promise.all([
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
61
|
+
const [openaiResult, zhipuResult, zaiResult, deepseekResult, googleResult, copilotResult,] = await Promise.all([
|
|
62
|
+
isProviderEnabled(disabledProviders, "openai")
|
|
63
|
+
? retryQuery(() => queryOpenAIUsage(authData.openai))
|
|
64
|
+
: null,
|
|
65
|
+
isProviderEnabled(disabledProviders, "zhipuai-coding-plan")
|
|
66
|
+
? retryQuery(() => queryZhipuUsage(authData["zhipuai-coding-plan"]))
|
|
67
|
+
: null,
|
|
68
|
+
isProviderEnabled(disabledProviders, "zai-coding-plan")
|
|
69
|
+
? retryQuery(() => queryZaiUsage(authData["zai-coding-plan"]))
|
|
70
|
+
: null,
|
|
71
|
+
isProviderEnabled(disabledProviders, "deepseek")
|
|
72
|
+
? retryQuery(() => queryDeepSeekUsage(authData.deepseek))
|
|
73
|
+
: null,
|
|
74
|
+
isProviderEnabled(disabledProviders, "google")
|
|
75
|
+
? retryQuery(() => queryGoogleUsage())
|
|
76
|
+
: null,
|
|
77
|
+
isProviderEnabled(disabledProviders, "github-copilot")
|
|
78
|
+
? retryQuery(() => queryCopilotUsage(authData["github-copilot"]))
|
|
79
|
+
: null,
|
|
53
80
|
]);
|
|
54
81
|
// 3. 收集结果
|
|
55
82
|
const results = [];
|
|
@@ -60,6 +87,8 @@ export default Plugin.define({
|
|
|
60
87
|
collectResult(zhipuResult, t.zhipuTitle, results, errors);
|
|
61
88
|
// 处理 Z.ai 结果
|
|
62
89
|
collectResult(zaiResult, t.zaiTitle, results, errors);
|
|
90
|
+
// 处理 DeepSeek 结果
|
|
91
|
+
collectResult(deepseekResult, t.deepseekTitle, results, errors);
|
|
63
92
|
// 处理 Google 结果
|
|
64
93
|
collectResult(googleResult, t.googleTitle, results, errors);
|
|
65
94
|
// 处理 GitHub Copilot 结果
|
|
@@ -98,3 +127,25 @@ function collectResult(result, title, results, errors) {
|
|
|
98
127
|
errors.push(result.error);
|
|
99
128
|
}
|
|
100
129
|
}
|
|
130
|
+
/** Retry transient provider failures before displaying an error. */
|
|
131
|
+
async function retryQuery(query) {
|
|
132
|
+
const maxAttempts = 3;
|
|
133
|
+
let result = null;
|
|
134
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
135
|
+
try {
|
|
136
|
+
result = await query();
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
result = {
|
|
140
|
+
success: false,
|
|
141
|
+
error: err instanceof Error ? err.message : String(err),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
// null means the provider is not configured; it should not be retried.
|
|
145
|
+
if (!result || result.success || attempt === maxAttempts)
|
|
146
|
+
return result;
|
|
147
|
+
// 500ms, then 1s. Avoid hammering a temporarily unavailable API.
|
|
148
|
+
await new Promise((resolve) => setTimeout(resolve, attempt * 500));
|
|
149
|
+
}
|
|
150
|
+
return result;
|
|
151
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekworp/opencode-mystatus",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.10",
|
|
4
4
|
"description": "Check all your AI subscription quotas in one command. Supports OpenAI, Zhipu AI, Z.ai, and Google Antigravity. More platforms coming soon.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/plugin/mystatus.js",
|
|
@@ -17,6 +17,17 @@
|
|
|
17
17
|
"README.md",
|
|
18
18
|
"LICENSE"
|
|
19
19
|
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"test": "pnpm build && node --loader ./tests/extension-loader.mjs --test tests/*.test.js",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"lint": "biome check .",
|
|
25
|
+
"lint:fix": "biome check --write .",
|
|
26
|
+
"format": "biome format --write .",
|
|
27
|
+
"prepublishOnly": "pnpm run lint && pnpm run build",
|
|
28
|
+
"publish:npm": "npm publish --access public --registry=https://registry.npmjs.org",
|
|
29
|
+
"prepare": "husky"
|
|
30
|
+
},
|
|
20
31
|
"keywords": [
|
|
21
32
|
"opencode",
|
|
22
33
|
"plugin",
|
|
@@ -34,14 +45,15 @@
|
|
|
34
45
|
"license": "MIT",
|
|
35
46
|
"repository": {
|
|
36
47
|
"type": "git",
|
|
37
|
-
"url": "git+https://github.com/
|
|
48
|
+
"url": "git+https://github.com/ekworp/opencode-mystatus.git"
|
|
38
49
|
},
|
|
39
50
|
"bugs": {
|
|
40
|
-
"url": "https://github.com/
|
|
51
|
+
"url": "https://github.com/ekworp/opencode-mystatus/issues"
|
|
41
52
|
},
|
|
42
|
-
"homepage": "https://github.com/
|
|
53
|
+
"homepage": "https://github.com/ekworp/opencode-mystatus#readme",
|
|
43
54
|
"dependencies": {
|
|
44
|
-
"@opencode/plugin": "2.0.25"
|
|
55
|
+
"@opencode/plugin": "2.0.25",
|
|
56
|
+
"undici": "^6.29.0"
|
|
45
57
|
},
|
|
46
58
|
"devDependencies": {
|
|
47
59
|
"@biomejs/biome": "^2.5.15",
|
|
@@ -54,12 +66,5 @@
|
|
|
54
66
|
},
|
|
55
67
|
"engines": {
|
|
56
68
|
"node": ">=18.0.0"
|
|
57
|
-
},
|
|
58
|
-
"scripts": {
|
|
59
|
-
"build": "tsc",
|
|
60
|
-
"typecheck": "tsc --noEmit",
|
|
61
|
-
"lint": "biome check .",
|
|
62
|
-
"lint:fix": "biome check --write .",
|
|
63
|
-
"format": "biome format --write ."
|
|
64
69
|
}
|
|
65
|
-
}
|
|
70
|
+
}
|