@hung319/opencode-iflow-cli 1.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 ADDED
@@ -0,0 +1,187 @@
1
+ # OpenCode iFlow CLI Plugin
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@hung319/opencode-iflow-cli)](https://www.npmjs.com/package/@hung319/opencode-iflow-cli)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@hung319/opencode-iflow-cli)](https://www.npmjs.com/package/@hung319/opencode-iflow-cli)
5
+ [![license](https://img.shields.io/npm/l/@hung319/opencode-iflow-cli)](https://www.npmjs.com/package/@hung319/opencode-iflow-cli)
6
+
7
+ OpenCode plugin for iFlow.cn providing access to Qwen, DeepSeek, Kimi, GLM, and iFlow ROME models with auto-configuration and headless OAuth support.
8
+
9
+ ## Features
10
+
11
+ - **Auto-configuration**: Models are automatically configured, no manual setup needed.
12
+ - **Dual authentication**: OAuth 2.0 (PKCE) and API Key support.
13
+ - **Headless support**: Works in SSH, containers, and CI environments with manual code input.
14
+ - **Multi-account rotation**: Sticky and round-robin strategies for account selection.
15
+ - **Automated token refresh** and rate limit handling with exponential backoff.
16
+ - **Native thinking mode support** for GLM-4.x and DeepSeek R1 models.
17
+ - **Flexible OAuth**: Automatic browser redirect OR manual code input - both work!
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install -g @hung319/opencode-iflow-cli
23
+ ```
24
+
25
+ Or add to your `opencode.json`:
26
+
27
+ ```json
28
+ {
29
+ "plugin": ["@hung319/opencode-iflow-cli"]
30
+ }
31
+ ```
32
+
33
+ That's it! Models are automatically configured. No manual provider configuration needed.
34
+
35
+ ## Quick Start
36
+
37
+ ### Interactive Mode (with browser)
38
+
39
+ ```bash
40
+ opencode auth login
41
+ # Select: Other → type "iflow" → Enter
42
+ # Choose: OAuth 2.0 or API Key
43
+ ```
44
+
45
+ Browser will open automatically. Complete authentication and you're done!
46
+
47
+ ### Headless Mode (SSH, CI, Containers)
48
+
49
+ ```bash
50
+ opencode auth login
51
+ # Select: Other → type "iflow" → Enter
52
+ # Choose: OAuth 2.0
53
+ ```
54
+
55
+ 1. Open the displayed URL in your local browser.
56
+ 2. Complete authentication on iFlow.cn.
57
+ 3. Copy the callback URL or authorization code.
58
+ 4. Paste it back into the terminal.
59
+
60
+ The plugin automatically detects headless environments and adapts accordingly.
61
+
62
+ ## Configuration
63
+
64
+ Optional configuration file at `~/.config/opencode/iflow.json`:
65
+
66
+ ```json
67
+ {
68
+ "default_auth_method": "oauth",
69
+ "account_selection_strategy": "round-robin",
70
+ "auth_server_port_start": 8087,
71
+ "auth_server_port_range": 10,
72
+ "max_request_iterations": 50,
73
+ "request_timeout_ms": 300000,
74
+ "enable_log_api_request": false
75
+ }
76
+ ```
77
+
78
+ ### Configuration Options
79
+
80
+ | Option | Description | Default |
81
+ |--------|-------------|---------|
82
+ | `default_auth_method` | Auth method (`oauth`, `apikey`) | `oauth` |
83
+ | `account_selection_strategy` | Rotation strategy (`sticky`, `round-robin`) | `round-robin` |
84
+ | `auth_server_port_start` | OAuth callback server starting port | `8087` |
85
+ | `auth_server_port_range` | Number of ports to try | `10` |
86
+ | `max_request_iterations` | Max iterations to prevent hangs | `50` |
87
+ | `request_timeout_ms` | Request timeout in milliseconds | `300000` |
88
+ | `enable_log_api_request` | Enable request/response logging | `false` |
89
+
90
+ ### Environment Variables
91
+
92
+ All config options can be overridden via environment variables:
93
+
94
+ ```bash
95
+ export IFLOW_DEFAULT_AUTH_METHOD=oauth
96
+ export IFLOW_ACCOUNT_SELECTION_STRATEGY=round-robin
97
+ export IFLOW_AUTH_SERVER_PORT_START=8087
98
+ export IFLOW_MAX_REQUEST_ITERATIONS=50
99
+ export IFLOW_REQUEST_TIMEOUT_MS=300000
100
+ ```
101
+
102
+ ## Supported Models
103
+
104
+ Models are automatically configured when you install the plugin:
105
+
106
+ | Model | Context | Output | Features |
107
+ |-------|---------|--------|----------|
108
+ | `iflow-rome-30ba3b` | 256K | 64K | iFlow ROME 30B |
109
+ | `qwen3-coder-plus` | 1M | 64K | Qwen3 Coder Plus |
110
+ | `qwen3-max` | 256K | 32K | Qwen3 Max |
111
+ | `qwen3-vl-plus` | 256K | 32K | Vision support |
112
+ | `qwen3-235b-a22b-thinking-2507` | 256K | 64K | Thinking mode |
113
+ | `kimi-k2` | 128K | 64K | Kimi K2 |
114
+ | `kimi-k2-0905` | 256K | 64K | Kimi K2 0905 |
115
+ | `glm-4.6` | 200K | 128K | Thinking + Vision |
116
+ | `deepseek-v3` | 128K | 32K | DeepSeek V3 |
117
+ | `deepseek-v3.2` | 128K | 64K | DeepSeek V3.2 |
118
+ | `deepseek-r1` | 128K | 32K | Reasoning model |
119
+ | `qwen3-32b` | 128K | 32K | Qwen3 32B |
120
+
121
+ ## Data Storage
122
+
123
+ **Linux/macOS:**
124
+ - Credentials: `~/.config/opencode/iflow-accounts.json`
125
+ - Config: `~/.config/opencode/iflow.json`
126
+
127
+ **Windows:**
128
+ - Credentials: `%APPDATA%\opencode\iflow-accounts.json`
129
+ - Config: `%APPDATA%\opencode\iflow.json`
130
+
131
+ ## Thinking Models
132
+
133
+ ### GLM-4.6
134
+
135
+ Variants with thinking budgets:
136
+
137
+ ```json
138
+ {
139
+ "model": "glm-4.6",
140
+ "variant": "medium"
141
+ }
142
+ ```
143
+
144
+ Available variants:
145
+ - `low`: 1024 thinking tokens
146
+ - `medium`: 8192 thinking tokens
147
+ - `max`: 32768 thinking tokens
148
+
149
+ ### DeepSeek R1
150
+
151
+ ```json
152
+ {
153
+ "model": "deepseek-r1",
154
+ "variant": "medium"
155
+ }
156
+ ```
157
+
158
+ Same variant options as GLM-4.6.
159
+
160
+ ## Headless Environment Detection
161
+
162
+ The plugin automatically detects headless environments via:
163
+ - `SSH_CONNECTION`, `SSH_CLIENT`, `SSH_TTY`
164
+ - `OPENCODE_HEADLESS`
165
+ - `CI`, `CONTAINER`
166
+ - Missing `DISPLAY` on Linux
167
+
168
+ In headless mode:
169
+ - OAuth URL is displayed for manual opening
170
+ - Browser auto-open is disabled
171
+ - Manual code input is prompted
172
+
173
+ ## Links
174
+
175
+ - **NPM Package**: https://www.npmjs.com/package/@hung319/opencode-iflow-cli
176
+ - **GitHub Repository**: https://github.com/hung319/opencode-iflow-cli
177
+ - **Issues**: https://github.com/hung319/opencode-iflow-cli/issues
178
+
179
+ ## License
180
+
181
+ MIT
182
+
183
+ ## Disclaimer
184
+
185
+ This plugin is provided strictly for learning and educational purposes. It is an independent implementation and is not affiliated with, endorsed by, or supported by iFlow.cn. Use of this plugin is at your own risk.
186
+
187
+ Feel free to open a PR to optimize this plugin further.
@@ -0,0 +1,19 @@
1
+ export type IFlowAuthMethod = 'oauth' | 'apikey';
2
+ export declare function isValidAuthMethod(method: string): method is IFlowAuthMethod;
3
+ export declare const IFLOW_CONSTANTS: {
4
+ BASE_URL: string;
5
+ OAUTH_TOKEN_URL: string;
6
+ OAUTH_AUTHORIZE_URL: string;
7
+ USER_INFO_URL: string;
8
+ SUCCESS_REDIRECT: string;
9
+ CLIENT_ID: string;
10
+ CLIENT_SECRET: string;
11
+ AXIOS_TIMEOUT: number;
12
+ USER_AGENT: string;
13
+ CALLBACK_PORT_START: number;
14
+ CALLBACK_PORT_RANGE: number;
15
+ };
16
+ export declare const SUPPORTED_MODELS: string[];
17
+ export declare const THINKING_MODELS: string[];
18
+ export declare function isThinkingModel(model: string): boolean;
19
+ export declare function applyThinkingConfig(body: any, model: string): any;
@@ -0,0 +1,61 @@
1
+ export function isValidAuthMethod(method) {
2
+ return method === 'oauth' || method === 'apikey';
3
+ }
4
+ export const IFLOW_CONSTANTS = {
5
+ BASE_URL: 'https://apis.iflow.cn/v1',
6
+ OAUTH_TOKEN_URL: 'https://iflow.cn/oauth/token',
7
+ OAUTH_AUTHORIZE_URL: 'https://iflow.cn/oauth',
8
+ USER_INFO_URL: 'https://iflow.cn/api/oauth/getUserInfo',
9
+ SUCCESS_REDIRECT: 'https://iflow.cn/oauth/success',
10
+ CLIENT_ID: '10009311001',
11
+ CLIENT_SECRET: '4Z3YjXycVsQvyGF1etiNlIBB4RsqSDtW',
12
+ AXIOS_TIMEOUT: 120000,
13
+ USER_AGENT: 'OpenCode-iFlow',
14
+ CALLBACK_PORT_START: 8087,
15
+ CALLBACK_PORT_RANGE: 10
16
+ };
17
+ export const SUPPORTED_MODELS = [
18
+ 'iflow-rome-30ba3b',
19
+ 'qwen3-coder-plus',
20
+ 'qwen3-max',
21
+ 'qwen3-vl-plus',
22
+ 'qwen3-max-preview',
23
+ 'qwen3-32b',
24
+ 'qwen3-235b-a22b-thinking-2507',
25
+ 'qwen3-235b-a22b-instruct',
26
+ 'qwen3-235b',
27
+ 'kimi-k2-0905',
28
+ 'kimi-k2',
29
+ 'glm-4.6',
30
+ 'deepseek-v3.2',
31
+ 'deepseek-r1',
32
+ 'deepseek-v3'
33
+ ];
34
+ export const THINKING_MODELS = ['glm-4.6', 'qwen3-235b-a22b-thinking-2507', 'deepseek-r1'];
35
+ export function isThinkingModel(model) {
36
+ return THINKING_MODELS.some((m) => model.startsWith(m));
37
+ }
38
+ export function applyThinkingConfig(body, model) {
39
+ const thinkingBudget = body.providerOptions?.thinkingConfig?.thinkingBudget;
40
+ if (model.startsWith('glm-4')) {
41
+ const result = {
42
+ ...body,
43
+ chat_template_kwargs: {
44
+ enable_thinking: true,
45
+ clear_thinking: false
46
+ }
47
+ };
48
+ if (thinkingBudget) {
49
+ result.thinking_budget = thinkingBudget;
50
+ }
51
+ return result;
52
+ }
53
+ if (model.startsWith('deepseek-r1')) {
54
+ const result = { ...body };
55
+ if (thinkingBudget) {
56
+ result.thinking_budget = thinkingBudget;
57
+ }
58
+ return result;
59
+ }
60
+ return body;
61
+ }
@@ -0,0 +1,6 @@
1
+ export interface IFlowApiKeyResult {
2
+ apiKey: string;
3
+ email: string;
4
+ authMethod: 'apikey';
5
+ }
6
+ export declare function validateApiKey(apiKey: string): Promise<IFlowApiKeyResult>;
@@ -0,0 +1,17 @@
1
+ import { IFLOW_CONSTANTS } from '../constants';
2
+ export async function validateApiKey(apiKey) {
3
+ const response = await fetch(`${IFLOW_CONSTANTS.BASE_URL}/models`, {
4
+ headers: {
5
+ Authorization: `Bearer ${apiKey}`,
6
+ 'User-Agent': IFLOW_CONSTANTS.USER_AGENT
7
+ }
8
+ });
9
+ if (!response.ok) {
10
+ throw new Error(`API key validation failed: ${response.status}`);
11
+ }
12
+ return {
13
+ apiKey,
14
+ email: 'api-key-user',
15
+ authMethod: 'apikey'
16
+ };
17
+ }
@@ -0,0 +1,20 @@
1
+ export interface IFlowOAuthAuthorization {
2
+ authUrl: string;
3
+ state: string;
4
+ redirectUri: string;
5
+ }
6
+ export interface IFlowOAuthTokenResult {
7
+ accessToken: string;
8
+ refreshToken: string;
9
+ expiresAt: number;
10
+ apiKey: string;
11
+ email: string;
12
+ authMethod: 'oauth';
13
+ }
14
+ export declare function authorizeIFlowOAuth(port: number): Promise<IFlowOAuthAuthorization>;
15
+ export declare function exchangeOAuthCode(code: string, redirectUri: string): Promise<IFlowOAuthTokenResult>;
16
+ export declare function refreshOAuthToken(refreshToken: string): Promise<IFlowOAuthTokenResult>;
17
+ export declare function fetchUserInfo(accessToken: string): Promise<{
18
+ apiKey: string;
19
+ email: string;
20
+ }>;
@@ -0,0 +1,113 @@
1
+ import { IFLOW_CONSTANTS } from '../constants';
2
+ import { randomBytes } from 'node:crypto';
3
+ function base64URLEncode(buffer) {
4
+ return buffer.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
5
+ }
6
+ function generateState() {
7
+ return base64URLEncode(randomBytes(16));
8
+ }
9
+ export async function authorizeIFlowOAuth(port) {
10
+ const state = generateState();
11
+ const redirectUri = `http://localhost:${port}/oauth2callback`;
12
+ const params = new URLSearchParams({
13
+ loginMethod: 'phone',
14
+ type: 'phone',
15
+ redirect: redirectUri,
16
+ state,
17
+ client_id: IFLOW_CONSTANTS.CLIENT_ID
18
+ });
19
+ const authUrl = `${IFLOW_CONSTANTS.OAUTH_AUTHORIZE_URL}?${params.toString()}`;
20
+ return { authUrl, state, redirectUri };
21
+ }
22
+ export async function exchangeOAuthCode(code, redirectUri) {
23
+ const params = new URLSearchParams({
24
+ grant_type: 'authorization_code',
25
+ code,
26
+ redirect_uri: redirectUri,
27
+ client_id: IFLOW_CONSTANTS.CLIENT_ID,
28
+ client_secret: IFLOW_CONSTANTS.CLIENT_SECRET
29
+ });
30
+ const basicAuth = Buffer.from(`${IFLOW_CONSTANTS.CLIENT_ID}:${IFLOW_CONSTANTS.CLIENT_SECRET}`).toString('base64');
31
+ const response = await fetch(IFLOW_CONSTANTS.OAUTH_TOKEN_URL, {
32
+ method: 'POST',
33
+ headers: {
34
+ 'Content-Type': 'application/x-www-form-urlencoded',
35
+ Accept: 'application/json',
36
+ Authorization: `Basic ${basicAuth}`
37
+ },
38
+ body: params.toString()
39
+ });
40
+ if (!response.ok) {
41
+ const errorText = await response.text().catch(() => '');
42
+ throw new Error(`Token exchange failed: ${response.status} ${errorText}`);
43
+ }
44
+ const data = await response.json();
45
+ const userInfo = await fetchUserInfo(data.access_token);
46
+ const expiresIn = data.expires_in || 3600;
47
+ const expiresAt = Date.now() + expiresIn * 1000;
48
+ return {
49
+ accessToken: data.access_token,
50
+ refreshToken: data.refresh_token,
51
+ expiresAt,
52
+ apiKey: userInfo.apiKey,
53
+ email: userInfo.email,
54
+ authMethod: 'oauth'
55
+ };
56
+ }
57
+ export async function refreshOAuthToken(refreshToken) {
58
+ const params = new URLSearchParams({
59
+ grant_type: 'refresh_token',
60
+ refresh_token: refreshToken,
61
+ client_id: IFLOW_CONSTANTS.CLIENT_ID,
62
+ client_secret: IFLOW_CONSTANTS.CLIENT_SECRET
63
+ });
64
+ const basicAuth = Buffer.from(`${IFLOW_CONSTANTS.CLIENT_ID}:${IFLOW_CONSTANTS.CLIENT_SECRET}`).toString('base64');
65
+ const response = await fetch(IFLOW_CONSTANTS.OAUTH_TOKEN_URL, {
66
+ method: 'POST',
67
+ headers: {
68
+ 'Content-Type': 'application/x-www-form-urlencoded',
69
+ Accept: 'application/json',
70
+ Authorization: `Basic ${basicAuth}`
71
+ },
72
+ body: params.toString()
73
+ });
74
+ if (!response.ok) {
75
+ const errorText = await response.text().catch(() => '');
76
+ throw new Error(`Token refresh failed: ${response.status} ${errorText}`);
77
+ }
78
+ const data = await response.json();
79
+ const userInfo = await fetchUserInfo(data.access_token);
80
+ const expiresIn = data.expires_in || 3600;
81
+ const expiresAt = Date.now() + expiresIn * 1000;
82
+ return {
83
+ accessToken: data.access_token,
84
+ refreshToken: data.refresh_token || refreshToken,
85
+ expiresAt,
86
+ apiKey: userInfo.apiKey,
87
+ email: userInfo.email,
88
+ authMethod: 'oauth'
89
+ };
90
+ }
91
+ export async function fetchUserInfo(accessToken) {
92
+ const response = await fetch(`${IFLOW_CONSTANTS.USER_INFO_URL}?accessToken=${encodeURIComponent(accessToken)}`, {
93
+ headers: {
94
+ Accept: 'application/json'
95
+ }
96
+ });
97
+ if (!response.ok) {
98
+ const errorText = await response.text().catch(() => '');
99
+ throw new Error(`User info fetch failed: ${response.status} ${errorText}`);
100
+ }
101
+ const data = await response.json();
102
+ if (!data.success || !data.data) {
103
+ throw new Error('User info request not successful');
104
+ }
105
+ if (!data.data.apiKey) {
106
+ throw new Error('Missing apiKey in user info response');
107
+ }
108
+ const email = data.data.email || data.data.phone || 'oauth-user';
109
+ return {
110
+ apiKey: data.data.apiKey,
111
+ email
112
+ };
113
+ }
@@ -0,0 +1,3 @@
1
+ export { IFlowOAuthPlugin } from './plugin.js';
2
+ export type { IFlowConfig } from './plugin/config/index.js';
3
+ export type { IFlowAuthMethod, ManagedAccount } from './plugin/types.js';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { IFlowOAuthPlugin } from './plugin.js';
@@ -0,0 +1,24 @@
1
+ import type { ManagedAccount, AccountSelectionStrategy, IFlowAuthDetails, RefreshParts } from './types';
2
+ export declare function generateAccountId(): string;
3
+ export declare function encodeRefreshToken(parts: RefreshParts): string;
4
+ export declare function decodeRefreshToken(encoded: string): RefreshParts;
5
+ export declare class AccountManager {
6
+ private accounts;
7
+ private cursor;
8
+ private strategy;
9
+ private lastToastTime;
10
+ constructor(accounts: ManagedAccount[], strategy?: AccountSelectionStrategy);
11
+ static loadFromDisk(strategy?: AccountSelectionStrategy): Promise<AccountManager>;
12
+ getAccountCount(): number;
13
+ getAccounts(): ManagedAccount[];
14
+ shouldShowToast(debounce?: number): boolean;
15
+ getMinWaitTime(): number;
16
+ getCurrentOrNext(): ManagedAccount | null;
17
+ addAccount(a: ManagedAccount): void;
18
+ removeAccount(a: ManagedAccount): void;
19
+ updateFromAuth(a: ManagedAccount, auth: IFlowAuthDetails): void;
20
+ markRateLimited(a: ManagedAccount, ms: number): void;
21
+ markUnhealthy(a: ManagedAccount, reason: string, recovery?: number): void;
22
+ saveToDisk(): Promise<void>;
23
+ toAuthDetails(a: ManagedAccount): IFlowAuthDetails;
24
+ }
@@ -0,0 +1,147 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { loadAccounts, saveAccounts } from './storage';
3
+ export function generateAccountId() {
4
+ return randomBytes(16).toString('hex');
5
+ }
6
+ export function encodeRefreshToken(parts) {
7
+ return Buffer.from(JSON.stringify(parts)).toString('base64');
8
+ }
9
+ export function decodeRefreshToken(encoded) {
10
+ try {
11
+ return JSON.parse(Buffer.from(encoded, 'base64').toString('utf-8'));
12
+ }
13
+ catch {
14
+ return { authMethod: 'apikey' };
15
+ }
16
+ }
17
+ export class AccountManager {
18
+ accounts;
19
+ cursor;
20
+ strategy;
21
+ lastToastTime = 0;
22
+ constructor(accounts, strategy = 'sticky') {
23
+ this.accounts = accounts;
24
+ this.cursor = 0;
25
+ this.strategy = strategy;
26
+ }
27
+ static async loadFromDisk(strategy) {
28
+ const s = await loadAccounts();
29
+ return new AccountManager(s.accounts, strategy || 'sticky');
30
+ }
31
+ getAccountCount() {
32
+ return this.accounts.length;
33
+ }
34
+ getAccounts() {
35
+ return [...this.accounts];
36
+ }
37
+ shouldShowToast(debounce = 30000) {
38
+ if (Date.now() - this.lastToastTime < debounce)
39
+ return false;
40
+ this.lastToastTime = Date.now();
41
+ return true;
42
+ }
43
+ getMinWaitTime() {
44
+ const now = Date.now();
45
+ const waits = this.accounts.map((a) => (a.rateLimitResetTime || 0) - now).filter((t) => t > 0);
46
+ return waits.length > 0 ? Math.min(...waits) : 0;
47
+ }
48
+ getCurrentOrNext() {
49
+ const now = Date.now();
50
+ const available = this.accounts.filter((a) => {
51
+ if (!a.isHealthy) {
52
+ if (a.recoveryTime && now >= a.recoveryTime) {
53
+ a.isHealthy = true;
54
+ delete a.unhealthyReason;
55
+ delete a.recoveryTime;
56
+ return true;
57
+ }
58
+ return false;
59
+ }
60
+ return !(a.rateLimitResetTime && now < a.rateLimitResetTime);
61
+ });
62
+ if (available.length === 0)
63
+ return null;
64
+ let selected;
65
+ if (this.strategy === 'sticky') {
66
+ selected = available.find((_, i) => i === this.cursor) || available[0];
67
+ }
68
+ else if (this.strategy === 'round-robin') {
69
+ selected = available[this.cursor % available.length];
70
+ this.cursor = (this.cursor + 1) % available.length;
71
+ }
72
+ if (selected) {
73
+ selected.lastUsed = now;
74
+ this.cursor = this.accounts.indexOf(selected);
75
+ return selected;
76
+ }
77
+ return null;
78
+ }
79
+ addAccount(a) {
80
+ const i = this.accounts.findIndex((x) => x.id === a.id);
81
+ if (i === -1)
82
+ this.accounts.push(a);
83
+ else
84
+ this.accounts[i] = a;
85
+ }
86
+ removeAccount(a) {
87
+ const removedIndex = this.accounts.findIndex((x) => x.id === a.id);
88
+ if (removedIndex === -1)
89
+ return;
90
+ this.accounts = this.accounts.filter((x) => x.id !== a.id);
91
+ if (this.accounts.length === 0) {
92
+ this.cursor = 0;
93
+ }
94
+ else if (this.cursor >= this.accounts.length) {
95
+ this.cursor = this.accounts.length - 1;
96
+ }
97
+ else if (removedIndex <= this.cursor && this.cursor > 0) {
98
+ this.cursor--;
99
+ }
100
+ }
101
+ updateFromAuth(a, auth) {
102
+ const acc = this.accounts.find((x) => x.id === a.id);
103
+ if (acc) {
104
+ acc.apiKey = auth.apiKey;
105
+ if (auth.authMethod === 'oauth') {
106
+ acc.accessToken = auth.access;
107
+ acc.expiresAt = auth.expires;
108
+ const p = decodeRefreshToken(auth.refresh);
109
+ acc.refreshToken = p.refreshToken;
110
+ }
111
+ acc.lastUsed = Date.now();
112
+ if (auth.email)
113
+ acc.email = auth.email;
114
+ }
115
+ }
116
+ markRateLimited(a, ms) {
117
+ const acc = this.accounts.find((x) => x.id === a.id);
118
+ if (acc)
119
+ acc.rateLimitResetTime = Date.now() + ms;
120
+ }
121
+ markUnhealthy(a, reason, recovery) {
122
+ const acc = this.accounts.find((x) => x.id === a.id);
123
+ if (acc) {
124
+ acc.isHealthy = false;
125
+ acc.unhealthyReason = reason;
126
+ acc.recoveryTime = recovery;
127
+ }
128
+ }
129
+ async saveToDisk() {
130
+ const metadata = this.accounts.map(({ lastUsed, ...rest }) => rest);
131
+ await saveAccounts({ version: 1, accounts: metadata, activeIndex: this.cursor });
132
+ }
133
+ toAuthDetails(a) {
134
+ const p = {
135
+ refreshToken: a.refreshToken,
136
+ authMethod: a.authMethod
137
+ };
138
+ return {
139
+ refresh: encodeRefreshToken(p),
140
+ access: a.accessToken || '',
141
+ expires: a.expiresAt || 0,
142
+ authMethod: a.authMethod,
143
+ apiKey: a.apiKey,
144
+ email: a.email
145
+ };
146
+ }
147
+ }
@@ -0,0 +1,3 @@
1
+ export declare function getIDCAuthHtml(verificationUrl: string, userCode: string, statusUrl: string): string;
2
+ export declare function getSuccessHtml(): string;
3
+ export declare function getErrorHtml(message: string): string;