@songsid/agend 2.1.4-beta.37 → 2.1.4-beta.39
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/dist/backend/codex.js +13 -0
- package/dist/backend/codex.js.map +1 -1
- package/dist/channel/mcp-server.js +11 -3
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/cli.js +14 -14
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +6 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/cross-instance-envelope.d.ts +6 -0
- package/dist/cross-instance-envelope.js +30 -0
- package/dist/cross-instance-envelope.js.map +1 -0
- package/dist/daemon.d.ts +73 -0
- package/dist/daemon.js +229 -54
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.js +33 -0
- package/dist/fleet-manager.js.map +1 -1
- package/dist/locale.js +168 -0
- package/dist/locale.js.map +1 -1
- package/dist/outbound-handlers.js +57 -13
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +2 -1
- package/dist/outbound-schemas.js +2 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/tmux-manager.d.ts +7 -0
- package/dist/tmux-manager.js +62 -14
- package/dist/tmux-manager.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/ui/view.html +124 -15
- package/dist/usage/format-rich.js +24 -23
- package/dist/usage/format-rich.js.map +1 -1
- package/dist/usage/i18n-keys.d.ts +7 -0
- package/dist/usage/i18n-keys.js +34 -0
- package/dist/usage/i18n-keys.js.map +1 -0
- package/dist/usage/i18n.d.ts +5 -0
- package/dist/usage/i18n.js +27 -0
- package/dist/usage/i18n.js.map +1 -0
- package/dist/usage/providers.d.ts +12 -0
- package/dist/usage/providers.js +132 -73
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/usage-api.js +22 -21
- package/dist/usage/usage-api.js.map +1 -1
- package/package.json +1 -1
package/dist/ui/view.html
CHANGED
|
@@ -194,6 +194,50 @@
|
|
|
194
194
|
tooltipUnavailable: "Unavailable", statusRunning: "Running", statusPaused: "Paused",
|
|
195
195
|
statusStopped: "Stopped", statusCrashed: "Crashed", statusUnknown: "Unknown",
|
|
196
196
|
usageResets: "resets in {t}", usageSoon: "resets soon", usageFail: "Could not load usage data.",
|
|
197
|
+
"usage.title": "AI Subscription Usage", "usage.title_plain": "AI subscription usage", "usage.empty_active": "No active backends with usage tracking",
|
|
198
|
+
"usage.not_logged_in": "not logged in", "usage.error_fallback": "error", "usage.no_data": "no data",
|
|
199
|
+
"usage.reset.in": "resets in {0}", "usage.reset.soon": "resets soon",
|
|
200
|
+
"usage.duration.days_hours": "{0}d {1}h", "usage.duration.hours_minutes": "{0}h {1}m", "usage.duration.minutes": "{0}m",
|
|
201
|
+
"usage.metric.session": "Session", "usage.metric.weekly": "Weekly", "usage.metric.monthly": "Monthly",
|
|
202
|
+
"usage.metric.days_window": "{0}-day window", "usage.metric.named": "{0}",
|
|
203
|
+
"usage.metric.named_session": "{0} (session)", "usage.metric.named_weekly": "{0} (weekly)",
|
|
204
|
+
"usage.metric.named_monthly": "{0} (monthly)", "usage.metric.named_days": "{0} ({1}d)",
|
|
205
|
+
"usage.metric.extra_usage": "Extra usage", "usage.metric.rate_limit_resets": "Rate limit resets",
|
|
206
|
+
"usage.metric.credits": "Credits", "usage.metric.weekly_limit": "Weekly limit",
|
|
207
|
+
"usage.metric.pay_as_you_go": "Pay as you go", "usage.metric.bonus_credits": "Bonus credits",
|
|
208
|
+
"usage.metric.overage_charges": "Overage charges", "usage.metric.pay_per_use": "Pay-per-use",
|
|
209
|
+
"usage.metric.usage_monthly": "Usage (monthly)",
|
|
210
|
+
"usage.unit.available": "available", "usage.unit.credits": "credits",
|
|
211
|
+
"usage.value.disabled": "Disabled", "usage.value.cap": "{0} cap",
|
|
212
|
+
"usage.note.binding": "binding", "usage.note.binding_severity": "binding · {0}",
|
|
213
|
+
"usage.note.busiest_model": "busiest of {0} model", "usage.note.busiest_models": "busiest of {0} models",
|
|
214
|
+
"usage.note.used_limit_unit": "{0} / {1} {2}", "usage.note.bonus_codes": "{0} / {1} {2} · {3} codes",
|
|
215
|
+
"usage.metric.agy_claude_others": "Claude & others", "usage.metric.agy_claude_others_session": "Claude & others (session)",
|
|
216
|
+
"usage.metric.agy_claude_others_weekly": "Claude & others (weekly)",
|
|
217
|
+
"usage.hint.statusline_now": "from {0}'s statusline, just now — no API call; plan and per-model limits need a CLI login",
|
|
218
|
+
"usage.hint.statusline_minutes": "from {0}'s statusline, {1}m ago — no API call; plan and per-model limits need a CLI login",
|
|
219
|
+
"usage.hint.claude_api_key": "API-key login has no subscription usage. Use `claude /login` or set CLAUDE_CODE_OAUTH_TOKEN (from `claude setup-token`).",
|
|
220
|
+
"usage.hint.login_claude": "Log in with the Claude Code CLI (`claude`).",
|
|
221
|
+
"usage.hint.login_agy": "Log in with the Antigravity CLI (`agy`).", "usage.hint.no_quota": "No quota information reported for this account.",
|
|
222
|
+
"usage.hint.login_codex": "Log in with the Codex CLI (`codex`).", "usage.hint.codex_no_oauth": "auth.json has no OAuth login (API-key-only auth cannot read usage).",
|
|
223
|
+
"usage.hint.login_grok": "Log in with the Grok CLI (`grok login`).", "usage.hint.grok_no_login": "auth.json has no usable login. Run `grok login`.",
|
|
224
|
+
"usage.hint.login_kiro": "Log in with the Kiro CLI (`kiro-cli`).", "usage.hint.kiro_signed_out": "Signed out — run `kiro-cli` to log in.",
|
|
225
|
+
"usage.hint.token_refreshing": "Token refreshing — try again in a moment.", "usage.hint.subscription_no_credit": "Subscription — no credit usage to report.",
|
|
226
|
+
"usage.hint.no_quota_account": "No quota information available for this account type.",
|
|
227
|
+
"usage.error.unreachable": "Could not reach {0}.", "usage.error.token_rejected": "Token rejected. Run `{0}` once to refresh the login.",
|
|
228
|
+
"usage.error.rate_limited": "Rate limited by {0} — try again later.", "usage.error.http": "Usage request failed (HTTP {0}).",
|
|
229
|
+
"usage.error.billing_http": "Billing request failed (HTTP {0}).", "usage.error.invalid_response": "Invalid response from usage endpoint.",
|
|
230
|
+
"usage.error.invalid_billing_response": "Invalid response from billing endpoint.", "usage.error.invalid_kiro_response": "Invalid response from GetUsageLimits.",
|
|
231
|
+
"usage.error.claude_token_expired": "Access token expired — it refreshes the next time any Claude instance runs.",
|
|
232
|
+
"usage.error.codex_token_expired": "Access token expired. Run `codex` once to refresh it.",
|
|
233
|
+
"usage.error.grok_expired": "Grok session expired. Run `grok login` again.", "usage.error.grok_response_changed": "Grok billing response changed.",
|
|
234
|
+
"usage.error.agy_cap": "Individual cap reached — CLI is rate limited (resets in ~{0}). The per-model pool quotas are separate and may still read 0% used.",
|
|
235
|
+
"usage.error.agy_no_refresh": "Access token expired and no refresh token is stored. Run `agy` once.",
|
|
236
|
+
"usage.error.agy_token_expired": "Access token expired. Run `agy` once to refresh it (or set AGY_OAUTH_CLIENT_ID/SECRET to refresh automatically).",
|
|
237
|
+
"usage.error.agy_login_expired": "Google sign-in expired. Run `agy` once to log in again.", "usage.error.agy_refresh_empty": "Google token refresh returned no token.",
|
|
238
|
+
"usage.error.grok_auth_readonly": "Token expired and auth.json is read-only. Run `grok` once to refresh it.", "usage.error.grok_no_refresh": "Token expired and no refresh token was found. Run `grok login`.",
|
|
239
|
+
"usage.error.grok_refresh_unreachable": "Could not reach auth.x.ai to refresh the token.", "usage.error.grok_invalid_refresh": "Invalid refresh response from auth.x.ai.",
|
|
240
|
+
"usage.error.grok_persist": "Refreshed but could not write auth.json. Run `grok` once instead.",
|
|
197
241
|
usageButton: "AI usage", languageButton: "Language", helpButton: "Help", close: "Close", expandProfile: "Expand profile",
|
|
198
242
|
rosterFailed: "Failed to load roster: {error}\n(Is the fleet running?)", paneFailed: "⚠ Could not load pane for {name} (HTTP {status}).",
|
|
199
243
|
tokenRequired: "web.token required", avatarFailed: "Avatar upload failed: {error}", saved: "Saved", saveFailed: "Save failed: {error}" },
|
|
@@ -212,6 +256,50 @@
|
|
|
212
256
|
tooltipUnavailable: "無資料", statusRunning: "運行中", statusPaused: "已暫停",
|
|
213
257
|
statusStopped: "已停止", statusCrashed: "已崩潰", statusUnknown: "未知",
|
|
214
258
|
usageResets: "{t} 後重置", usageSoon: "即將重置", usageFail: "無法載入用量資料。",
|
|
259
|
+
"usage.title": "AI 訂閱用量", "usage.title_plain": "AI 訂閱用量", "usage.empty_active": "沒有使用中且支援用量追蹤的 Backend",
|
|
260
|
+
"usage.not_logged_in": "未登入", "usage.error_fallback": "錯誤", "usage.no_data": "無資料",
|
|
261
|
+
"usage.reset.in": "{0} 後重置", "usage.reset.soon": "即將重置",
|
|
262
|
+
"usage.duration.days_hours": "{0}d {1}h", "usage.duration.hours_minutes": "{0}h {1}m", "usage.duration.minutes": "{0}m",
|
|
263
|
+
"usage.metric.session": "Session", "usage.metric.weekly": "每週", "usage.metric.monthly": "每月",
|
|
264
|
+
"usage.metric.days_window": "{0} 天視窗", "usage.metric.named": "{0}",
|
|
265
|
+
"usage.metric.named_session": "{0}(Session)", "usage.metric.named_weekly": "{0}(每週)",
|
|
266
|
+
"usage.metric.named_monthly": "{0}(每月)", "usage.metric.named_days": "{0}({1} 天)",
|
|
267
|
+
"usage.metric.extra_usage": "額外用量", "usage.metric.rate_limit_resets": "速率限制重置",
|
|
268
|
+
"usage.metric.credits": "額度", "usage.metric.weekly_limit": "每週上限",
|
|
269
|
+
"usage.metric.pay_as_you_go": "隨用隨付", "usage.metric.bonus_credits": "贈送額度",
|
|
270
|
+
"usage.metric.overage_charges": "超額費用", "usage.metric.pay_per_use": "隨用隨付",
|
|
271
|
+
"usage.metric.usage_monthly": "用量(每月)",
|
|
272
|
+
"usage.unit.available": "可用", "usage.unit.credits": "額度",
|
|
273
|
+
"usage.value.disabled": "已停用", "usage.value.cap": "上限 {0}",
|
|
274
|
+
"usage.note.binding": "綁定", "usage.note.binding_severity": "綁定 · {0}",
|
|
275
|
+
"usage.note.busiest_model": "{0} 個模型中用量最高者", "usage.note.busiest_models": "{0} 個模型中用量最高者",
|
|
276
|
+
"usage.note.used_limit_unit": "{0} / {1} {2}", "usage.note.bonus_codes": "{0} / {1} {2} · {3} 組代碼",
|
|
277
|
+
"usage.metric.agy_claude_others": "Claude 與其他模型", "usage.metric.agy_claude_others_session": "Claude 與其他模型(Session)",
|
|
278
|
+
"usage.metric.agy_claude_others_weekly": "Claude 與其他模型(每週)",
|
|
279
|
+
"usage.hint.statusline_now": "來自 {0} 的 statusline(剛剛);未呼叫 API,方案與各模型上限需要 CLI 登入資料",
|
|
280
|
+
"usage.hint.statusline_minutes": "來自 {0} 的 statusline({1} 分鐘前);未呼叫 API,方案與各模型上限需要 CLI 登入資料",
|
|
281
|
+
"usage.hint.claude_api_key": "API key 登入沒有訂閱用量資料。請使用 `claude /login`,或設定 CLAUDE_CODE_OAUTH_TOKEN(由 `claude setup-token` 取得)。",
|
|
282
|
+
"usage.hint.login_claude": "請使用 Claude Code CLI(`claude`)登入。",
|
|
283
|
+
"usage.hint.login_agy": "請使用 Antigravity CLI(`agy`)登入。", "usage.hint.no_quota": "此帳號未回報額度資訊。",
|
|
284
|
+
"usage.hint.login_codex": "請使用 Codex CLI(`codex`)登入。", "usage.hint.codex_no_oauth": "auth.json 沒有 OAuth 登入資料;只有 API key 時無法讀取用量。",
|
|
285
|
+
"usage.hint.login_grok": "請使用 Grok CLI(`grok login`)登入。", "usage.hint.grok_no_login": "auth.json 沒有可用的登入資料。請執行 `grok login`。",
|
|
286
|
+
"usage.hint.login_kiro": "請使用 Kiro CLI(`kiro-cli`)登入。", "usage.hint.kiro_signed_out": "目前已登出;請執行 `kiro-cli` 登入。",
|
|
287
|
+
"usage.hint.token_refreshing": "Token 更新中 — 請稍候再試。", "usage.hint.subscription_no_credit": "訂閱方案沒有可回報的額度用量。",
|
|
288
|
+
"usage.hint.no_quota_account": "此帳號類型沒有可用的額度資訊。",
|
|
289
|
+
"usage.error.unreachable": "無法連線至 {0}。", "usage.error.token_rejected": "Token 已被拒絕。請執行一次 `{0}` 以更新登入。",
|
|
290
|
+
"usage.error.rate_limited": "{0} 暫時限制請求速率,請稍後再試。", "usage.error.http": "用量請求失敗(HTTP {0})。",
|
|
291
|
+
"usage.error.billing_http": "計費請求失敗(HTTP {0})。", "usage.error.invalid_response": "用量端點回傳了無效資料。",
|
|
292
|
+
"usage.error.invalid_billing_response": "計費端點回傳了無效資料。", "usage.error.invalid_kiro_response": "GetUsageLimits 回傳了無效資料。",
|
|
293
|
+
"usage.error.claude_token_expired": "Access token 已過期;下次啟動任一 Claude instance 時會重新整理。",
|
|
294
|
+
"usage.error.codex_token_expired": "Access token 已過期。請執行一次 `codex` 以重新整理。",
|
|
295
|
+
"usage.error.grok_expired": "Grok session 已過期。請再次執行 `grok login`。", "usage.error.grok_response_changed": "Grok 計費回應格式已變更。",
|
|
296
|
+
"usage.error.agy_cap": "個人上限已用完,CLI 正受到速率限制(約 {0} 後重置)。各模型的額度池是另一組計數,因此仍可能顯示 0% 已使用。",
|
|
297
|
+
"usage.error.agy_no_refresh": "Access token 已過期,且未儲存 refresh token。請執行一次 `agy`。",
|
|
298
|
+
"usage.error.agy_token_expired": "Access token 已過期。請執行一次 `agy` 重新整理,或設定 AGY_OAUTH_CLIENT_ID/SECRET 以自動更新。",
|
|
299
|
+
"usage.error.agy_login_expired": "Google 登入已過期。請執行一次 `agy` 重新登入。", "usage.error.agy_refresh_empty": "Google token 更新回應沒有 access token。",
|
|
300
|
+
"usage.error.grok_auth_readonly": "Token 已過期且 auth.json 為唯讀。請執行一次 `grok` 重新整理。", "usage.error.grok_no_refresh": "Token 已過期且找不到 refresh token。請執行 `grok login`。",
|
|
301
|
+
"usage.error.grok_refresh_unreachable": "無法連線至 auth.x.ai 以更新 token。", "usage.error.grok_invalid_refresh": "auth.x.ai 回傳了無效的 token 更新資料。",
|
|
302
|
+
"usage.error.grok_persist": "Token 已更新但無法寫入 auth.json。請改為執行一次 `grok`。",
|
|
215
303
|
usageButton: "AI 訂閱用量", languageButton: "語言", helpButton: "使用說明", close: "關閉", expandProfile: "展開個人資料",
|
|
216
304
|
rosterFailed: "載入 instance 清單失敗:{error}\n(Fleet 是否正在執行?)", paneFailed: "⚠ 無法載入 {name} 的 pane(HTTP {status})。",
|
|
217
305
|
tokenRequired: "需要 web.token", avatarFailed: "上傳頭像失敗:{error}", saved: "已儲存", saveFailed: "儲存失敗:{error}" },
|
|
@@ -791,19 +879,32 @@
|
|
|
791
879
|
if (!iso) return "";
|
|
792
880
|
const ms = new Date(iso).getTime() - Date.now();
|
|
793
881
|
if (isNaN(ms)) return "";
|
|
794
|
-
if (ms <= 0) return T("
|
|
795
|
-
const
|
|
796
|
-
const
|
|
797
|
-
|
|
882
|
+
if (ms <= 0) return T("usage.reset.soon");
|
|
883
|
+
const totalMinutes = Math.max(1, Math.ceil(ms / 60000));
|
|
884
|
+
const d = Math.floor(totalMinutes / 1440), h = Math.floor((totalMinutes % 1440) / 60), m = totalMinutes % 60;
|
|
885
|
+
const duration = totalMinutes >= 2880
|
|
886
|
+
? usageLocalized("", { key: "usage.duration.days_hours", args: [d, h] })
|
|
887
|
+
: totalMinutes >= 60
|
|
888
|
+
? usageLocalized("", { key: "usage.duration.hours_minutes", args: [Math.floor(totalMinutes / 60), m] })
|
|
889
|
+
: usageLocalized("", { key: "usage.duration.minutes", args: [totalMinutes] });
|
|
890
|
+
return usageLocalized("", { key: "usage.reset.in", args: [duration] });
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
function usageLocalized(fallback, ref) {
|
|
894
|
+
if (!ref || typeof ref.key !== "string") return fallback == null ? "" : String(fallback);
|
|
895
|
+
const args = Array.isArray(ref.args) ? ref.args : [];
|
|
896
|
+
return args.reduce((text, value, index) => text.replaceAll(`{${index}}`, String(value)), T(ref.key));
|
|
798
897
|
}
|
|
799
898
|
|
|
800
899
|
function usageMetricHtml(m) {
|
|
900
|
+
const label = usageLocalized(m.label, m.labelI18n);
|
|
901
|
+
const note = m.note ? usageLocalized(m.note, m.noteI18n) : "";
|
|
801
902
|
if (m.type === "percent") {
|
|
802
903
|
const pct = Math.min(100, Math.max(0, m.used ?? 0));
|
|
803
904
|
const cls = pct >= 90 ? "crit" : pct >= 70 ? "warn" : "";
|
|
804
905
|
const word = pct >= 90 ? T("usageNear") : pct >= 70 ? T("usageHigh") : "";
|
|
805
|
-
const sub = [
|
|
806
|
-
return `<div class="u-metric"><div class="u-row"><span class="u-label">${esc(
|
|
906
|
+
const sub = [note, usageResetText(m.resetsAt)].filter(Boolean).join(" · ");
|
|
907
|
+
return `<div class="u-metric"><div class="u-row"><span class="u-label">${esc(label)}</span>` +
|
|
807
908
|
(word ? `<span class="u-status ${cls}">${esc(word)}</span>` : "") +
|
|
808
909
|
`<span class="u-val">${pct.toFixed(0)}%</span></div>` +
|
|
809
910
|
`<div class="u-meter"><div class="u-fill ${cls}" style="width:${pct}%"></div></div>` +
|
|
@@ -811,19 +912,22 @@
|
|
|
811
912
|
}
|
|
812
913
|
if (m.type === "dollars") {
|
|
813
914
|
const val = m.limit ? `$${(m.used ?? 0).toFixed(2)} / $${m.limit.toFixed(2)}` : `$${(m.used ?? 0).toFixed(2)}`;
|
|
814
|
-
return `<div class="u-metric"><div class="u-row"><span class="u-label">${esc(
|
|
915
|
+
return `<div class="u-metric"><div class="u-row"><span class="u-label">${esc(label)}</span><span class="u-val">${esc(val)}</span></div>` +
|
|
916
|
+
(note ? `<div class="u-sub">${esc(note)}</div>` : "") + `</div>`;
|
|
815
917
|
}
|
|
816
|
-
const
|
|
817
|
-
|
|
818
|
-
|
|
918
|
+
const value = usageLocalized(m.value ?? "", m.valueI18n);
|
|
919
|
+
const unit = usageLocalized(m.unit ?? "", m.unitI18n);
|
|
920
|
+
const val = `${value} ${unit}`.trim();
|
|
921
|
+
return `<div class="u-metric"><div class="u-row"><span class="u-label">${esc(label)}</span><span class="u-val">${esc(val)}</span></div>` +
|
|
922
|
+
(note ? `<div class="u-sub">${esc(note)}</div>` : "") + `</div>`;
|
|
819
923
|
}
|
|
820
924
|
|
|
821
925
|
function renderUsage() {
|
|
822
926
|
const box = q("usageContent");
|
|
823
|
-
if (!usageData || !Array.isArray(usageData.providers)) { box.innerHTML = `<h3
|
|
824
|
-
let html = `<h3
|
|
927
|
+
if (!usageData || !Array.isArray(usageData.providers)) { box.innerHTML = `<h3>📊 ${T("usage.title")}</h3><div class="u-err">${T("usageFail")}</div>`; return; }
|
|
928
|
+
let html = `<h3>📊 ${T("usage.title")}</h3>`;
|
|
825
929
|
const providers = orderedUsageProviders(usageData.providers);
|
|
826
|
-
if (!providers.length) html += `<div class="u-err">${T("
|
|
930
|
+
if (!providers.length) html += `<div class="u-err">${T("usage.empty_active")}</div>`;
|
|
827
931
|
for (let index = 0; index < providers.length; index++) {
|
|
828
932
|
const p = providers[index];
|
|
829
933
|
const key = providerKey(p);
|
|
@@ -831,13 +935,18 @@
|
|
|
831
935
|
(p.plan ? `<span class="u-plan">${esc(p.plan)}</span>` : "") +
|
|
832
936
|
`<span class="u-order"><button class="u-move" data-provider="${esc(key)}" data-delta="-1" title="${T("usageMoveUp")}" aria-label="${T("usageMoveUp")}"${index === 0 ? " disabled" : ""}>↑</button>` +
|
|
833
937
|
`<button class="u-move" data-provider="${esc(key)}" data-delta="1" title="${T("usageMoveDown")}" aria-label="${T("usageMoveDown")}"${index === providers.length - 1 ? " disabled" : ""}>↓</button></span></div>`;
|
|
834
|
-
if (p.status === "error") html += `<div class="u-err">${esc(p.error || "
|
|
938
|
+
if (p.status === "error") html += `<div class="u-err">${esc(usageLocalized(p.error || T("usage.error_fallback"), p.errorI18n))}</div>`;
|
|
939
|
+
else if (p.status === "no-credentials") {
|
|
940
|
+
html += `<div class="u-note">${esc(T("usage.not_logged_in"))}</div>`;
|
|
941
|
+
if (p.hint) html += `<div class="u-note">${esc(usageLocalized(p.hint, p.hintI18n))}</div>`;
|
|
942
|
+
}
|
|
835
943
|
else {
|
|
836
944
|
html += (p.metrics || []).map(usageMetricHtml).join("");
|
|
837
945
|
// A provider with nothing to meter (subscription plan) or one awaiting
|
|
838
946
|
// sign-in carries a hint instead of metrics. Without this the panel
|
|
839
947
|
// rendered an empty box, so those states looked broken.
|
|
840
|
-
if (p.hint) html += `<div class="u-note">${esc(p.hint)}</div>`;
|
|
948
|
+
if (p.hint) html += `<div class="u-note">${esc(usageLocalized(p.hint, p.hintI18n))}</div>`;
|
|
949
|
+
else if (!(p.metrics || []).length) html += `<div class="u-note">${esc(T("usage.no_data"))}</div>`;
|
|
841
950
|
}
|
|
842
951
|
html += `</div>`;
|
|
843
952
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { t } from "../locale.js";
|
|
2
|
+
import { usageResetText, usageText } from "./i18n.js";
|
|
1
3
|
const BAR_WIDTH = 10;
|
|
2
4
|
/** `████░░░░░░` for 40%. Clamped — a vendor "105% used" must not overflow. */
|
|
3
5
|
export function usageBar(percent) {
|
|
@@ -22,36 +24,35 @@ function metricPercent(m) {
|
|
|
22
24
|
return null;
|
|
23
25
|
}
|
|
24
26
|
function resetSuffix(resetsAt) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const at = new Date(resetsAt);
|
|
28
|
-
if (Number.isNaN(at.getTime()))
|
|
29
|
-
return "";
|
|
30
|
-
const hours = Math.round((at.getTime() - Date.now()) / 3_600_000);
|
|
31
|
-
if (hours <= 0)
|
|
32
|
-
return "";
|
|
33
|
-
return hours >= 48 ? ` · resets in ${Math.round(hours / 24)}d` : ` · resets in ${hours}h`;
|
|
27
|
+
const text = usageResetText(resetsAt);
|
|
28
|
+
return text ? ` · ${text}` : "";
|
|
34
29
|
}
|
|
35
30
|
function metricLine(m) {
|
|
36
31
|
const pct = metricPercent(m);
|
|
32
|
+
const label = usageText(m.label, m.labelI18n);
|
|
33
|
+
const note = m.note ? usageText(m.note, m.noteI18n) : "";
|
|
37
34
|
switch (m.type) {
|
|
38
35
|
case "percent":
|
|
39
36
|
return {
|
|
40
37
|
bar: usageBar(pct ?? 0),
|
|
41
38
|
// note explains WHICH number this is (e.g. "busiest of 8 models"); it is
|
|
42
39
|
// what stops a legitimate 0% from reading as a broken meter.
|
|
43
|
-
text: `${Math.round(m.used ?? 0)}% ${
|
|
40
|
+
text: `${Math.round(m.used ?? 0)}% ${label}${note ? ` (${note})` : ""}${resetSuffix(m.resetsAt)}`,
|
|
44
41
|
};
|
|
45
42
|
case "dollars": {
|
|
46
43
|
const used = `$${(m.used ?? 0).toFixed(2)}`;
|
|
47
44
|
return m.limit
|
|
48
|
-
? { bar: usageBar(pct ?? 0), text: `${used}/$${m.limit.toFixed(2)} ${
|
|
49
|
-
: { bar: null, text: `${used} ${
|
|
45
|
+
? { bar: usageBar(pct ?? 0), text: `${used}/$${m.limit.toFixed(2)} ${label}` }
|
|
46
|
+
: { bar: null, text: `${used} ${label}` };
|
|
47
|
+
}
|
|
48
|
+
case "count": {
|
|
49
|
+
const unit = m.unit ? usageText(m.unit, m.unitI18n) : "";
|
|
50
|
+
return { bar: null, text: `${label}: ${m.value ?? "?"}${unit ? ` ${unit}` : ""}${note ? ` (${note})` : ""}` };
|
|
51
|
+
}
|
|
52
|
+
case "text": {
|
|
53
|
+
const value = m.value != null ? usageText(String(m.value), m.valueI18n) : null;
|
|
54
|
+
return value != null ? { bar: null, text: `${label}: ${value}${note ? ` (${note})` : ""}` } : null;
|
|
50
55
|
}
|
|
51
|
-
case "count":
|
|
52
|
-
return { bar: null, text: `${m.label}: ${m.value ?? "?"}${m.unit ? ` ${m.unit}` : ""}` };
|
|
53
|
-
case "text":
|
|
54
|
-
return m.value != null ? { bar: null, text: `${m.label}: ${m.value}` } : null;
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
function toBlocks(payload) {
|
|
@@ -62,16 +63,16 @@ function toBlocks(payload) {
|
|
|
62
63
|
// An ok row can carry a hint too — e.g. "cached 3m ago — live query is rate
|
|
63
64
|
// limited" from the stale fallback. Shown under the metrics, not instead of
|
|
64
65
|
// them, so old numbers still read as numbers.
|
|
65
|
-
note: p.status === "no-credentials" ? "
|
|
66
|
-
: p.status === "error" ? `⚠️ ${p.error ?? "
|
|
66
|
+
note: p.status === "no-credentials" ? t("usage.not_logged_in")
|
|
67
|
+
: p.status === "error" ? `⚠️ ${usageText(p.error ?? t("usage.error_fallback"), p.errorI18n)}`
|
|
67
68
|
: null,
|
|
68
|
-
okHint: p.status === "ok" && p.hint ? p.hint : null,
|
|
69
|
+
okHint: p.status === "ok" && p.hint ? usageText(p.hint, p.hintI18n) : null,
|
|
69
70
|
lines: p.status === "ok" ? p.metrics.map(metricLine).filter((l) => l !== null) : [],
|
|
70
71
|
}));
|
|
71
72
|
}
|
|
72
73
|
/** Discord: native Markdown in plain message content. */
|
|
73
74
|
export function renderUsageMarkdown(payload) {
|
|
74
|
-
const out = [
|
|
75
|
+
const out = [`📊 **${t("usage.title")}**`];
|
|
75
76
|
for (const b of toBlocks(payload)) {
|
|
76
77
|
out.push("", `${b.dot} **${b.name}**${b.plan ? ` (${b.plan})` : ""}`);
|
|
77
78
|
if (b.note) {
|
|
@@ -79,7 +80,7 @@ export function renderUsageMarkdown(payload) {
|
|
|
79
80
|
continue;
|
|
80
81
|
}
|
|
81
82
|
if (b.lines.length === 0) {
|
|
82
|
-
out.push(b.okHint ? `> ${b.okHint}` :
|
|
83
|
+
out.push(b.okHint ? `> ${b.okHint}` : `> ${t("usage.no_data")}`);
|
|
83
84
|
continue;
|
|
84
85
|
}
|
|
85
86
|
for (const l of b.lines) {
|
|
@@ -99,7 +100,7 @@ function escapeHtml(s) {
|
|
|
99
100
|
* `<` would make Telegram reject the whole message.
|
|
100
101
|
*/
|
|
101
102
|
export function renderUsageHtml(payload) {
|
|
102
|
-
const out = [
|
|
103
|
+
const out = [`📊 <b>${escapeHtml(t("usage.title"))}</b>`];
|
|
103
104
|
for (const b of toBlocks(payload)) {
|
|
104
105
|
out.push("", `${b.dot} <b>${escapeHtml(b.name)}</b>${b.plan ? ` (${escapeHtml(b.plan)})` : ""}`);
|
|
105
106
|
if (b.note) {
|
|
@@ -107,7 +108,7 @@ export function renderUsageHtml(payload) {
|
|
|
107
108
|
continue;
|
|
108
109
|
}
|
|
109
110
|
if (b.lines.length === 0) {
|
|
110
|
-
out.push(b.okHint ? escapeHtml(b.okHint) : "
|
|
111
|
+
out.push(b.okHint ? escapeHtml(b.okHint) : escapeHtml(t("usage.no_data")));
|
|
111
112
|
continue;
|
|
112
113
|
}
|
|
113
114
|
for (const l of b.lines) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-rich.js","sourceRoot":"","sources":["../../src/usage/format-rich.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"format-rich.js","sourceRoot":"","sources":["../../src/usage/format-rich.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage copy shared by the server renderers and the browser-only View renderer.
|
|
3
|
+
* Keep this list explicit: the parity test rejects a key missing from either
|
|
4
|
+
* locale surface before a mixed-language release can ship.
|
|
5
|
+
*/
|
|
6
|
+
export declare const USAGE_I18N_KEYS: readonly ["usage.title", "usage.title_plain", "usage.empty_active", "usage.not_logged_in", "usage.error_fallback", "usage.no_data", "usage.reset.in", "usage.reset.soon", "usage.duration.days_hours", "usage.duration.hours_minutes", "usage.duration.minutes", "usage.metric.session", "usage.metric.weekly", "usage.metric.monthly", "usage.metric.days_window", "usage.metric.named", "usage.metric.named_session", "usage.metric.named_weekly", "usage.metric.named_monthly", "usage.metric.named_days", "usage.metric.extra_usage", "usage.metric.rate_limit_resets", "usage.metric.credits", "usage.metric.weekly_limit", "usage.metric.pay_as_you_go", "usage.metric.bonus_credits", "usage.metric.overage_charges", "usage.metric.pay_per_use", "usage.unit.available", "usage.unit.credits", "usage.metric.usage_monthly", "usage.note.used_limit_unit", "usage.note.bonus_codes", "usage.value.disabled", "usage.value.cap", "usage.note.binding", "usage.note.binding_severity", "usage.note.busiest_model", "usage.note.busiest_models", "usage.metric.agy_claude_others", "usage.metric.agy_claude_others_session", "usage.metric.agy_claude_others_weekly", "usage.hint.statusline_now", "usage.hint.statusline_minutes", "usage.hint.claude_api_key", "usage.hint.login_claude", "usage.hint.login_agy", "usage.hint.no_quota", "usage.hint.login_codex", "usage.hint.codex_no_oauth", "usage.hint.login_grok", "usage.hint.grok_no_login", "usage.hint.login_kiro", "usage.hint.kiro_signed_out", "usage.hint.token_refreshing", "usage.hint.subscription_no_credit", "usage.hint.no_quota_account", "usage.error.unreachable", "usage.error.token_rejected", "usage.error.rate_limited", "usage.error.http", "usage.error.billing_http", "usage.error.invalid_response", "usage.error.invalid_billing_response", "usage.error.invalid_kiro_response", "usage.error.claude_token_expired", "usage.error.codex_token_expired", "usage.error.grok_expired", "usage.error.grok_response_changed", "usage.error.agy_cap", "usage.error.agy_no_refresh", "usage.error.agy_token_expired", "usage.error.agy_login_expired", "usage.error.agy_refresh_empty", "usage.error.grok_auth_readonly", "usage.error.grok_no_refresh", "usage.error.grok_refresh_unreachable", "usage.error.grok_invalid_refresh", "usage.error.grok_persist"];
|
|
7
|
+
export type UsageI18nKey = typeof USAGE_I18N_KEYS[number];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage copy shared by the server renderers and the browser-only View renderer.
|
|
3
|
+
* Keep this list explicit: the parity test rejects a key missing from either
|
|
4
|
+
* locale surface before a mixed-language release can ship.
|
|
5
|
+
*/
|
|
6
|
+
export const USAGE_I18N_KEYS = [
|
|
7
|
+
"usage.title", "usage.title_plain", "usage.empty_active", "usage.not_logged_in",
|
|
8
|
+
"usage.error_fallback", "usage.no_data", "usage.reset.in", "usage.reset.soon",
|
|
9
|
+
"usage.duration.days_hours", "usage.duration.hours_minutes", "usage.duration.minutes",
|
|
10
|
+
"usage.metric.session", "usage.metric.weekly", "usage.metric.monthly", "usage.metric.days_window",
|
|
11
|
+
"usage.metric.named", "usage.metric.named_session", "usage.metric.named_weekly",
|
|
12
|
+
"usage.metric.named_monthly", "usage.metric.named_days", "usage.metric.extra_usage",
|
|
13
|
+
"usage.metric.rate_limit_resets", "usage.metric.credits", "usage.metric.weekly_limit",
|
|
14
|
+
"usage.metric.pay_as_you_go", "usage.metric.bonus_credits", "usage.metric.overage_charges",
|
|
15
|
+
"usage.metric.pay_per_use", "usage.unit.available", "usage.unit.credits",
|
|
16
|
+
"usage.metric.usage_monthly", "usage.note.used_limit_unit", "usage.note.bonus_codes",
|
|
17
|
+
"usage.value.disabled", "usage.value.cap", "usage.note.binding",
|
|
18
|
+
"usage.note.binding_severity", "usage.note.busiest_model", "usage.note.busiest_models",
|
|
19
|
+
"usage.metric.agy_claude_others", "usage.metric.agy_claude_others_session", "usage.metric.agy_claude_others_weekly",
|
|
20
|
+
"usage.hint.statusline_now", "usage.hint.statusline_minutes", "usage.hint.claude_api_key",
|
|
21
|
+
"usage.hint.login_claude", "usage.hint.login_agy", "usage.hint.no_quota",
|
|
22
|
+
"usage.hint.login_codex", "usage.hint.codex_no_oauth", "usage.hint.login_grok",
|
|
23
|
+
"usage.hint.grok_no_login", "usage.hint.login_kiro", "usage.hint.kiro_signed_out",
|
|
24
|
+
"usage.hint.token_refreshing", "usage.hint.subscription_no_credit", "usage.hint.no_quota_account",
|
|
25
|
+
"usage.error.unreachable", "usage.error.token_rejected", "usage.error.rate_limited",
|
|
26
|
+
"usage.error.http", "usage.error.billing_http", "usage.error.invalid_response",
|
|
27
|
+
"usage.error.invalid_billing_response", "usage.error.invalid_kiro_response",
|
|
28
|
+
"usage.error.claude_token_expired", "usage.error.codex_token_expired",
|
|
29
|
+
"usage.error.grok_expired", "usage.error.grok_response_changed", "usage.error.agy_cap",
|
|
30
|
+
"usage.error.agy_no_refresh", "usage.error.agy_token_expired", "usage.error.agy_login_expired",
|
|
31
|
+
"usage.error.agy_refresh_empty", "usage.error.grok_auth_readonly", "usage.error.grok_no_refresh",
|
|
32
|
+
"usage.error.grok_refresh_unreachable", "usage.error.grok_invalid_refresh", "usage.error.grok_persist",
|
|
33
|
+
];
|
|
34
|
+
//# sourceMappingURL=i18n-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n-keys.js","sourceRoot":"","sources":["../../src/usage/i18n-keys.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,aAAa,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,qBAAqB;IAC/E,sBAAsB,EAAE,eAAe,EAAE,gBAAgB,EAAE,kBAAkB;IAC7E,2BAA2B,EAAE,8BAA8B,EAAE,wBAAwB;IACrF,sBAAsB,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,0BAA0B;IACjG,oBAAoB,EAAE,4BAA4B,EAAE,2BAA2B;IAC/E,4BAA4B,EAAE,yBAAyB,EAAE,0BAA0B;IACnF,gCAAgC,EAAE,sBAAsB,EAAE,2BAA2B;IACrF,4BAA4B,EAAE,4BAA4B,EAAE,8BAA8B;IAC1F,0BAA0B,EAAE,sBAAsB,EAAE,oBAAoB;IACxE,4BAA4B,EAAE,4BAA4B,EAAE,wBAAwB;IACpF,sBAAsB,EAAE,iBAAiB,EAAE,oBAAoB;IAC/D,6BAA6B,EAAE,0BAA0B,EAAE,2BAA2B;IACtF,gCAAgC,EAAE,wCAAwC,EAAE,uCAAuC;IACnH,2BAA2B,EAAE,+BAA+B,EAAE,2BAA2B;IACzF,yBAAyB,EAAE,sBAAsB,EAAE,qBAAqB;IACxE,wBAAwB,EAAE,2BAA2B,EAAE,uBAAuB;IAC9E,0BAA0B,EAAE,uBAAuB,EAAE,4BAA4B;IACjF,6BAA6B,EAAE,mCAAmC,EAAE,6BAA6B;IACjG,yBAAyB,EAAE,4BAA4B,EAAE,0BAA0B;IACnF,kBAAkB,EAAE,0BAA0B,EAAE,8BAA8B;IAC9E,sCAAsC,EAAE,mCAAmC;IAC3E,kCAAkC,EAAE,iCAAiC;IACrE,0BAA0B,EAAE,mCAAmC,EAAE,qBAAqB;IACtF,4BAA4B,EAAE,+BAA+B,EAAE,+BAA+B;IAC9F,+BAA+B,EAAE,gCAAgC,EAAE,6BAA6B;IAChG,sCAAsC,EAAE,kCAAkC,EAAE,0BAA0B;CAC9F,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { UsageI18nRef } from "./providers.js";
|
|
2
|
+
/** Resolve AgEnD-owned usage copy while preserving raw vendor text as fallback. */
|
|
3
|
+
export declare function usageText(fallback: string, ref?: UsageI18nRef): string;
|
|
4
|
+
/** Minute-precision duration shared by the chat and MCP usage renderers. */
|
|
5
|
+
export declare function usageResetText(resetsAt?: string | null): string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { t } from "../locale.js";
|
|
2
|
+
/** Resolve AgEnD-owned usage copy while preserving raw vendor text as fallback. */
|
|
3
|
+
export function usageText(fallback, ref) {
|
|
4
|
+
return ref ? t(ref.key, ...(ref.args ?? [])) : fallback;
|
|
5
|
+
}
|
|
6
|
+
/** Minute-precision duration shared by the chat and MCP usage renderers. */
|
|
7
|
+
export function usageResetText(resetsAt) {
|
|
8
|
+
if (!resetsAt)
|
|
9
|
+
return "";
|
|
10
|
+
const at = new Date(resetsAt);
|
|
11
|
+
if (Number.isNaN(at.getTime()))
|
|
12
|
+
return "";
|
|
13
|
+
const remainingMs = at.getTime() - Date.now();
|
|
14
|
+
if (remainingMs <= 0)
|
|
15
|
+
return t("usage.reset.soon");
|
|
16
|
+
const totalMinutes = Math.max(1, Math.ceil(remainingMs / 60_000));
|
|
17
|
+
const days = Math.floor(totalMinutes / 1_440);
|
|
18
|
+
const hours = Math.floor((totalMinutes % 1_440) / 60);
|
|
19
|
+
const minutes = totalMinutes % 60;
|
|
20
|
+
const duration = totalMinutes >= 2_880
|
|
21
|
+
? t("usage.duration.days_hours", days, hours)
|
|
22
|
+
: totalMinutes >= 60
|
|
23
|
+
? t("usage.duration.hours_minutes", Math.floor(totalMinutes / 60), minutes)
|
|
24
|
+
: t("usage.duration.minutes", totalMinutes);
|
|
25
|
+
return t("usage.reset.in", duration);
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=i18n.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../../src/usage/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAC;AAGjC,mFAAmF;AACnF,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,GAAkB;IAC5D,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC1D,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,cAAc,CAAC,QAAwB;IACrD,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzB,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9B,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1C,MAAM,WAAW,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC9C,IAAI,WAAW,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC;IAEnD,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC;IAClE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,YAAY,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;IACtD,MAAM,OAAO,GAAG,YAAY,GAAG,EAAE,CAAC;IAClC,MAAM,QAAQ,GAAG,YAAY,IAAI,KAAK;QACpC,CAAC,CAAC,CAAC,CAAC,2BAA2B,EAAE,IAAI,EAAE,KAAK,CAAC;QAC7C,CAAC,CAAC,YAAY,IAAI,EAAE;YAClB,CAAC,CAAC,CAAC,CAAC,8BAA8B,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,EAAE,CAAC,EAAE,OAAO,CAAC;YAC3E,CAAC,CAAC,CAAC,CAAC,wBAAwB,EAAE,YAAY,CAAC,CAAC;IAChD,OAAO,CAAC,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;AACvC,CAAC"}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { type StatuslineRateLimits } from "./statusline-usage.js";
|
|
2
|
+
import type { UsageI18nKey } from "./i18n-keys.js";
|
|
2
3
|
export interface UsageMetric {
|
|
3
4
|
label: string;
|
|
5
|
+
/** Locale-neutral semantic text. Renderers translate this and keep `label` as a fallback. */
|
|
6
|
+
labelI18n?: UsageI18nRef;
|
|
4
7
|
type: "percent" | "dollars" | "count" | "text";
|
|
5
8
|
used?: number;
|
|
6
9
|
limit?: number;
|
|
7
10
|
value?: number | string;
|
|
11
|
+
valueI18n?: UsageI18nRef;
|
|
8
12
|
unit?: string;
|
|
13
|
+
unitI18n?: UsageI18nRef;
|
|
9
14
|
note?: string;
|
|
15
|
+
noteI18n?: UsageI18nRef;
|
|
10
16
|
resetsAt?: string | null;
|
|
11
17
|
windowMs?: number | null;
|
|
12
18
|
}
|
|
@@ -16,9 +22,15 @@ export interface ProviderUsage {
|
|
|
16
22
|
status: "ok" | "error" | "no-credentials";
|
|
17
23
|
plan?: string | null;
|
|
18
24
|
error?: string;
|
|
25
|
+
errorI18n?: UsageI18nRef;
|
|
19
26
|
hint?: string;
|
|
27
|
+
hintI18n?: UsageI18nRef;
|
|
20
28
|
metrics: UsageMetric[];
|
|
21
29
|
}
|
|
30
|
+
export interface UsageI18nRef {
|
|
31
|
+
key: UsageI18nKey;
|
|
32
|
+
args?: Array<string | number>;
|
|
33
|
+
}
|
|
22
34
|
/** `CLAUDE_CODE_OAUTH_TOKEN` as exported in a shell rc file, or null. */
|
|
23
35
|
export declare function tokenFromShellRc(read: (path: string) => string, home?: string, files?: readonly string[]): string | null;
|
|
24
36
|
/**
|