opencode-copilot-multi-fix 1.1.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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -0
  3. package/bin/cli.js +26 -0
  4. package/dist/commands/multi-copilot.d.ts +18 -0
  5. package/dist/commands/multi-copilot.d.ts.map +1 -0
  6. package/dist/commands/multi-copilot.js +403 -0
  7. package/dist/commands/multi-copilot.js.map +1 -0
  8. package/dist/config/writer.d.ts +22 -0
  9. package/dist/config/writer.d.ts.map +1 -0
  10. package/dist/config/writer.js +143 -0
  11. package/dist/config/writer.js.map +1 -0
  12. package/dist/discovery/accounts.d.ts +24 -0
  13. package/dist/discovery/accounts.d.ts.map +1 -0
  14. package/dist/discovery/accounts.js +128 -0
  15. package/dist/discovery/accounts.js.map +1 -0
  16. package/dist/discovery/models.d.ts +32 -0
  17. package/dist/discovery/models.d.ts.map +1 -0
  18. package/dist/discovery/models.js +124 -0
  19. package/dist/discovery/models.js.map +1 -0
  20. package/dist/discovery/username.d.ts +8 -0
  21. package/dist/discovery/username.d.ts.map +1 -0
  22. package/dist/discovery/username.js +47 -0
  23. package/dist/discovery/username.js.map +1 -0
  24. package/dist/index.d.ts +31 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +82 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/provider.d.ts +16 -0
  29. package/dist/provider.d.ts.map +1 -0
  30. package/dist/provider.js +174 -0
  31. package/dist/provider.js.map +1 -0
  32. package/dist/storage/auth.d.ts +21 -0
  33. package/dist/storage/auth.d.ts.map +1 -0
  34. package/dist/storage/auth.js +130 -0
  35. package/dist/storage/auth.js.map +1 -0
  36. package/dist/storage/pool.d.ts +29 -0
  37. package/dist/storage/pool.d.ts.map +1 -0
  38. package/dist/storage/pool.js +152 -0
  39. package/dist/storage/pool.js.map +1 -0
  40. package/dist/types.d.ts +108 -0
  41. package/dist/types.d.ts.map +1 -0
  42. package/dist/types.js +32 -0
  43. package/dist/types.js.map +1 -0
  44. package/dist/utils/errors.d.ts +28 -0
  45. package/dist/utils/errors.d.ts.map +1 -0
  46. package/dist/utils/errors.js +52 -0
  47. package/dist/utils/errors.js.map +1 -0
  48. package/dist/utils/jsonc.d.ts +20 -0
  49. package/dist/utils/jsonc.d.ts.map +1 -0
  50. package/dist/utils/jsonc.js +91 -0
  51. package/dist/utils/jsonc.js.map +1 -0
  52. package/dist/utils/logger.d.ts +11 -0
  53. package/dist/utils/logger.d.ts.map +1 -0
  54. package/dist/utils/logger.js +53 -0
  55. package/dist/utils/logger.js.map +1 -0
  56. package/package.json +71 -0
  57. package/scripts/postinstall.js +86 -0
package/dist/index.js ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * OpenCode GitHub Copilot Multi-Account Plugin
3
+ *
4
+ * Enables using multiple GitHub Copilot accounts simultaneously.
5
+ * Each account's models appear in the model selector with format:
6
+ * copilot-multi/username:model-name
7
+ *
8
+ * Architecture:
9
+ * - Single auth hook for provider "copilot-multi"
10
+ * - Custom fetch routes requests based on model ID
11
+ * - Account pool stored separately from OpenCode auth
12
+ * - Lazy loading - no blocking init
13
+ *
14
+ * Management:
15
+ * - Use CLI tool: opencode-copilot-multi list|remove|clear
16
+ *
17
+ * @author Valerio Fantozzi
18
+ * @license MIT
19
+ */
20
+ import { logger } from './utils/logger.js';
21
+ import { createProviderLoader } from './provider.js';
22
+ import { getAccountPool } from './storage/pool.js';
23
+ import { detectAndAddNewAccount, syncAccounts } from './discovery/accounts.js';
24
+ import { PLUGIN_CONSTANTS } from './types.js';
25
+ /**
26
+ * Plugin entry point
27
+ *
28
+ * IMPORTANT: This function must return quickly!
29
+ * - No blocking HTTP calls
30
+ * - No heavy I/O operations
31
+ * - Defer everything to lazy loading
32
+ */
33
+ export const plugin = async ({ client }) => {
34
+ logger.info('=== Copilot Multi-Account Plugin Starting ===');
35
+ logger.info(`Registering provider: ${PLUGIN_CONSTANTS.PROVIDER_NAME}`);
36
+ // Start background sync immediately (but don't await - let it run async)
37
+ // This will detect new accounts and update config
38
+ syncAccounts().catch((error) => {
39
+ logger.error('Background sync failed', {
40
+ error: error instanceof Error ? error.message : String(error)
41
+ });
42
+ });
43
+ return {
44
+ auth: {
45
+ provider: PLUGIN_CONSTANTS.PROVIDER_NAME,
46
+ methods: [], // No auth methods - we manage auth internally
47
+ /**
48
+ * Loader is called when OpenCode needs to use this provider
49
+ * Returns custom fetch that handles multi-account routing
50
+ */
51
+ loader: async (_getAuth, _provider) => {
52
+ logger.debug('Provider loader called');
53
+ // Check if we have accounts
54
+ const pool = await getAccountPool();
55
+ if (pool.accounts.length === 0) {
56
+ logger.warn('No accounts in pool. Use "opencode auth login" (GitHub Copilot) first.');
57
+ // Try to detect from current auth
58
+ await detectAndAddNewAccount();
59
+ }
60
+ return createProviderLoader();
61
+ }
62
+ },
63
+ /**
64
+ * Event hook for session events
65
+ * Used to trigger account sync on new sessions and detect logout
66
+ */
67
+ event: async ({ event }) => {
68
+ if (event.type === 'session.created') {
69
+ logger.debug('New session created, syncing accounts...');
70
+ // Non-blocking sync to detect changes (new accounts or logout)
71
+ syncAccounts().catch(error => {
72
+ logger.error('Account sync failed', {
73
+ error: error instanceof Error ? error.message : String(error)
74
+ });
75
+ });
76
+ }
77
+ }
78
+ };
79
+ };
80
+ // Default export for OpenCode plugin loader
81
+ export default plugin;
82
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG9C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,MAAM,GAAW,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;IACjD,MAAM,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;IAE7D,MAAM,CAAC,IAAI,CAAC,yBAAyB,gBAAgB,CAAC,aAAa,EAAE,CAAC,CAAC;IAEvE,yEAAyE;IACzE,kDAAkD;IAClD,YAAY,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QAC7B,MAAM,CAAC,KAAK,CAAC,wBAAwB,EAAE;YACrC,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAC9D,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,IAAI,EAAE;YACJ,QAAQ,EAAE,gBAAgB,CAAC,aAAa;YACxC,OAAO,EAAE,EAAE,EAAE,8CAA8C;YAE3D;;;eAGG;YACH,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE;gBACpC,MAAM,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;gBAEvC,4BAA4B;gBAC5B,MAAM,IAAI,GAAG,MAAM,cAAc,EAAE,CAAC;gBAEpC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC/B,MAAM,CAAC,IAAI,CAAC,wEAAwE,CAAC,CAAC;oBACtF,kCAAkC;oBAClC,MAAM,sBAAsB,EAAE,CAAC;gBACjC,CAAC;gBAED,OAAO,oBAAoB,EAAE,CAAC;YAChC,CAAC;SACF;QAED;;;WAGG;QACH,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;YACzB,IAAI,KAAK,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;gBACrC,MAAM,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC;gBACzD,+DAA+D;gBAC/D,YAAY,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;oBAC3B,MAAM,CAAC,KAAK,CAAC,qBAAqB,EAAE;wBAClC,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;qBAC9D,CAAC,CAAC;gBACL,CAAC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,4CAA4C;AAC5C,eAAe,MAAM,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Provider implementation with custom fetch for multi-account routing
3
+ */
4
+ /**
5
+ * Create the provider loader with custom fetch
6
+ * This is what OpenCode calls when using copilot-multi provider
7
+ */
8
+ export declare function createProviderLoader(): {
9
+ baseURL: "https://api.githubcopilot.com";
10
+ apiKey: string;
11
+ /**
12
+ * Custom fetch that routes requests to the correct account
13
+ */
14
+ fetch(input: Request | string | URL, init?: RequestInit): Promise<Response>;
15
+ };
16
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;GAEG;AAkIH;;;GAGG;AACH,wBAAgB,oBAAoB;;;IAMhC;;OAEG;iBACgB,OAAO,GAAG,MAAM,GAAG,GAAG,SAAS,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC;EA2EpF"}
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Provider implementation with custom fetch for multi-account routing
3
+ */
4
+ import { logger } from './utils/logger.js';
5
+ import { AccountNotFoundError, InvalidModelIdError, TokenRefreshError } from './utils/errors.js';
6
+ import { getAccountPool, updateAccountInPool } from './storage/pool.js';
7
+ import { COPILOT_HEADERS, PLUGIN_CONSTANTS } from './types.js';
8
+ // Token refresh mutex to prevent concurrent refreshes
9
+ const refreshLocks = new Map();
10
+ /**
11
+ * Parse model ID to extract username and real model
12
+ * Format: "username:model-id" -> { username: "username", model: "model-id" }
13
+ */
14
+ function parseModelId(modelId) {
15
+ const colonIndex = modelId.indexOf(':');
16
+ if (colonIndex === -1) {
17
+ throw new InvalidModelIdError(modelId);
18
+ }
19
+ const username = modelId.slice(0, colonIndex);
20
+ const model = modelId.slice(colonIndex + 1);
21
+ if (!username || !model) {
22
+ throw new InvalidModelIdError(modelId);
23
+ }
24
+ return { username, model };
25
+ }
26
+ /**
27
+ * Refresh token for an account if needed
28
+ */
29
+ async function refreshTokenIfNeeded(account) {
30
+ const now = Date.now();
31
+ const expiresAt = account.auth.expires;
32
+ const bufferMs = PLUGIN_CONSTANTS.TOKEN_REFRESH_BUFFER_MS;
33
+ // expires: 0 means the token never expires (gho_* tokens)
34
+ if (expiresAt === 0) {
35
+ logger.debug(`Token never expires for ${account.username} (gho_* token)`);
36
+ return;
37
+ }
38
+ // Check if token is still valid
39
+ if (expiresAt > now + bufferMs) {
40
+ logger.debug(`Token valid for ${account.username}`, {
41
+ expiresIn: Math.round((expiresAt - now) / 1000 / 60) + ' minutes'
42
+ });
43
+ return;
44
+ }
45
+ logger.info(`Token expired or expiring soon for ${account.username}, refreshing...`);
46
+ // Check for existing refresh in progress
47
+ const existingLock = refreshLocks.get(account.id);
48
+ if (existingLock) {
49
+ logger.debug(`Waiting for existing refresh for ${account.username}`);
50
+ await existingLock;
51
+ return;
52
+ }
53
+ // Create refresh lock
54
+ const refreshPromise = (async () => {
55
+ try {
56
+ const response = await fetch('https://github.com/login/oauth/access_token', {
57
+ method: 'POST',
58
+ headers: {
59
+ 'Content-Type': 'application/json',
60
+ 'Accept': 'application/json',
61
+ },
62
+ body: JSON.stringify({
63
+ client_id: 'Iv1.b507a08c87ecfe98', // VS Code client ID
64
+ grant_type: 'refresh_token',
65
+ refresh_token: account.auth.refresh,
66
+ }),
67
+ });
68
+ if (!response.ok) {
69
+ throw new Error(`Token refresh failed: ${response.status}`);
70
+ }
71
+ const data = await response.json();
72
+ if (data.error || !data.access_token) {
73
+ throw new Error(data.error || 'No access token in response');
74
+ }
75
+ // Update account with new tokens
76
+ account.auth.access = data.access_token;
77
+ if (data.refresh_token) {
78
+ account.auth.refresh = data.refresh_token;
79
+ }
80
+ account.auth.expires = Date.now() + (data.expires_in || 28800) * 1000;
81
+ // Persist updated account
82
+ await updateAccountInPool(account);
83
+ logger.info(`Token refreshed for ${account.username}`, {
84
+ expiresIn: Math.round((account.auth.expires - Date.now()) / 1000 / 60) + ' minutes'
85
+ });
86
+ }
87
+ catch (error) {
88
+ logger.error(`Token refresh failed for ${account.username}`, {
89
+ error: error instanceof Error ? error.message : String(error)
90
+ });
91
+ throw new TokenRefreshError(account.username, error instanceof Error ? error : undefined);
92
+ }
93
+ finally {
94
+ refreshLocks.delete(account.id);
95
+ }
96
+ })();
97
+ refreshLocks.set(account.id, refreshPromise);
98
+ await refreshPromise;
99
+ }
100
+ /**
101
+ * Create the provider loader with custom fetch
102
+ * This is what OpenCode calls when using copilot-multi provider
103
+ */
104
+ export function createProviderLoader() {
105
+ logger.info('createProviderLoader called - returning config with baseURL:', { baseURL: PLUGIN_CONSTANTS.BASE_URL });
106
+ return {
107
+ baseURL: PLUGIN_CONSTANTS.BASE_URL,
108
+ apiKey: '', // Not used - we inject auth in fetch
109
+ /**
110
+ * Custom fetch that routes requests to the correct account
111
+ */
112
+ async fetch(input, init) {
113
+ logger.info('Custom fetch called', { input: String(input) });
114
+ const startTime = Date.now();
115
+ try {
116
+ // Parse request body to get model
117
+ let body = {};
118
+ if (init?.body && typeof init.body === 'string') {
119
+ body = JSON.parse(init.body);
120
+ }
121
+ const modelId = body.model;
122
+ if (!modelId) {
123
+ throw new Error('No model specified in request');
124
+ }
125
+ logger.debug(`Request for model: ${modelId}`);
126
+ // Parse model ID to get username and real model
127
+ const { username, model } = parseModelId(modelId);
128
+ logger.debug(`Routing to account: ${username}, model: ${model}`);
129
+ // Load account pool and find account
130
+ const pool = await getAccountPool();
131
+ const account = pool.accounts.find(a => a.username === username);
132
+ if (!account) {
133
+ throw new AccountNotFoundError(username);
134
+ }
135
+ // Refresh token if needed
136
+ await refreshTokenIfNeeded(account);
137
+ // Replace model in body with the real model name
138
+ body.model = model;
139
+ // Build headers
140
+ const headers = new Headers(init?.headers);
141
+ headers.set('Authorization', `Bearer ${account.auth.access}`);
142
+ // Add Copilot headers
143
+ for (const [key, value] of Object.entries(COPILOT_HEADERS)) {
144
+ headers.set(key, value);
145
+ }
146
+ // Update last used timestamp
147
+ account.lastUsed = Date.now();
148
+ // Make the actual request
149
+ const response = await fetch(input, {
150
+ ...init,
151
+ body: JSON.stringify(body),
152
+ headers,
153
+ });
154
+ const duration = Date.now() - startTime;
155
+ logger.info(`Request completed`, {
156
+ account: username,
157
+ model,
158
+ status: response.status,
159
+ duration: `${duration}ms`
160
+ });
161
+ return response;
162
+ }
163
+ catch (error) {
164
+ const duration = Date.now() - startTime;
165
+ logger.error(`Request failed`, {
166
+ error: error instanceof Error ? error.message : String(error),
167
+ duration: `${duration}ms`
168
+ });
169
+ throw error;
170
+ }
171
+ }
172
+ };
173
+ }
174
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Storage for reading OpenCode's auth.json
3
+ */
4
+ import type { OAuthData } from '../types.js';
5
+ /**
6
+ * Read GitHub Copilot auth from OpenCode's auth.json
7
+ * Returns null if not found or invalid
8
+ */
9
+ export declare function getGitHubCopilotAuth(): Promise<OAuthData | null>;
10
+ /**
11
+ * Generate a stable ID from refresh token (hash first 16 chars)
12
+ */
13
+ export declare function generateAccountId(refreshToken: string): string;
14
+ /**
15
+ * Register copilot-multi provider in OpenCode's auth.json
16
+ * This is required so OpenCode knows to call our auth loader
17
+ *
18
+ * @returns true if registration was successful, false otherwise
19
+ */
20
+ export declare function registerProviderInAuthJson(): Promise<boolean>;
21
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/storage/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AA4B7C;;;GAGG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CA4CtE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAI9D;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,OAAO,CAAC,CAgDnE"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Storage for reading OpenCode's auth.json
3
+ */
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import os from 'os';
7
+ import { logger } from '../utils/logger.js';
8
+ /**
9
+ * Get path to OpenCode's auth.json
10
+ * Checks multiple possible locations for cross-platform compatibility.
11
+ * OpenCode stores auth.json at ~/.local/share/opencode/auth.json on all platforms,
12
+ * but some older installations may use ~/.opencode/auth.json on Windows.
13
+ */
14
+ function getAuthPath() {
15
+ const homeDir = os.homedir();
16
+ const xdgDataHome = process.env.XDG_DATA_HOME;
17
+ if (xdgDataHome) {
18
+ return path.join(xdgDataHome, 'opencode', 'auth.json');
19
+ }
20
+ // Check both possible locations (OpenCode uses XDG path on Windows too)
21
+ const xdgPath = path.join(homeDir, '.local', 'share', 'opencode', 'auth.json');
22
+ const fallbackPath = path.join(homeDir, '.opencode', 'auth.json');
23
+ // Use whichever exists (prioritize XDG standard path)
24
+ if (fs.existsSync(xdgPath))
25
+ return xdgPath;
26
+ if (fs.existsSync(fallbackPath))
27
+ return fallbackPath;
28
+ // Default to XDG path for new installations
29
+ return xdgPath;
30
+ }
31
+ /**
32
+ * Read GitHub Copilot auth from OpenCode's auth.json
33
+ * Returns null if not found or invalid
34
+ */
35
+ export async function getGitHubCopilotAuth() {
36
+ const authPath = getAuthPath();
37
+ try {
38
+ if (!fs.existsSync(authPath)) {
39
+ logger.debug('Auth file not found', { path: authPath });
40
+ return null;
41
+ }
42
+ const content = fs.readFileSync(authPath, 'utf-8');
43
+ const authData = JSON.parse(content);
44
+ const copilotAuth = authData['github-copilot'];
45
+ if (!copilotAuth) {
46
+ logger.debug('No github-copilot entry in auth.json');
47
+ return null;
48
+ }
49
+ if (copilotAuth.type !== 'oauth') {
50
+ logger.debug('github-copilot auth is not oauth type', { type: copilotAuth.type });
51
+ return null;
52
+ }
53
+ // Validate required fields
54
+ // Note: expires can be 0 for non-expiring tokens (gho_* tokens)
55
+ if (!copilotAuth.refresh || !copilotAuth.access || copilotAuth.expires === undefined) {
56
+ logger.warn('github-copilot auth missing required fields');
57
+ return null;
58
+ }
59
+ return {
60
+ type: 'oauth',
61
+ refresh: copilotAuth.refresh,
62
+ access: copilotAuth.access,
63
+ expires: copilotAuth.expires,
64
+ };
65
+ }
66
+ catch (error) {
67
+ logger.error('Failed to read auth.json', {
68
+ error: error instanceof Error ? error.message : String(error)
69
+ });
70
+ return null;
71
+ }
72
+ }
73
+ /**
74
+ * Generate a stable ID from refresh token (hash first 16 chars)
75
+ */
76
+ export function generateAccountId(refreshToken) {
77
+ // Simple hash for ID - just use first 16 chars encoded
78
+ const prefix = refreshToken.slice(0, 16);
79
+ return Buffer.from(prefix).toString('base64').replace(/[^a-zA-Z0-9]/g, '').slice(0, 12);
80
+ }
81
+ /**
82
+ * Register copilot-multi provider in OpenCode's auth.json
83
+ * This is required so OpenCode knows to call our auth loader
84
+ *
85
+ * @returns true if registration was successful, false otherwise
86
+ */
87
+ export async function registerProviderInAuthJson() {
88
+ const authPath = getAuthPath();
89
+ try {
90
+ // Read existing auth.json or create empty object
91
+ let authData = {};
92
+ if (fs.existsSync(authPath)) {
93
+ const content = fs.readFileSync(authPath, 'utf-8');
94
+ authData = JSON.parse(content);
95
+ logger.debug('Read existing auth.json', { path: authPath });
96
+ }
97
+ else {
98
+ logger.debug('auth.json not found, will create new one', { path: authPath });
99
+ }
100
+ // Check if copilot-multi is already registered
101
+ if (authData['copilot-multi']) {
102
+ logger.debug('copilot-multi already registered in auth.json');
103
+ return true;
104
+ }
105
+ // Add copilot-multi provider entry
106
+ // Empty credentials since we manage auth internally via our loader
107
+ authData['copilot-multi'] = {
108
+ type: 'oauth',
109
+ refresh: '',
110
+ access: '',
111
+ expires: 0
112
+ };
113
+ // Ensure directory exists
114
+ const authDir = path.dirname(authPath);
115
+ if (!fs.existsSync(authDir)) {
116
+ fs.mkdirSync(authDir, { recursive: true });
117
+ }
118
+ // Write updated auth.json
119
+ fs.writeFileSync(authPath, JSON.stringify(authData, null, 2), { mode: 0o600 });
120
+ logger.info('Registered copilot-multi in auth.json', { path: authPath });
121
+ return true;
122
+ }
123
+ catch (error) {
124
+ logger.error('Failed to register provider in auth.json', {
125
+ error: error instanceof Error ? error.message : String(error)
126
+ });
127
+ return false;
128
+ }
129
+ }
130
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/storage/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Account pool storage with lazy loading singleton
3
+ */
4
+ import type { AccountPool, Account } from '../types.js';
5
+ /**
6
+ * Get account pool (lazy loaded, cached)
7
+ */
8
+ export declare function getAccountPool(): Promise<AccountPool>;
9
+ /**
10
+ * Save account pool to disk
11
+ */
12
+ export declare function saveAccountPool(pool: AccountPool): Promise<void>;
13
+ /**
14
+ * Add account to pool (if not exists)
15
+ */
16
+ export declare function addAccountToPool(account: Account): Promise<boolean>;
17
+ /**
18
+ * Update account in pool
19
+ */
20
+ export declare function updateAccountInPool(account: Account): Promise<void>;
21
+ /**
22
+ * Find account by username
23
+ */
24
+ export declare function findAccountByUsername(username: string): Promise<Account | undefined>;
25
+ /**
26
+ * Invalidate pool cache (for testing or forced reload)
27
+ */
28
+ export declare function invalidatePoolCache(): void;
29
+ //# sourceMappingURL=pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../src/storage/pool.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AA8ExD;;GAEG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,WAAW,CAAC,CAa3D;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBtE;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAkBzE;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAWzE;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAG1F;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAI1C"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Account pool storage with lazy loading singleton
3
+ */
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import os from 'os';
7
+ import { logger } from '../utils/logger.js';
8
+ import { PoolCorruptedError } from '../utils/errors.js';
9
+ import { PLUGIN_CONSTANTS } from '../types.js';
10
+ /**
11
+ * Get path to the account pool file
12
+ */
13
+ function getPoolPath() {
14
+ const homeDir = os.homedir();
15
+ const dataDir = process.env.XDG_DATA_HOME
16
+ ? path.join(process.env.XDG_DATA_HOME, 'opencode')
17
+ : path.join(homeDir, '.local', 'share', 'opencode');
18
+ return path.join(dataDir, PLUGIN_CONSTANTS.POOL_FILE);
19
+ }
20
+ /**
21
+ * Create an empty pool
22
+ */
23
+ function createEmptyPool() {
24
+ return {
25
+ version: PLUGIN_CONSTANTS.POOL_VERSION,
26
+ accounts: [],
27
+ lastUpdated: Date.now(),
28
+ };
29
+ }
30
+ /**
31
+ * Validate pool structure
32
+ */
33
+ function isValidPool(data) {
34
+ if (!data || typeof data !== 'object')
35
+ return false;
36
+ const pool = data;
37
+ return (typeof pool.version === 'number' &&
38
+ Array.isArray(pool.accounts) &&
39
+ typeof pool.lastUpdated === 'number');
40
+ }
41
+ // Singleton cache
42
+ let poolInstance = null;
43
+ let loadPromise = null;
44
+ /**
45
+ * Load account pool from disk
46
+ */
47
+ async function loadPoolFromDisk() {
48
+ const poolPath = getPoolPath();
49
+ try {
50
+ if (!fs.existsSync(poolPath)) {
51
+ logger.info('Pool file not found, creating empty pool');
52
+ return createEmptyPool();
53
+ }
54
+ const content = fs.readFileSync(poolPath, 'utf-8');
55
+ const data = JSON.parse(content);
56
+ if (!isValidPool(data)) {
57
+ logger.warn('Pool file has invalid structure, creating new pool');
58
+ // Backup corrupted file
59
+ const backupPath = `${poolPath}.backup.${Date.now()}`;
60
+ fs.renameSync(poolPath, backupPath);
61
+ return createEmptyPool();
62
+ }
63
+ logger.info(`Loaded pool with ${data.accounts.length} accounts`);
64
+ return data;
65
+ }
66
+ catch (error) {
67
+ if (error instanceof SyntaxError) {
68
+ logger.error('Pool file is corrupted (invalid JSON)');
69
+ throw new PoolCorruptedError(poolPath);
70
+ }
71
+ throw error;
72
+ }
73
+ }
74
+ /**
75
+ * Get account pool (lazy loaded, cached)
76
+ */
77
+ export async function getAccountPool() {
78
+ if (poolInstance) {
79
+ return poolInstance;
80
+ }
81
+ if (!loadPromise) {
82
+ loadPromise = loadPoolFromDisk().then(pool => {
83
+ poolInstance = pool;
84
+ return pool;
85
+ });
86
+ }
87
+ return loadPromise;
88
+ }
89
+ /**
90
+ * Save account pool to disk
91
+ */
92
+ export async function saveAccountPool(pool) {
93
+ const poolPath = getPoolPath();
94
+ const poolDir = path.dirname(poolPath);
95
+ // Ensure directory exists
96
+ if (!fs.existsSync(poolDir)) {
97
+ fs.mkdirSync(poolDir, { recursive: true });
98
+ }
99
+ // Update timestamp
100
+ pool.lastUpdated = Date.now();
101
+ // Write with pretty formatting
102
+ fs.writeFileSync(poolPath, JSON.stringify(pool, null, 2));
103
+ fs.chmodSync(poolPath, 0o600); // Secure permissions
104
+ // Update cache
105
+ poolInstance = pool;
106
+ logger.info('Pool saved', { accounts: pool.accounts.length });
107
+ }
108
+ /**
109
+ * Add account to pool (if not exists)
110
+ */
111
+ export async function addAccountToPool(account) {
112
+ const pool = await getAccountPool();
113
+ // Check if already exists (by ID or username)
114
+ const exists = pool.accounts.some(a => a.id === account.id || a.username === account.username);
115
+ if (exists) {
116
+ logger.debug('Account already in pool', { username: account.username });
117
+ return false;
118
+ }
119
+ pool.accounts.push(account);
120
+ await saveAccountPool(pool);
121
+ logger.info('Account added to pool', { username: account.username });
122
+ return true;
123
+ }
124
+ /**
125
+ * Update account in pool
126
+ */
127
+ export async function updateAccountInPool(account) {
128
+ const pool = await getAccountPool();
129
+ const index = pool.accounts.findIndex(a => a.id === account.id);
130
+ if (index === -1) {
131
+ logger.warn('Account not found for update', { id: account.id });
132
+ return;
133
+ }
134
+ pool.accounts[index] = account;
135
+ await saveAccountPool(pool);
136
+ }
137
+ /**
138
+ * Find account by username
139
+ */
140
+ export async function findAccountByUsername(username) {
141
+ const pool = await getAccountPool();
142
+ return pool.accounts.find(a => a.username === username);
143
+ }
144
+ /**
145
+ * Invalidate pool cache (for testing or forced reload)
146
+ */
147
+ export function invalidatePoolCache() {
148
+ poolInstance = null;
149
+ loadPromise = null;
150
+ logger.debug('Pool cache invalidated');
151
+ }
152
+ //# sourceMappingURL=pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.js","sourceRoot":"","sources":["../../src/storage/pool.ts"],"names":[],"mappings":"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"}