ikanban-web 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -0
- package/bin/cli.js +1055 -0
- package/dist/apple-touch-icon-120x120.png +0 -0
- package/dist/apple-touch-icon-152x152.png +0 -0
- package/dist/apple-touch-icon-167x167.png +0 -0
- package/dist/apple-touch-icon-180x180.png +0 -0
- package/dist/apple-touch-icon.png +0 -0
- package/dist/apple-touch-icon.svg +16 -0
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/ToolOutputDialog-BgFj3J4u.js +5 -0
- package/dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/dist/assets/index-BQJPAP5f.css +1 -0
- package/dist/assets/index-MBuF9PXk.js +2 -0
- package/dist/assets/main-C1GggQif.css +1 -0
- package/dist/assets/main-Dyy8WvPJ.js +311 -0
- package/dist/assets/vendor--DEobTDja.css +1 -0
- package/dist/assets/vendor-.bun-KwPUu5oc.js +4594 -0
- package/dist/assets/wasm-CG6Dc4jp.js +1 -0
- package/dist/assets/worker-suRQxAtO.js +155 -0
- package/dist/favicon-16.png +0 -0
- package/dist/favicon-32.png +0 -0
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +15 -0
- package/dist/index.html +325 -0
- package/dist/logo-dark-192x192.png +0 -0
- package/dist/logo-dark-512x512.svg +16 -0
- package/dist/logo-light-192x192.png +0 -0
- package/dist/logo-light-512x512.svg +16 -0
- package/dist/pwa-192.png +0 -0
- package/dist/pwa-512.png +0 -0
- package/dist/pwa-maskable-192.png +0 -0
- package/dist/pwa-maskable-512.png +0 -0
- package/dist/site.webmanifest +22 -0
- package/dist/sw.js +1 -0
- package/package.json +102 -0
- package/public/apple-touch-icon-120x120.png +0 -0
- package/public/apple-touch-icon-152x152.png +0 -0
- package/public/apple-touch-icon-167x167.png +0 -0
- package/public/apple-touch-icon-180x180.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/apple-touch-icon.svg +16 -0
- package/public/favicon-16.png +0 -0
- package/public/favicon-32.png +0 -0
- package/public/favicon.png +0 -0
- package/public/favicon.svg +15 -0
- package/public/logo-dark-192x192.png +0 -0
- package/public/logo-dark-512x512.svg +16 -0
- package/public/logo-light-192x192.png +0 -0
- package/public/logo-light-512x512.svg +16 -0
- package/public/pwa-192.png +0 -0
- package/public/pwa-512.png +0 -0
- package/public/pwa-maskable-192.png +0 -0
- package/public/pwa-maskable-512.png +0 -0
- package/public/site.webmanifest +22 -0
- package/server/TERMINAL_INPUT_WS_PROTOCOL.md +44 -0
- package/server/index.d.ts +28 -0
- package/server/index.js +11181 -0
- package/server/lib/cloudflare-tunnel.js +196 -0
- package/server/lib/git-credentials.js +74 -0
- package/server/lib/git-identity-storage.js +110 -0
- package/server/lib/git-service.js +2690 -0
- package/server/lib/github-auth.js +307 -0
- package/server/lib/github-device-flow.js +50 -0
- package/server/lib/github-octokit.js +10 -0
- package/server/lib/github-repo.js +55 -0
- package/server/lib/notification-message.js +49 -0
- package/server/lib/notification-message.test.js +59 -0
- package/server/lib/opencode-auth.js +81 -0
- package/server/lib/opencode-config.js +1840 -0
- package/server/lib/opencode-config.js.d.ts +12 -0
- package/server/lib/package-manager.js +255 -0
- package/server/lib/quota/DOCUMENTATION.md +52 -0
- package/server/lib/quota/index.js +21 -0
- package/server/lib/quota/providers/claude.js +107 -0
- package/server/lib/quota/providers/codex.js +111 -0
- package/server/lib/quota/providers/copilot.js +165 -0
- package/server/lib/quota/providers/google/api.js +92 -0
- package/server/lib/quota/providers/google/auth.js +108 -0
- package/server/lib/quota/providers/google/index.js +124 -0
- package/server/lib/quota/providers/google/transforms.js +109 -0
- package/server/lib/quota/providers/index.js +128 -0
- package/server/lib/quota/providers/interface.js +55 -0
- package/server/lib/quota/providers/kimi.js +108 -0
- package/server/lib/quota/providers/nanogpt.js +124 -0
- package/server/lib/quota/providers/openai.js +91 -0
- package/server/lib/quota/providers/openrouter.js +92 -0
- package/server/lib/quota/providers/zai.js +91 -0
- package/server/lib/quota/utils/auth.js +46 -0
- package/server/lib/quota/utils/formatters.js +76 -0
- package/server/lib/quota/utils/index.js +10 -0
- package/server/lib/quota/utils/transformers.js +55 -0
- package/server/lib/skills-catalog/cache.js +29 -0
- package/server/lib/skills-catalog/clawdhub/api.js +158 -0
- package/server/lib/skills-catalog/clawdhub/index.js +30 -0
- package/server/lib/skills-catalog/clawdhub/install.js +223 -0
- package/server/lib/skills-catalog/clawdhub/scan.js +113 -0
- package/server/lib/skills-catalog/curated-sources.js +21 -0
- package/server/lib/skills-catalog/git.js +76 -0
- package/server/lib/skills-catalog/install.js +280 -0
- package/server/lib/skills-catalog/scan.js +221 -0
- package/server/lib/skills-catalog/source.js +85 -0
- package/server/lib/summarization-service.js +171 -0
- package/server/lib/terminal-input-ws-protocol.js +66 -0
- package/server/lib/terminal-input-ws-protocol.test.js +138 -0
- package/server/lib/tts-service.js +162 -0
- package/server/lib/ui-auth.js +518 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quota Providers Registry
|
|
3
|
+
*
|
|
4
|
+
* Implements quota fetching for various AI providers using a registry pattern.
|
|
5
|
+
* @module quota/providers
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { buildResult } from '../utils/index.js';
|
|
9
|
+
|
|
10
|
+
import * as claude from './claude.js';
|
|
11
|
+
import * as codex from './codex.js';
|
|
12
|
+
import * as copilot from './copilot.js';
|
|
13
|
+
import * as google from './google/index.js';
|
|
14
|
+
import * as kimi from './kimi.js';
|
|
15
|
+
import * as nanogpt from './nanogpt.js';
|
|
16
|
+
import * as openai from './openai.js';
|
|
17
|
+
import * as openrouter from './openrouter.js';
|
|
18
|
+
import * as zai from './zai.js';
|
|
19
|
+
|
|
20
|
+
const registry = {
|
|
21
|
+
claude: {
|
|
22
|
+
providerId: claude.providerId,
|
|
23
|
+
providerName: claude.providerName,
|
|
24
|
+
isConfigured: claude.isConfigured,
|
|
25
|
+
fetchQuota: claude.fetchQuota
|
|
26
|
+
},
|
|
27
|
+
codex: {
|
|
28
|
+
providerId: codex.providerId,
|
|
29
|
+
providerName: codex.providerName,
|
|
30
|
+
isConfigured: codex.isConfigured,
|
|
31
|
+
fetchQuota: codex.fetchQuota
|
|
32
|
+
},
|
|
33
|
+
google: {
|
|
34
|
+
providerId: 'google',
|
|
35
|
+
providerName: 'Google',
|
|
36
|
+
isConfigured: () => google.resolveGoogleAuthSources().length > 0,
|
|
37
|
+
fetchQuota: google.fetchGoogleQuota
|
|
38
|
+
},
|
|
39
|
+
'zai-coding-plan': {
|
|
40
|
+
providerId: zai.providerId,
|
|
41
|
+
providerName: zai.providerName,
|
|
42
|
+
isConfigured: zai.isConfigured,
|
|
43
|
+
fetchQuota: zai.fetchQuota
|
|
44
|
+
},
|
|
45
|
+
'kimi-for-coding': {
|
|
46
|
+
providerId: kimi.providerId,
|
|
47
|
+
providerName: kimi.providerName,
|
|
48
|
+
isConfigured: kimi.isConfigured,
|
|
49
|
+
fetchQuota: kimi.fetchQuota
|
|
50
|
+
},
|
|
51
|
+
openrouter: {
|
|
52
|
+
providerId: openrouter.providerId,
|
|
53
|
+
providerName: openrouter.providerName,
|
|
54
|
+
isConfigured: openrouter.isConfigured,
|
|
55
|
+
fetchQuota: openrouter.fetchQuota
|
|
56
|
+
},
|
|
57
|
+
'nano-gpt': {
|
|
58
|
+
providerId: nanogpt.providerId,
|
|
59
|
+
providerName: nanogpt.providerName,
|
|
60
|
+
isConfigured: nanogpt.isConfigured,
|
|
61
|
+
fetchQuota: nanogpt.fetchQuota
|
|
62
|
+
},
|
|
63
|
+
'github-copilot': {
|
|
64
|
+
providerId: copilot.providerId,
|
|
65
|
+
providerName: copilot.providerName,
|
|
66
|
+
isConfigured: copilot.isConfigured,
|
|
67
|
+
fetchQuota: copilot.fetchQuota
|
|
68
|
+
},
|
|
69
|
+
'github-copilot-addon': {
|
|
70
|
+
providerId: copilot.providerIdAddon,
|
|
71
|
+
providerName: copilot.providerNameAddon,
|
|
72
|
+
isConfigured: copilot.isConfigured,
|
|
73
|
+
fetchQuota: copilot.fetchQuotaAddon
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const listConfiguredQuotaProviders = () => {
|
|
78
|
+
const configured = [];
|
|
79
|
+
|
|
80
|
+
for (const [id, provider] of Object.entries(registry)) {
|
|
81
|
+
try {
|
|
82
|
+
if (provider.isConfigured()) {
|
|
83
|
+
configured.push(id);
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
// Ignore provider-specific config errors in list API.
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return configured;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const fetchQuotaForProvider = async (providerId) => {
|
|
94
|
+
const provider = registry[providerId];
|
|
95
|
+
|
|
96
|
+
if (!provider) {
|
|
97
|
+
return buildResult({
|
|
98
|
+
providerId,
|
|
99
|
+
providerName: providerId,
|
|
100
|
+
ok: false,
|
|
101
|
+
configured: false,
|
|
102
|
+
error: 'Unsupported provider'
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
return await provider.fetchQuota();
|
|
108
|
+
} catch (error) {
|
|
109
|
+
return buildResult({
|
|
110
|
+
providerId: provider.providerId,
|
|
111
|
+
providerName: provider.providerName,
|
|
112
|
+
ok: false,
|
|
113
|
+
configured: true,
|
|
114
|
+
error: error instanceof Error ? error.message : 'Request failed'
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export const fetchClaudeQuota = claude.fetchQuota;
|
|
120
|
+
export const fetchOpenaiQuota = openai.fetchQuota;
|
|
121
|
+
export const fetchGoogleQuota = google.fetchGoogleQuota;
|
|
122
|
+
export const fetchCodexQuota = codex.fetchQuota;
|
|
123
|
+
export const fetchCopilotQuota = copilot.fetchQuota;
|
|
124
|
+
export const fetchCopilotAddonQuota = copilot.fetchQuotaAddon;
|
|
125
|
+
export const fetchKimiQuota = kimi.fetchQuota;
|
|
126
|
+
export const fetchOpenRouterQuota = openrouter.fetchQuota;
|
|
127
|
+
export const fetchZaiQuota = zai.fetchQuota;
|
|
128
|
+
export const fetchNanoGptQuota = nanogpt.fetchQuota;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quota Provider Interface
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract for implementing quota providers.
|
|
5
|
+
* @module quota/providers
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} UsageWindow
|
|
10
|
+
* @property {number|null} usedPercent - Percentage of usage (0-100)
|
|
11
|
+
* @property {number|null} remainingPercent - Percentage remaining (0-100)
|
|
12
|
+
* @property {number|null} windowSeconds - Window duration in seconds
|
|
13
|
+
* @property {number|null} resetAfterSeconds - Seconds until reset
|
|
14
|
+
* @property {number|null} resetAt - Unix timestamp when quota resets
|
|
15
|
+
* @property {string|null} resetAtFormatted - Human-readable reset time
|
|
16
|
+
* @property {string|null} resetAfterFormatted - Human-readable time until reset
|
|
17
|
+
* @property {string|null} valueLabel - Optional label for display (e.g., "$10.00 remaining")
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} ProviderUsage
|
|
22
|
+
* @property {Object.<string, UsageWindow>} windows - Usage windows by key (e.g., '5h', '7d', 'daily')
|
|
23
|
+
* @property {Object.<string, Object>} [models] - Model-specific usage (provider-specific)
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {Object} QuotaProviderResult
|
|
28
|
+
* @property {string} providerId - Unique identifier for the provider
|
|
29
|
+
* @property {string} providerName - Display name for the provider
|
|
30
|
+
* @property {boolean} ok - Whether the fetch was successful
|
|
31
|
+
* @property {boolean} configured - Whether the provider is configured
|
|
32
|
+
* @property {ProviderUsage|null} usage - Usage data if successful
|
|
33
|
+
* @property {string|null} [error] - Error message if not successful
|
|
34
|
+
* @property {number} fetchedAt - Unix timestamp when the result was fetched
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {Function} ProviderQuotaFetcher
|
|
39
|
+
* @returns {Promise<QuotaProviderResult>}
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @typedef {Function} ProviderConfigurationChecker
|
|
44
|
+
* @param {Object.<string, unknown>} [auth]
|
|
45
|
+
* @returns {boolean}
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {Object} QuotaProvider
|
|
50
|
+
* @property {string} providerId
|
|
51
|
+
* @property {string} providerName
|
|
52
|
+
* @property {string[]} aliases
|
|
53
|
+
* @property {ProviderConfigurationChecker} isConfigured
|
|
54
|
+
* @property {ProviderQuotaFetcher} fetchQuota
|
|
55
|
+
*/
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { readAuthFile } from '../../opencode-auth.js';
|
|
2
|
+
import {
|
|
3
|
+
getAuthEntry,
|
|
4
|
+
normalizeAuthEntry,
|
|
5
|
+
buildResult,
|
|
6
|
+
toUsageWindow,
|
|
7
|
+
toNumber,
|
|
8
|
+
toTimestamp,
|
|
9
|
+
durationToLabel,
|
|
10
|
+
durationToSeconds
|
|
11
|
+
} from '../utils/index.js';
|
|
12
|
+
|
|
13
|
+
export const providerId = 'kimi-for-coding';
|
|
14
|
+
export const providerName = 'Kimi for Coding';
|
|
15
|
+
export const aliases = ['kimi-for-coding', 'kimi'];
|
|
16
|
+
|
|
17
|
+
export const isConfigured = () => {
|
|
18
|
+
const auth = readAuthFile();
|
|
19
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
20
|
+
return Boolean(entry?.key || entry?.token);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const fetchQuota = async () => {
|
|
24
|
+
const auth = readAuthFile();
|
|
25
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
26
|
+
const apiKey = entry?.key ?? entry?.token;
|
|
27
|
+
|
|
28
|
+
if (!apiKey) {
|
|
29
|
+
return buildResult({
|
|
30
|
+
providerId,
|
|
31
|
+
providerName,
|
|
32
|
+
ok: false,
|
|
33
|
+
configured: false,
|
|
34
|
+
error: 'Not configured'
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch('https://api.kimi.com/coding/v1/usages', {
|
|
40
|
+
method: 'GET',
|
|
41
|
+
headers: {
|
|
42
|
+
Authorization: `Bearer ${apiKey}`,
|
|
43
|
+
'Content-Type': 'application/json'
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
return buildResult({
|
|
49
|
+
providerId,
|
|
50
|
+
providerName,
|
|
51
|
+
ok: false,
|
|
52
|
+
configured: true,
|
|
53
|
+
error: `API error: ${response.status}`
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const payload = await response.json();
|
|
58
|
+
const windows = {};
|
|
59
|
+
const usage = payload?.usage ?? null;
|
|
60
|
+
if (usage) {
|
|
61
|
+
const limit = toNumber(usage.limit);
|
|
62
|
+
const remaining = toNumber(usage.remaining);
|
|
63
|
+
const usedPercent = limit && remaining !== null
|
|
64
|
+
? Math.max(0, Math.min(100, 100 - (remaining / limit) * 100))
|
|
65
|
+
: null;
|
|
66
|
+
windows.weekly = toUsageWindow({
|
|
67
|
+
usedPercent,
|
|
68
|
+
windowSeconds: null,
|
|
69
|
+
resetAt: toTimestamp(usage.resetTime)
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const limits = Array.isArray(payload?.limits) ? payload.limits : [];
|
|
74
|
+
for (const limit of limits) {
|
|
75
|
+
const window = limit?.window;
|
|
76
|
+
const detail = limit?.detail;
|
|
77
|
+
const rawLabel = durationToLabel(window?.duration, window?.timeUnit);
|
|
78
|
+
const windowSeconds = durationToSeconds(window?.duration, window?.timeUnit);
|
|
79
|
+
const label = windowSeconds === 5 * 60 * 60 ? `Rate Limit (${rawLabel})` : rawLabel;
|
|
80
|
+
const total = toNumber(detail?.limit);
|
|
81
|
+
const remaining = toNumber(detail?.remaining);
|
|
82
|
+
const usedPercent = total && remaining !== null
|
|
83
|
+
? Math.max(0, Math.min(100, 100 - (remaining / total) * 100))
|
|
84
|
+
: null;
|
|
85
|
+
windows[label] = toUsageWindow({
|
|
86
|
+
usedPercent,
|
|
87
|
+
windowSeconds,
|
|
88
|
+
resetAt: toTimestamp(detail?.resetTime)
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return buildResult({
|
|
93
|
+
providerId,
|
|
94
|
+
providerName,
|
|
95
|
+
ok: true,
|
|
96
|
+
configured: true,
|
|
97
|
+
usage: { windows }
|
|
98
|
+
});
|
|
99
|
+
} catch (error) {
|
|
100
|
+
return buildResult({
|
|
101
|
+
providerId,
|
|
102
|
+
providerName,
|
|
103
|
+
ok: false,
|
|
104
|
+
configured: true,
|
|
105
|
+
error: error instanceof Error ? error.message : 'Request failed'
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { readAuthFile } from '../../opencode-auth.js';
|
|
2
|
+
import {
|
|
3
|
+
getAuthEntry,
|
|
4
|
+
normalizeAuthEntry,
|
|
5
|
+
buildResult,
|
|
6
|
+
toUsageWindow,
|
|
7
|
+
toNumber,
|
|
8
|
+
toTimestamp
|
|
9
|
+
} from '../utils/index.js';
|
|
10
|
+
|
|
11
|
+
const NANO_GPT_DAILY_WINDOW_SECONDS = 86400;
|
|
12
|
+
|
|
13
|
+
export const providerId = 'nano-gpt';
|
|
14
|
+
export const providerName = 'NanoGPT';
|
|
15
|
+
export const aliases = ['nano-gpt', 'nanogpt', 'nano_gpt'];
|
|
16
|
+
|
|
17
|
+
export const isConfigured = () => {
|
|
18
|
+
const auth = readAuthFile();
|
|
19
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
20
|
+
return Boolean(entry?.key || entry?.token);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const fetchQuota = async () => {
|
|
24
|
+
const auth = readAuthFile();
|
|
25
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
26
|
+
const apiKey = entry?.key ?? entry?.token;
|
|
27
|
+
|
|
28
|
+
if (!apiKey) {
|
|
29
|
+
return buildResult({
|
|
30
|
+
providerId,
|
|
31
|
+
providerName,
|
|
32
|
+
ok: false,
|
|
33
|
+
configured: false,
|
|
34
|
+
error: 'Not configured'
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch('https://nano-gpt.com/api/subscription/v1/usage', {
|
|
40
|
+
method: 'GET',
|
|
41
|
+
headers: {
|
|
42
|
+
Authorization: `Bearer ${apiKey}`,
|
|
43
|
+
'Content-Type': 'application/json'
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
return buildResult({
|
|
49
|
+
providerId,
|
|
50
|
+
providerName,
|
|
51
|
+
ok: false,
|
|
52
|
+
configured: true,
|
|
53
|
+
error: `API error: ${response.status}`
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const payload = await response.json();
|
|
58
|
+
const windows = {};
|
|
59
|
+
const period = payload?.period ?? null;
|
|
60
|
+
const daily = payload?.daily ?? null;
|
|
61
|
+
const monthly = payload?.monthly ?? null;
|
|
62
|
+
const state = payload?.state ?? 'active';
|
|
63
|
+
|
|
64
|
+
if (daily) {
|
|
65
|
+
let usedPercent = null;
|
|
66
|
+
const percentUsed = daily?.percentUsed;
|
|
67
|
+
if (typeof percentUsed === 'number') {
|
|
68
|
+
usedPercent = Math.max(0, Math.min(100, percentUsed * 100));
|
|
69
|
+
} else {
|
|
70
|
+
const used = toNumber(daily?.used);
|
|
71
|
+
const limit = toNumber(daily?.limit ?? daily?.limits?.daily);
|
|
72
|
+
if (used !== null && limit !== null && limit > 0) {
|
|
73
|
+
usedPercent = Math.max(0, Math.min(100, (used / limit) * 100));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const resetAt = toTimestamp(daily?.resetAt);
|
|
77
|
+
const valueLabel = state !== 'active' ? `(${state})` : null;
|
|
78
|
+
windows['daily'] = toUsageWindow({
|
|
79
|
+
usedPercent,
|
|
80
|
+
windowSeconds: NANO_GPT_DAILY_WINDOW_SECONDS,
|
|
81
|
+
resetAt,
|
|
82
|
+
valueLabel
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (monthly) {
|
|
87
|
+
let usedPercent = null;
|
|
88
|
+
const percentUsed = monthly?.percentUsed;
|
|
89
|
+
if (typeof percentUsed === 'number') {
|
|
90
|
+
usedPercent = Math.max(0, Math.min(100, percentUsed * 100));
|
|
91
|
+
} else {
|
|
92
|
+
const used = toNumber(monthly?.used);
|
|
93
|
+
const limit = toNumber(monthly?.limit ?? monthly?.limits?.monthly);
|
|
94
|
+
if (used !== null && limit !== null && limit > 0) {
|
|
95
|
+
usedPercent = Math.max(0, Math.min(100, (used / limit) * 100));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const resetAt = toTimestamp(monthly?.resetAt ?? period?.currentPeriodEnd);
|
|
99
|
+
const valueLabel = state !== 'active' ? `(${state})` : null;
|
|
100
|
+
windows['monthly'] = toUsageWindow({
|
|
101
|
+
usedPercent,
|
|
102
|
+
windowSeconds: null,
|
|
103
|
+
resetAt,
|
|
104
|
+
valueLabel
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return buildResult({
|
|
109
|
+
providerId,
|
|
110
|
+
providerName,
|
|
111
|
+
ok: true,
|
|
112
|
+
configured: true,
|
|
113
|
+
usage: { windows }
|
|
114
|
+
});
|
|
115
|
+
} catch (error) {
|
|
116
|
+
return buildResult({
|
|
117
|
+
providerId,
|
|
118
|
+
providerName,
|
|
119
|
+
ok: false,
|
|
120
|
+
configured: true,
|
|
121
|
+
error: error instanceof Error ? error.message : 'Request failed'
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { readAuthFile } from '../../opencode-auth.js';
|
|
2
|
+
import {
|
|
3
|
+
getAuthEntry,
|
|
4
|
+
normalizeAuthEntry,
|
|
5
|
+
buildResult,
|
|
6
|
+
toUsageWindow,
|
|
7
|
+
toNumber,
|
|
8
|
+
toTimestamp
|
|
9
|
+
} from '../utils/index.js';
|
|
10
|
+
|
|
11
|
+
export const providerId = 'openai';
|
|
12
|
+
export const providerName = 'OpenAI';
|
|
13
|
+
export const aliases = ['openai', 'codex', 'chatgpt'];
|
|
14
|
+
|
|
15
|
+
export const isConfigured = () => {
|
|
16
|
+
const auth = readAuthFile();
|
|
17
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
18
|
+
return Boolean(entry?.access || entry?.token);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const fetchQuota = async () => {
|
|
22
|
+
const auth = readAuthFile();
|
|
23
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
24
|
+
const accessToken = entry?.access ?? entry?.token;
|
|
25
|
+
|
|
26
|
+
if (!accessToken) {
|
|
27
|
+
return buildResult({
|
|
28
|
+
providerId,
|
|
29
|
+
providerName,
|
|
30
|
+
ok: false,
|
|
31
|
+
configured: false,
|
|
32
|
+
error: 'Not configured'
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const response = await fetch('https://chatgpt.com/backend-api/wham/usage', {
|
|
38
|
+
method: 'GET',
|
|
39
|
+
headers: {
|
|
40
|
+
Authorization: `Bearer ${accessToken}`,
|
|
41
|
+
'Content-Type': 'application/json'
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
return buildResult({
|
|
47
|
+
providerId,
|
|
48
|
+
providerName,
|
|
49
|
+
ok: false,
|
|
50
|
+
configured: true,
|
|
51
|
+
error: `API error: ${response.status}`
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const payload = await response.json();
|
|
56
|
+
const primary = payload?.rate_limit?.primary_window ?? null;
|
|
57
|
+
const secondary = payload?.rate_limit?.secondary_window ?? null;
|
|
58
|
+
|
|
59
|
+
const windows = {};
|
|
60
|
+
if (primary) {
|
|
61
|
+
windows['5h'] = toUsageWindow({
|
|
62
|
+
usedPercent: primary.used_percent ?? null,
|
|
63
|
+
windowSeconds: primary.limit_window_seconds ?? null,
|
|
64
|
+
resetAt: primary.reset_at ? primary.reset_at * 1000 : null
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (secondary) {
|
|
68
|
+
windows['weekly'] = toUsageWindow({
|
|
69
|
+
usedPercent: secondary.used_percent ?? null,
|
|
70
|
+
windowSeconds: secondary.limit_window_seconds ?? null,
|
|
71
|
+
resetAt: secondary.reset_at ? secondary.reset_at * 1000 : null
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return buildResult({
|
|
76
|
+
providerId,
|
|
77
|
+
providerName,
|
|
78
|
+
ok: true,
|
|
79
|
+
configured: true,
|
|
80
|
+
usage: { windows }
|
|
81
|
+
});
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return buildResult({
|
|
84
|
+
providerId,
|
|
85
|
+
providerName,
|
|
86
|
+
ok: false,
|
|
87
|
+
configured: true,
|
|
88
|
+
error: error instanceof Error ? error.message : 'Request failed'
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { readAuthFile } from '../../opencode-auth.js';
|
|
2
|
+
import {
|
|
3
|
+
getAuthEntry,
|
|
4
|
+
normalizeAuthEntry,
|
|
5
|
+
buildResult,
|
|
6
|
+
toUsageWindow,
|
|
7
|
+
toNumber,
|
|
8
|
+
formatMoney
|
|
9
|
+
} from '../utils/index.js';
|
|
10
|
+
|
|
11
|
+
export const providerId = 'openrouter';
|
|
12
|
+
export const providerName = 'OpenRouter';
|
|
13
|
+
export const aliases = ['openrouter'];
|
|
14
|
+
|
|
15
|
+
export const isConfigured = () => {
|
|
16
|
+
const auth = readAuthFile();
|
|
17
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
18
|
+
return Boolean(entry?.key || entry?.token);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const fetchQuota = async () => {
|
|
22
|
+
const auth = readAuthFile();
|
|
23
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
24
|
+
const apiKey = entry?.key ?? entry?.token;
|
|
25
|
+
|
|
26
|
+
if (!apiKey) {
|
|
27
|
+
return buildResult({
|
|
28
|
+
providerId,
|
|
29
|
+
providerName,
|
|
30
|
+
ok: false,
|
|
31
|
+
configured: false,
|
|
32
|
+
error: 'Not configured'
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const response = await fetch('https://openrouter.ai/api/v1/credits', {
|
|
38
|
+
method: 'GET',
|
|
39
|
+
headers: {
|
|
40
|
+
Authorization: `Bearer ${apiKey}`,
|
|
41
|
+
'Content-Type': 'application/json'
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
return buildResult({
|
|
47
|
+
providerId,
|
|
48
|
+
providerName,
|
|
49
|
+
ok: false,
|
|
50
|
+
configured: true,
|
|
51
|
+
error: `API error: ${response.status}`
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const payload = await response.json();
|
|
56
|
+
const credits = payload?.data ?? {};
|
|
57
|
+
const totalCredits = toNumber(credits.total_credits);
|
|
58
|
+
const totalUsage = toNumber(credits.total_usage);
|
|
59
|
+
const remaining = totalCredits !== null && totalUsage !== null
|
|
60
|
+
? Math.max(0, totalCredits - totalUsage)
|
|
61
|
+
: null;
|
|
62
|
+
const usedPercent = totalCredits && totalUsage !== null
|
|
63
|
+
? Math.max(0, Math.min(100, (totalUsage / totalCredits) * 100))
|
|
64
|
+
: null;
|
|
65
|
+
const valueLabel = remaining !== null ? `$${formatMoney(remaining)} remaining` : null;
|
|
66
|
+
|
|
67
|
+
const windows = {
|
|
68
|
+
credits: toUsageWindow({
|
|
69
|
+
usedPercent,
|
|
70
|
+
windowSeconds: null,
|
|
71
|
+
resetAt: null,
|
|
72
|
+
valueLabel
|
|
73
|
+
})
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return buildResult({
|
|
77
|
+
providerId,
|
|
78
|
+
providerName,
|
|
79
|
+
ok: true,
|
|
80
|
+
configured: true,
|
|
81
|
+
usage: { windows }
|
|
82
|
+
});
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return buildResult({
|
|
85
|
+
providerId,
|
|
86
|
+
providerName,
|
|
87
|
+
ok: false,
|
|
88
|
+
configured: true,
|
|
89
|
+
error: error instanceof Error ? error.message : 'Request failed'
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { readAuthFile } from '../../opencode-auth.js';
|
|
2
|
+
import {
|
|
3
|
+
getAuthEntry,
|
|
4
|
+
normalizeAuthEntry,
|
|
5
|
+
buildResult,
|
|
6
|
+
toUsageWindow,
|
|
7
|
+
toNumber,
|
|
8
|
+
toTimestamp,
|
|
9
|
+
resolveWindowSeconds,
|
|
10
|
+
resolveWindowLabel,
|
|
11
|
+
normalizeTimestamp
|
|
12
|
+
} from '../utils/index.js';
|
|
13
|
+
|
|
14
|
+
export const providerId = 'zai-coding-plan';
|
|
15
|
+
export const providerName = 'z.ai';
|
|
16
|
+
export const aliases = ['zai-coding-plan', 'zai', 'z.ai'];
|
|
17
|
+
|
|
18
|
+
export const isConfigured = () => {
|
|
19
|
+
const auth = readAuthFile();
|
|
20
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
21
|
+
return Boolean(entry?.key || entry?.token);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const fetchQuota = async () => {
|
|
25
|
+
const auth = readAuthFile();
|
|
26
|
+
const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
|
|
27
|
+
const apiKey = entry?.key ?? entry?.token;
|
|
28
|
+
|
|
29
|
+
if (!apiKey) {
|
|
30
|
+
return buildResult({
|
|
31
|
+
providerId,
|
|
32
|
+
providerName,
|
|
33
|
+
ok: false,
|
|
34
|
+
configured: false,
|
|
35
|
+
error: 'Not configured'
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetch('https://api.z.ai/api/monitor/usage/quota/limit', {
|
|
41
|
+
method: 'GET',
|
|
42
|
+
headers: {
|
|
43
|
+
Authorization: `Bearer ${apiKey}`,
|
|
44
|
+
'Content-Type': 'application/json'
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
return buildResult({
|
|
50
|
+
providerId,
|
|
51
|
+
providerName,
|
|
52
|
+
ok: false,
|
|
53
|
+
configured: true,
|
|
54
|
+
error: `API error: ${response.status}`
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const payload = await response.json();
|
|
59
|
+
const limits = Array.isArray(payload?.data?.limits) ? payload.data.limits : [];
|
|
60
|
+
const tokensLimit = limits.find((limit) => limit?.type === 'TOKENS_LIMIT');
|
|
61
|
+
const windowSeconds = resolveWindowSeconds(tokensLimit);
|
|
62
|
+
const windowLabel = resolveWindowLabel(windowSeconds);
|
|
63
|
+
const resetAt = tokensLimit?.nextResetTime ? normalizeTimestamp(tokensLimit.nextResetTime) : null;
|
|
64
|
+
const usedPercent = typeof tokensLimit?.percentage === 'number' ? tokensLimit.percentage : null;
|
|
65
|
+
|
|
66
|
+
const windows = {};
|
|
67
|
+
if (tokensLimit) {
|
|
68
|
+
windows[windowLabel] = toUsageWindow({
|
|
69
|
+
usedPercent,
|
|
70
|
+
windowSeconds,
|
|
71
|
+
resetAt
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return buildResult({
|
|
76
|
+
providerId,
|
|
77
|
+
providerName,
|
|
78
|
+
ok: true,
|
|
79
|
+
configured: true,
|
|
80
|
+
usage: { windows }
|
|
81
|
+
});
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return buildResult({
|
|
84
|
+
providerId,
|
|
85
|
+
providerName,
|
|
86
|
+
ok: false,
|
|
87
|
+
configured: true,
|
|
88
|
+
error: error instanceof Error ? error.message : 'Request failed'
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
};
|