opencode-multi-account-core 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,6 +15,7 @@ Shared core logic for multi-account OpenCode plugins. This package contains ~70%
15
15
  | ProactiveRefreshQueue | Background token refresh before expiry. Created via `createProactiveRefreshForProvider`. |
16
16
  | Config | Plugin configuration loading and validation with valibot. Created via `createConfigLoaderForProvider`. |
17
17
  | AuthMigration | One-time import of existing single-account OAuth creds from OpenCode's `auth.json`. |
18
+ | Adapters | Provider-specific OAuth adapter definitions (endpoints, client IDs, plan labels). |
18
19
  | UI | Terminal UI primitives (ANSI formatting, confirm dialogs, select menus). |
19
20
  | Utils | Config directory resolution, formatting helpers. |
20
21
 
@@ -41,10 +42,8 @@ export const AccountManager = createAccountManagerForProvider({
41
42
  │ multi-account-core ← you are here │
42
43
  │ AccountStore . AccountManager . Executor │
43
44
  │ Claims . Storage . RateLimit . ProactiveRefresh │
44
- │ AuthMigration . Config . Utils . UI
45
- ├─────────────────────────────────────────────────┤
46
- │ oauth-adapters │
47
- │ (endpoints, client IDs, plan labels) │
45
+ │ AuthMigration . Config . Utils . UI . Adapters
46
+ │ (endpoints, client IDs, plan labels) │
48
47
  └─────────────────────────────────────────────────┘
49
48
  ```
50
49
 
@@ -0,0 +1,47 @@
1
+ import type { AccountStore } from "./account-store";
2
+ import type { ManagedAccount, OAuthCredentials, PluginClient, TokenRefreshResult, UsageLimits } from "./types";
3
+ export interface ProfileData {
4
+ email?: string;
5
+ planTier: string;
6
+ }
7
+ export interface RuntimeFactoryLike {
8
+ invalidate(uuid: string): void;
9
+ }
10
+ export interface AccountManagerDependencies {
11
+ providerAuthId: string;
12
+ isTokenExpired: (account: Pick<ManagedAccount, "accessToken" | "expiresAt">) => boolean;
13
+ refreshToken: (currentRefreshToken: string, accountId: string, client: PluginClient) => Promise<TokenRefreshResult>;
14
+ }
15
+ export interface AccountManagerInstance {
16
+ initialize(currentAuth: OAuthCredentials, client?: PluginClient): Promise<void>;
17
+ refresh(): Promise<void>;
18
+ getAccountCount(): number;
19
+ getAccounts(): ManagedAccount[];
20
+ getActiveAccount(): ManagedAccount | null;
21
+ setClient(client: PluginClient): void;
22
+ setRuntimeFactory(factory: RuntimeFactoryLike): void;
23
+ hasAnyUsableAccount(): boolean;
24
+ isRateLimited(account: ManagedAccount): boolean;
25
+ clearExpiredRateLimits(): void;
26
+ getMinWaitTime(): number;
27
+ selectAccount(): Promise<ManagedAccount | null>;
28
+ markRateLimited(uuid: string, backoffMs?: number): Promise<void>;
29
+ markRevoked(uuid: string): Promise<void>;
30
+ markSuccess(uuid: string): Promise<void>;
31
+ markAuthFailure(uuid: string, result: TokenRefreshResult): Promise<void>;
32
+ applyUsageCache(uuid: string, usage: UsageLimits): Promise<void>;
33
+ applyProfileCache(uuid: string, profile: ProfileData): Promise<void>;
34
+ ensureValidToken(uuid: string, client: PluginClient): Promise<TokenRefreshResult>;
35
+ validateNonActiveTokens(client: PluginClient): Promise<void>;
36
+ removeAccount(index: number): Promise<boolean>;
37
+ clearAllAccounts(): Promise<void>;
38
+ addAccount(auth: OAuthCredentials): Promise<void>;
39
+ toggleEnabled(uuid: string): Promise<void>;
40
+ replaceAccountCredentials(uuid: string, auth: OAuthCredentials): Promise<void>;
41
+ retryAuth(uuid: string, client: PluginClient): Promise<TokenRefreshResult>;
42
+ }
43
+ export interface AccountManagerClass {
44
+ new (store: AccountStore): AccountManagerInstance;
45
+ create(store: AccountStore, currentAuth: OAuthCredentials, client?: PluginClient): Promise<AccountManagerInstance>;
46
+ }
47
+ export declare function createAccountManagerForProvider(dependencies: AccountManagerDependencies): AccountManagerClass;
@@ -0,0 +1,17 @@
1
+ import type { AccountStorage, StoredAccount } from "./types";
2
+ export interface DiskCredentials {
3
+ refreshToken: string;
4
+ accessToken?: string;
5
+ expiresAt?: number;
6
+ accountId?: string;
7
+ }
8
+ export declare class AccountStore {
9
+ load(): Promise<AccountStorage>;
10
+ readCredentials(uuid: string): Promise<DiskCredentials | null>;
11
+ mutateAccount(uuid: string, fn: (account: StoredAccount) => void): Promise<StoredAccount | null>;
12
+ mutateStorage(fn: (storage: AccountStorage) => void): Promise<void>;
13
+ addAccount(account: StoredAccount): Promise<void>;
14
+ removeAccount(uuid: string): Promise<boolean>;
15
+ setActiveUuid(uuid: string | undefined): Promise<void>;
16
+ clear(): Promise<void>;
17
+ }
@@ -0,0 +1,2 @@
1
+ import type { OAuthAdapter } from "./types";
2
+ export declare const anthropicOAuthAdapter: OAuthAdapter;
@@ -0,0 +1,3 @@
1
+ export type { OAuthAdapter, OAuthAdapterTransformConfig, OAuthAdapterPlanLabels } from "./types";
2
+ export { anthropicOAuthAdapter } from "./anthropic";
3
+ export { openAIOAuthAdapter } from "./openai";
@@ -0,0 +1,2 @@
1
+ import type { OAuthAdapter } from "./types";
2
+ export declare const openAIOAuthAdapter: OAuthAdapter;
@@ -0,0 +1,28 @@
1
+ export interface OAuthAdapterTransformConfig {
2
+ rewriteOpenCodeBranding: boolean;
3
+ addToolPrefix: boolean;
4
+ stripToolPrefixInResponse: boolean;
5
+ enableMessagesBetaQuery: boolean;
6
+ }
7
+ export type OAuthAdapterPlanLabels = Record<string, string>;
8
+ export interface OAuthAdapter {
9
+ id: string;
10
+ authProviderId: string;
11
+ modelDisplayName: string;
12
+ statusToolName: string;
13
+ authMethodLabel: string;
14
+ serviceLogName: string;
15
+ oauthClientId: string;
16
+ tokenEndpoint: string;
17
+ usageEndpoint: string;
18
+ profileEndpoint: string;
19
+ oauthBetaHeader: string;
20
+ requestBetaHeader: string;
21
+ cliUserAgent: string;
22
+ toolPrefix: string;
23
+ accountStorageFilename: string;
24
+ transform: OAuthAdapterTransformConfig;
25
+ planLabels: OAuthAdapterPlanLabels;
26
+ supported: boolean;
27
+ unsupportedReason?: string;
28
+ }
@@ -0,0 +1,12 @@
1
+ import type { AccountStore } from "./account-store";
2
+ /**
3
+ * Imports an existing OAuth credential from OpenCode's auth.json
4
+ * into the multi-account storage on first use.
5
+ *
6
+ * Only runs when storage has zero accounts. Does not modify auth.json.
7
+ *
8
+ * @param providerKey - The key in auth.json ("anthropic" or "openai")
9
+ * @param store - The AccountStore instance to import into
10
+ * @returns true if a credential was imported, false otherwise
11
+ */
12
+ export declare function migrateFromAuthJson(providerKey: string, store: AccountStore): Promise<boolean>;
@@ -0,0 +1,8 @@
1
+ export type ClaimsMap = Record<string, {
2
+ pid: number;
3
+ at: number;
4
+ }>;
5
+ export declare function readClaims(): Promise<ClaimsMap>;
6
+ export declare function writeClaim(accountId: string): Promise<void>;
7
+ export declare function releaseClaim(accountId: string): Promise<void>;
8
+ export declare function isClaimedByOther(claims: ClaimsMap, accountId: string | undefined): boolean;
@@ -0,0 +1,8 @@
1
+ import type { PluginConfig } from "./types";
2
+ export type CoreConfig = Pick<PluginConfig, "quiet_mode" | "debug">;
3
+ export declare function initCoreConfig(filename: string): void;
4
+ export declare function loadConfig(): Promise<PluginConfig>;
5
+ export declare function getConfig(): PluginConfig;
6
+ export declare function resetConfigCache(): void;
7
+ export declare function setConfigGetter(getter: () => PluginConfig): void;
8
+ export declare function updateConfigField<K extends keyof PluginConfig>(key: K, value: PluginConfig[K]): Promise<void>;
@@ -0,0 +1,2 @@
1
+ export declare let ACCOUNTS_FILENAME: string;
2
+ export declare function setAccountsFilename(filename: string): void;
@@ -0,0 +1,27 @@
1
+ import type { ManagedAccount, PluginClient, TokenRefreshResult } from "./types";
2
+ export interface ExecutorAccountManager {
3
+ getAccountCount(): number;
4
+ refresh(): Promise<void>;
5
+ selectAccount(): Promise<ManagedAccount | null>;
6
+ markSuccess(uuid: string): Promise<void>;
7
+ markAuthFailure(uuid: string, result: TokenRefreshResult): Promise<void>;
8
+ markRevoked(uuid: string): Promise<void>;
9
+ hasAnyUsableAccount(): boolean;
10
+ getMinWaitTime(): number;
11
+ }
12
+ export interface ExecutorRuntimeFactory {
13
+ getRuntime(uuid: string): Promise<{
14
+ fetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
15
+ }>;
16
+ invalidate(uuid: string): void;
17
+ }
18
+ export interface ExecutorDependencies {
19
+ handleRateLimitResponse: (manager: unknown, client: PluginClient, account: ManagedAccount, response: Response) => Promise<void>;
20
+ formatWaitTime: (ms: number) => string;
21
+ sleep: (ms: number) => Promise<void>;
22
+ showToast: (client: PluginClient, message: string, variant: "info" | "warning" | "success" | "error") => Promise<void>;
23
+ getAccountLabel: (account: ManagedAccount) => string;
24
+ }
25
+ export declare function createExecutorForProvider(providerName: string, dependencies: ExecutorDependencies): {
26
+ executeWithAccountRotation: (manager: ExecutorAccountManager, runtimeFactory: ExecutorRuntimeFactory, client: PluginClient, input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
27
+ };
@@ -0,0 +1,16 @@
1
+ export * from "./account-manager";
2
+ export * from "./account-store";
3
+ export * from "./claims";
4
+ export * from "./config";
5
+ export * from "./constants";
6
+ export * from "./executor";
7
+ export * from "./proactive-refresh";
8
+ export * from "./rate-limit";
9
+ export * from "./storage";
10
+ export * from "./types";
11
+ export * from "./utils";
12
+ export * from "./auth-migration";
13
+ export * from "./ui/ansi";
14
+ export * from "./ui/confirm";
15
+ export * from "./ui/select";
16
+ export * from "./adapters";
package/dist/index.js CHANGED
@@ -1752,6 +1752,70 @@ async function confirm(message, defaultYes = false) {
1752
1752
  const result = await select(items, { message });
1753
1753
  return result ?? false;
1754
1754
  }
1755
+
1756
+ // src/adapters/anthropic.ts
1757
+ var anthropicOAuthAdapter = {
1758
+ id: "anthropic",
1759
+ authProviderId: "anthropic",
1760
+ modelDisplayName: "Claude",
1761
+ statusToolName: "claude_multiauth_status",
1762
+ authMethodLabel: "Claude Pro/Max (Multi-Auth)",
1763
+ serviceLogName: "claude-multiauth",
1764
+ oauthClientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
1765
+ tokenEndpoint: "https://console.anthropic.com/v1/oauth/token",
1766
+ usageEndpoint: "https://api.anthropic.com/api/oauth/usage",
1767
+ profileEndpoint: "https://api.anthropic.com/api/oauth/profile",
1768
+ oauthBetaHeader: "oauth-2025-04-20",
1769
+ requestBetaHeader: "oauth-2025-04-20,interleaved-thinking-2025-05-14",
1770
+ cliUserAgent: "claude-cli/2.1.2 (external, cli)",
1771
+ toolPrefix: "mcp_",
1772
+ accountStorageFilename: "anthropic-multi-account-accounts.json",
1773
+ transform: {
1774
+ rewriteOpenCodeBranding: true,
1775
+ addToolPrefix: true,
1776
+ stripToolPrefixInResponse: true,
1777
+ enableMessagesBetaQuery: true
1778
+ },
1779
+ planLabels: {
1780
+ max: "Claude Max",
1781
+ pro: "Claude Pro",
1782
+ free: "Free"
1783
+ },
1784
+ supported: true
1785
+ };
1786
+
1787
+ // src/adapters/openai.ts
1788
+ var ISSUER = "https://auth.openai.com";
1789
+ var openAIOAuthAdapter = {
1790
+ id: "openai",
1791
+ authProviderId: "openai",
1792
+ modelDisplayName: "ChatGPT",
1793
+ statusToolName: "chatgpt_multiauth_status",
1794
+ authMethodLabel: "ChatGPT Plus/Pro (Multi-Auth)",
1795
+ serviceLogName: "chatgpt-multiauth",
1796
+ oauthClientId: "app_EMoamEEZ73f0CkXaXp7hrann",
1797
+ tokenEndpoint: `${ISSUER}/oauth/token`,
1798
+ usageEndpoint: "",
1799
+ profileEndpoint: "",
1800
+ oauthBetaHeader: "",
1801
+ requestBetaHeader: "",
1802
+ cliUserAgent: "opencode/1.1.53",
1803
+ toolPrefix: "mcp_",
1804
+ accountStorageFilename: "openai-multi-account-accounts.json",
1805
+ transform: {
1806
+ rewriteOpenCodeBranding: false,
1807
+ addToolPrefix: false,
1808
+ stripToolPrefixInResponse: false,
1809
+ enableMessagesBetaQuery: false
1810
+ },
1811
+ planLabels: {
1812
+ pro: "ChatGPT Pro",
1813
+ plus: "ChatGPT Plus",
1814
+ go: "ChatGPT Go",
1815
+ free: "Free"
1816
+ },
1817
+ supported: true
1818
+ };
1755
1819
  export {
1756
1820
  ACCOUNTS_FILENAME,
1757
1821
  ANSI,
@@ -1764,6 +1828,7 @@ export {
1764
1828
  StoredAccountSchema,
1765
1829
  UsageLimitEntrySchema,
1766
1830
  UsageLimitsSchema,
1831
+ anthropicOAuthAdapter,
1767
1832
  confirm,
1768
1833
  createAccountManagerForProvider,
1769
1834
  createExecutorForProvider,
@@ -1783,6 +1848,7 @@ export {
1783
1848
  loadAccounts,
1784
1849
  loadConfig,
1785
1850
  migrateFromAuthJson,
1851
+ openAIOAuthAdapter,
1786
1852
  parseKey,
1787
1853
  readClaims,
1788
1854
  readStorageFromDisk,
@@ -0,0 +1,16 @@
1
+ import { AccountStore } from "./account-store";
2
+ import type { PluginClient, PluginConfig, StoredAccount, TokenRefreshResult } from "./types";
3
+ export interface ProactiveRefreshDependencies {
4
+ getConfig: () => PluginConfig;
5
+ refreshToken: (currentRefreshToken: string, accountId: string, client: PluginClient) => Promise<TokenRefreshResult>;
6
+ isTokenExpired: (account: Pick<StoredAccount, "accessToken" | "expiresAt">) => boolean;
7
+ debugLog: (client: PluginClient, message: string, extra?: Record<string, unknown>) => void;
8
+ }
9
+ export interface ProactiveRefreshQueueInstance {
10
+ start(): void;
11
+ stop(): Promise<void>;
12
+ }
13
+ export interface ProactiveRefreshQueueClass {
14
+ new (client: PluginClient, store: AccountStore, onInvalidate?: (uuid: string) => void): ProactiveRefreshQueueInstance;
15
+ }
16
+ export declare function createProactiveRefreshQueueForProvider(dependencies: ProactiveRefreshDependencies): ProactiveRefreshQueueClass;
@@ -0,0 +1,25 @@
1
+ import type { ManagedAccount, PluginClient, PluginConfig, UsageLimits } from "./types";
2
+ export interface RateLimitDependencies {
3
+ fetchUsage: (accessToken: string, accountId?: string) => Promise<{
4
+ ok: true;
5
+ data: UsageLimits;
6
+ } | {
7
+ ok: false;
8
+ reason: string;
9
+ }>;
10
+ getConfig: () => Pick<PluginConfig, "default_retry_after_ms">;
11
+ formatWaitTime: (ms: number) => string;
12
+ getAccountLabel: (account: ManagedAccount) => string;
13
+ showToast: (client: PluginClient, message: string, variant: "info" | "warning" | "success" | "error") => Promise<void>;
14
+ }
15
+ export interface RateLimitAccountManager {
16
+ markRateLimited(uuid: string, backoffMs?: number): Promise<void>;
17
+ applyUsageCache(uuid: string, usage: UsageLimits): Promise<void>;
18
+ getAccountCount(): number;
19
+ }
20
+ export declare function createRateLimitHandlers(dependencies: RateLimitDependencies): {
21
+ retryAfterMsFromResponse: (response: Response) => number;
22
+ getResetMsFromUsage: (account: ManagedAccount) => number | null;
23
+ fetchUsageLimits: (accessToken: string, accountId?: string) => Promise<UsageLimits | null>;
24
+ handleRateLimitResponse: (manager: RateLimitAccountManager, client: PluginClient, account: ManagedAccount, response: Response) => Promise<void>;
25
+ };
@@ -0,0 +1,4 @@
1
+ import type { AccountStorage, StoredAccount } from "./types";
2
+ export declare function readStorageFromDisk(targetPath: string, backupOnCorrupt: boolean): Promise<AccountStorage | null>;
3
+ export declare function deduplicateAccounts(accounts: StoredAccount[]): StoredAccount[];
4
+ export declare function loadAccounts(): Promise<AccountStorage | null>;
@@ -0,0 +1,187 @@
1
+ import * as v from "valibot";
2
+ export declare const OAuthCredentialsSchema: v.ObjectSchema<{
3
+ readonly type: v.LiteralSchema<"oauth", undefined>;
4
+ readonly refresh: v.StringSchema<undefined>;
5
+ readonly access: v.StringSchema<undefined>;
6
+ readonly expires: v.NumberSchema<undefined>;
7
+ }, undefined>;
8
+ export declare const UsageLimitEntrySchema: v.ObjectSchema<{
9
+ readonly utilization: v.NumberSchema<undefined>;
10
+ readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
11
+ }, undefined>;
12
+ export declare const UsageLimitsSchema: v.ObjectSchema<{
13
+ readonly five_hour: v.OptionalSchema<v.NullableSchema<v.ObjectSchema<{
14
+ readonly utilization: v.NumberSchema<undefined>;
15
+ readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
16
+ }, undefined>, undefined>, null>;
17
+ readonly seven_day: v.OptionalSchema<v.NullableSchema<v.ObjectSchema<{
18
+ readonly utilization: v.NumberSchema<undefined>;
19
+ readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
20
+ }, undefined>, undefined>, null>;
21
+ readonly seven_day_sonnet: v.OptionalSchema<v.NullableSchema<v.ObjectSchema<{
22
+ readonly utilization: v.NumberSchema<undefined>;
23
+ readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
24
+ }, undefined>, undefined>, null>;
25
+ }, undefined>;
26
+ export declare const CredentialRefreshPatchSchema: v.ObjectSchema<{
27
+ readonly accessToken: v.StringSchema<undefined>;
28
+ readonly expiresAt: v.NumberSchema<undefined>;
29
+ readonly refreshToken: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
30
+ readonly uuid: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
31
+ readonly accountId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
32
+ readonly email: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
33
+ }, undefined>;
34
+ export declare const StoredAccountSchema: v.ObjectSchema<{
35
+ readonly uuid: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
36
+ readonly accountId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
37
+ readonly label: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
38
+ readonly email: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
39
+ readonly planTier: v.OptionalSchema<v.StringSchema<undefined>, "">;
40
+ readonly refreshToken: v.StringSchema<undefined>;
41
+ readonly accessToken: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
42
+ readonly expiresAt: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
43
+ readonly addedAt: v.NumberSchema<undefined>;
44
+ readonly lastUsed: v.NumberSchema<undefined>;
45
+ readonly enabled: v.OptionalSchema<v.BooleanSchema<undefined>, true>;
46
+ readonly rateLimitResetAt: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
47
+ readonly cachedUsage: v.OptionalSchema<v.ObjectSchema<{
48
+ readonly five_hour: v.OptionalSchema<v.NullableSchema<v.ObjectSchema<{
49
+ readonly utilization: v.NumberSchema<undefined>;
50
+ readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
51
+ }, undefined>, undefined>, null>;
52
+ readonly seven_day: v.OptionalSchema<v.NullableSchema<v.ObjectSchema<{
53
+ readonly utilization: v.NumberSchema<undefined>;
54
+ readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
55
+ }, undefined>, undefined>, null>;
56
+ readonly seven_day_sonnet: v.OptionalSchema<v.NullableSchema<v.ObjectSchema<{
57
+ readonly utilization: v.NumberSchema<undefined>;
58
+ readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
59
+ }, undefined>, undefined>, null>;
60
+ }, undefined>, undefined>;
61
+ readonly cachedUsageAt: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
62
+ readonly consecutiveAuthFailures: v.OptionalSchema<v.NumberSchema<undefined>, 0>;
63
+ readonly isAuthDisabled: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
64
+ readonly authDisabledReason: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
65
+ }, undefined>;
66
+ export declare const AccountStorageSchema: v.ObjectSchema<{
67
+ readonly version: v.LiteralSchema<1, undefined>;
68
+ readonly accounts: v.OptionalSchema<v.ArraySchema<v.ObjectSchema<{
69
+ readonly uuid: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
70
+ readonly accountId: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
71
+ readonly label: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
72
+ readonly email: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
73
+ readonly planTier: v.OptionalSchema<v.StringSchema<undefined>, "">;
74
+ readonly refreshToken: v.StringSchema<undefined>;
75
+ readonly accessToken: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
76
+ readonly expiresAt: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
77
+ readonly addedAt: v.NumberSchema<undefined>;
78
+ readonly lastUsed: v.NumberSchema<undefined>;
79
+ readonly enabled: v.OptionalSchema<v.BooleanSchema<undefined>, true>;
80
+ readonly rateLimitResetAt: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
81
+ readonly cachedUsage: v.OptionalSchema<v.ObjectSchema<{
82
+ readonly five_hour: v.OptionalSchema<v.NullableSchema<v.ObjectSchema<{
83
+ readonly utilization: v.NumberSchema<undefined>;
84
+ readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
85
+ }, undefined>, undefined>, null>;
86
+ readonly seven_day: v.OptionalSchema<v.NullableSchema<v.ObjectSchema<{
87
+ readonly utilization: v.NumberSchema<undefined>;
88
+ readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
89
+ }, undefined>, undefined>, null>;
90
+ readonly seven_day_sonnet: v.OptionalSchema<v.NullableSchema<v.ObjectSchema<{
91
+ readonly utilization: v.NumberSchema<undefined>;
92
+ readonly resets_at: v.NullableSchema<v.StringSchema<undefined>, undefined>;
93
+ }, undefined>, undefined>, null>;
94
+ }, undefined>, undefined>;
95
+ readonly cachedUsageAt: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
96
+ readonly consecutiveAuthFailures: v.OptionalSchema<v.NumberSchema<undefined>, 0>;
97
+ readonly isAuthDisabled: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
98
+ readonly authDisabledReason: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
99
+ }, undefined>, undefined>, readonly []>;
100
+ readonly activeAccountUuid: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
101
+ }, undefined>;
102
+ export declare const AccountSelectionStrategySchema: v.PicklistSchema<["sticky", "round-robin", "hybrid"], undefined>;
103
+ export declare const PluginConfigSchema: v.ObjectSchema<{
104
+ readonly account_selection_strategy: v.OptionalSchema<v.PicklistSchema<["sticky", "round-robin", "hybrid"], undefined>, "sticky">;
105
+ readonly cross_process_claims: v.OptionalSchema<v.BooleanSchema<undefined>, true>;
106
+ readonly soft_quota_threshold_percent: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>, v.MaxValueAction<number, 100, undefined>]>, 100>;
107
+ readonly rate_limit_min_backoff_ms: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>]>, 30000>;
108
+ readonly default_retry_after_ms: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>]>, 60000>;
109
+ readonly max_consecutive_auth_failures: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>, 3>;
110
+ readonly token_failure_backoff_ms: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>]>, 30000>;
111
+ readonly proactive_refresh: v.OptionalSchema<v.BooleanSchema<undefined>, true>;
112
+ readonly proactive_refresh_buffer_seconds: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 60, undefined>]>, 1800>;
113
+ readonly proactive_refresh_interval_seconds: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 30, undefined>]>, 300>;
114
+ readonly quiet_mode: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
115
+ readonly debug: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
116
+ }, undefined>;
117
+ export type OAuthCredentials = v.InferOutput<typeof OAuthCredentialsSchema>;
118
+ export type UsageLimitEntry = v.InferOutput<typeof UsageLimitEntrySchema>;
119
+ export type UsageLimits = v.InferOutput<typeof UsageLimitsSchema>;
120
+ export type CredentialRefreshPatch = v.InferOutput<typeof CredentialRefreshPatchSchema>;
121
+ export type StoredAccount = v.InferOutput<typeof StoredAccountSchema>;
122
+ export type AccountStorage = v.InferOutput<typeof AccountStorageSchema>;
123
+ export type AccountSelectionStrategy = v.InferOutput<typeof AccountSelectionStrategySchema>;
124
+ export type PluginConfig = v.InferOutput<typeof PluginConfigSchema>;
125
+ export type TokenRefreshResult = {
126
+ ok: true;
127
+ patch: CredentialRefreshPatch;
128
+ } | {
129
+ ok: false;
130
+ permanent: boolean;
131
+ status?: number;
132
+ };
133
+ export interface ManagedAccount {
134
+ index: number;
135
+ uuid?: string;
136
+ accountId?: string;
137
+ label?: string;
138
+ email?: string;
139
+ planTier?: string;
140
+ refreshToken: string;
141
+ accessToken?: string;
142
+ expiresAt?: number;
143
+ addedAt: number;
144
+ lastUsed: number;
145
+ enabled: boolean;
146
+ rateLimitResetAt?: number;
147
+ last429At?: number;
148
+ cachedUsage?: UsageLimits;
149
+ cachedUsageAt?: number;
150
+ consecutiveAuthFailures: number;
151
+ isAuthDisabled: boolean;
152
+ authDisabledReason?: string;
153
+ }
154
+ export interface PluginClient {
155
+ auth: {
156
+ set: (params: {
157
+ path: {
158
+ id: string;
159
+ };
160
+ body: {
161
+ type: string;
162
+ refresh: string;
163
+ access: string;
164
+ expires: number;
165
+ };
166
+ }) => Promise<void>;
167
+ };
168
+ tui: {
169
+ showToast: (params: {
170
+ body: {
171
+ title?: string;
172
+ message: string;
173
+ variant: "info" | "warning" | "success" | "error";
174
+ };
175
+ }) => Promise<void>;
176
+ };
177
+ app: {
178
+ log: (params: {
179
+ body: {
180
+ service: string;
181
+ level: "debug" | "info" | "warn" | "error";
182
+ message: string;
183
+ extra?: Record<string, unknown>;
184
+ };
185
+ }) => Promise<void>;
186
+ };
187
+ }
@@ -0,0 +1,17 @@
1
+ export declare const ANSI: {
2
+ readonly hide: "\u001B[?25l";
3
+ readonly show: "\u001B[?25h";
4
+ readonly up: (n?: number) => string;
5
+ readonly down: (n?: number) => string;
6
+ readonly clearLine: "\u001B[2K";
7
+ readonly cyan: "\u001B[36m";
8
+ readonly green: "\u001B[32m";
9
+ readonly red: "\u001B[31m";
10
+ readonly yellow: "\u001B[33m";
11
+ readonly dim: "\u001B[2m";
12
+ readonly bold: "\u001B[1m";
13
+ readonly reset: "\u001B[0m";
14
+ };
15
+ export type KeyAction = "up" | "down" | "enter" | "escape" | "escape-start" | null;
16
+ export declare function parseKey(data: Buffer): KeyAction;
17
+ export declare function isTTY(): boolean;
@@ -0,0 +1 @@
1
+ export declare function confirm(message: string, defaultYes?: boolean): Promise<boolean>;
@@ -0,0 +1,13 @@
1
+ export interface MenuItem<T = string> {
2
+ label: string;
3
+ value: T;
4
+ hint?: string;
5
+ disabled?: boolean;
6
+ separator?: boolean;
7
+ color?: "red" | "green" | "yellow" | "cyan";
8
+ }
9
+ export interface SelectOptions {
10
+ message: string;
11
+ subtitle?: string;
12
+ }
13
+ export declare function select<T>(items: MenuItem<T>[], options: SelectOptions): Promise<T | null>;
@@ -0,0 +1,9 @@
1
+ import type { ManagedAccount, PluginClient } from "./types";
2
+ export declare function getConfigDir(): string;
3
+ export declare function getErrorCode(error: unknown): string | undefined;
4
+ export declare function formatWaitTime(ms: number): string;
5
+ export declare function getAccountLabel(account: ManagedAccount): string;
6
+ export declare function sleep(ms: number): Promise<void>;
7
+ export declare function showToast(client: PluginClient, message: string, variant: "info" | "warning" | "success" | "error"): Promise<void>;
8
+ export declare function debugLog(client: PluginClient, message: string, extra?: Record<string, unknown>): void;
9
+ export declare function createMinimalClient(): PluginClient;
package/package.json CHANGED
@@ -1,19 +1,22 @@
1
1
  {
2
2
  "name": "opencode-multi-account-core",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Shared core for multi-account OpenCode plugins",
5
- "main": "./src/index.ts",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
6
8
  "exports": {
7
9
  ".": {
8
10
  "source": "./src/index.ts",
11
+ "types": "./dist/index.d.ts",
9
12
  "import": "./dist/index.js",
10
13
  "default": "./dist/index.js"
11
14
  }
12
15
  },
13
16
  "scripts": {
14
- "build": "esbuild src/index.ts --bundle --outdir=dist --platform=node --format=esm --packages=external",
17
+ "build": "esbuild src/index.ts --bundle --outdir=dist --platform=node --format=esm --packages=external && tsc -p tsconfig.build.json --emitDeclarationOnly",
15
18
  "typecheck": "tsc --noEmit",
16
- "test": "vitest run",
19
+ "test": "for f in tests/*.test.ts; do bun test \"$f\" || exit 1; done",
17
20
  "prepack": "bun run build",
18
21
  "dev": "bun run build --watch"
19
22
  },
@@ -30,7 +33,6 @@
30
33
  "directory": "packages/multi-account-core"
31
34
  },
32
35
  "dependencies": {
33
- "opencode-oauth-adapters": "^0.1.2",
34
36
  "proper-lockfile": "^4.1.2",
35
37
  "valibot": "^1.2.0"
36
38
  },