@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 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/auth.json` |
18
- | Zhipu AI | Coding Plan | `~/.local/share/opencode/auth.json` |
19
- | Z.ai | Coding Plan | `~/.local/share/opencode/auth.json` |
20
- | GitHub Copilot | Individual / Business | `~/.local/share/opencode/auth.json` |
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/vbgate/opencode-mystatus/main/README.md
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/auth.json`
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/auth.json` - OpenCode's official auth storage
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/auth.json` |
18
- | 智谱 AI | Coding Plan | `~/.local/share/opencode/auth.json` |
19
- | Z.ai | Coding Plan | `~/.local/share/opencode/auth.json` |
20
- | GitHub Copilot | Individual / Business | `~/.local/share/opencode/auth.json` |
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/vbgate/opencode-mystatus/main/README.md
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/auth.json`
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/auth.json` - 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 };
@@ -18,4 +18,3 @@ export type { CopilotAuthData };
18
18
  * @returns Query result, null if no account configured
19
19
  */
20
20
  export declare function queryCopilotUsage(authData: CopilotAuthData | undefined): Promise<QueryResult | null>;
21
- //# sourceMappingURL=copilot.d.ts.map
@@ -0,0 +1,3 @@
1
+ /** DeepSeek account balance query module. */
2
+ import type { DeepSeekAuthData, QueryResult } from "./types";
3
+ export declare function queryDeepSeekUsage(authData: DeepSeekAuthData | undefined): Promise<QueryResult | null>;
@@ -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
+ }
@@ -12,4 +12,3 @@ import { type QueryResult } from "./types";
12
12
  * @returns 查询结果
13
13
  */
14
14
  export declare function queryGoogleUsage(): Promise<QueryResult | null>;
15
- //# sourceMappingURL=google.d.ts.map
@@ -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 fetch(GOOGLE_TOKEN_REFRESH_URL, {
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
@@ -50,16 +50,17 @@ const translations = {
50
50
  unknown: "未知",
51
51
  used: "已用",
52
52
  // 错误信息
53
- authError: (path, err) => `❌ 无法读取认证文件: ${path}\n错误: ${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 auth file: ${path}\nError: ${err}`,
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",
@@ -14,4 +14,3 @@ export type { OpenAIAuthData };
14
14
  * @returns 查询结果,如果账号不存在或无效返回 null
15
15
  */
16
16
  export declare function queryOpenAIUsage(authData: OpenAIAuthData | undefined): Promise<QueryResult | null>;
17
- //# sourceMappingURL=openai.d.ts.map
@@ -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
@@ -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
  }
@@ -20,4 +20,3 @@ export declare function queryZhipuUsage(authData: ZhipuAuthData | undefined): Pr
20
20
  * @returns 查询结果,如果账号不存在或无效返回 null
21
21
  */
22
22
  export declare function queryZaiUsage(authData: ZhipuAuthData | undefined): Promise<QueryResult | null>;
23
- //# sourceMappingURL=zhipu.d.ts.map
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * OpenCode 额度状态查询插件
3
3
  *
4
- * [输入]: ~/.local/share/opencode/auth.json 和 ~/.config/opencode/antigravity-accounts.json 中的认证信息
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
@@ -1,19 +1,22 @@
1
1
  /**
2
2
  * OpenCode 额度状态查询插件
3
3
  *
4
- * [输入]: ~/.local/share/opencode/auth.json 和 ~/.config/opencode/antigravity-accounts.json 中的认证信息
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 with visual progress bars. Currently supports OpenAI (ChatGPT/Codex), Zhipu AI, Z.ai, Google Antigravity, and GitHub Copilot.",
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
- // 1. 读取 auth.json
35
- const authPath = join(homedir(), ".local/share/opencode/auth.json");
36
- let authData;
37
- try {
38
- const content = await readFile(authPath, "utf-8");
39
- authData = JSON.parse(content);
40
- }
41
- catch (err) {
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(authPath, err instanceof Error ? err.message : String(err)),
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
- queryOpenAIUsage(authData.openai),
49
- queryZhipuUsage(authData["zhipuai-coding-plan"]),
50
- queryZaiUsage(authData["zai-coding-plan"]),
51
- queryGoogleUsage(),
52
- queryCopilotUsage(authData["github-copilot"]),
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.7",
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/vbgate/opencode-mystatus.git"
48
+ "url": "git+https://github.com/ekworp/opencode-mystatus.git"
38
49
  },
39
50
  "bugs": {
40
- "url": "https://github.com/vbgate/opencode-mystatus/issues"
51
+ "url": "https://github.com/ekworp/opencode-mystatus/issues"
41
52
  },
42
- "homepage": "https://github.com/vbgate/opencode-mystatus#readme",
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
+ }