opencode-1password-auth 1.0.3 → 1.0.6

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
@@ -31,7 +31,9 @@ Create these environments in 1Password:
31
31
  - `OPENCODE_MCPS_ENV_ID` - Environment ID containing MCP server secrets
32
32
 
33
33
  **Providers Environment** (holds API keys):
34
- - Provider IDs as variable names (e.g., `MINIMAX_CODING_PLAN`, `DEEPSEEK`, `OPENCODE`)
34
+ - Variable names should match provider IDs (case-insensitive, underscores converted to hyphens)
35
+ - Examples: `opencode`, `OPENCODE`, `deepseek`, `DEEPSEEK`, `minimax-coding-plan`, `MINIMAX_CODING_PLAN`
36
+ - The plugin normalizes: lowercase + underscores → hyphens
35
37
  - API keys as values
36
38
 
37
39
  **MCPS Environment** (holds MCP secrets):
@@ -97,9 +99,9 @@ To change configurations, simply update `OP_CONFIG_ENV_ID` to point to a differe
97
99
  ### Providers Environment
98
100
  ```
99
101
  opencode-providers
100
- ├── MINIMAX_CODING_PLAN = your-minimax-api-key
101
- ├── DEEPSEEK = your-deepseek-api-key
102
- └── OPENCODE = your-opencode-api-key
102
+ ├── opencode = your-opencode-api-key # or OPENCODE
103
+ ├── deepseek = your-deepseek-api-key # or DEEPSEEK
104
+ └── minimax-coding-plan = your-minimax-api-key # or MINIMAX_CODING_PLAN
103
105
  ```
104
106
 
105
107
  ### MCPS Environment
package/index-v2.ts ADDED
@@ -0,0 +1,387 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ import * as sdk from "@1password/sdk";
3
+
4
+ // Debug logging to file
5
+ const debugLog = (message: string) => {
6
+ const timestamp = new Date().toISOString();
7
+ const logMessage = `[${timestamp}] 1Password: ${message}\n`;
8
+ console.log(logMessage.trim());
9
+
10
+ try {
11
+ // Try to write to a debug log file
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const home = process.env.HOME || process.env.USERPROFILE || '';
15
+ const logDir = path.join(home, '.opencode-1password-debug');
16
+ if (!fs.existsSync(logDir)) {
17
+ fs.mkdirSync(logDir, { recursive: true });
18
+ }
19
+ const logFile = path.join(logDir, 'debug.log');
20
+ fs.appendFileSync(logFile, logMessage);
21
+ } catch (err) {
22
+ // Ignore file logging errors
23
+ }
24
+ };
25
+
26
+ interface MCPServerConfig {
27
+ environment?: Record<string, string>;
28
+ }
29
+
30
+ interface OpenCodeConfig {
31
+ mcp?: Record<string, MCPServerConfig>;
32
+ }
33
+
34
+ interface ProviderAuth {
35
+ type: string;
36
+ key: string;
37
+ }
38
+
39
+ interface AuthJson {
40
+ [providerId: string]: ProviderAuth;
41
+ }
42
+
43
+ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
44
+ let opClient: Awaited<ReturnType<typeof sdk.createClient>> | null = null;
45
+ let mcpsEnvId: string | undefined;
46
+
47
+ debugLog("Plugin loaded");
48
+
49
+ const normalizeProviderId = (variableName: string): string => {
50
+ // Convert common patterns to match auth.json provider IDs
51
+ // e.g., "MINIMAX_CODING_PLAN" -> "minimax-coding-plan"
52
+ // e.g., "OPENCODE" -> "opencode"
53
+ // e.g., "DEEPSEEK" -> "deepseek"
54
+
55
+ let normalized = variableName.toLowerCase();
56
+
57
+ // Convert underscores to hyphens
58
+ normalized = normalized.replace(/_/g, '-');
59
+
60
+ debugLog(`Normalized ${variableName} -> ${normalized}`);
61
+ return normalized;
62
+ };
63
+
64
+ const getAlternativeEnvVarNames = (providerId: string): string[] => {
65
+ // Generate all possible env var names that might be used
66
+ const names = new Set<string>();
67
+
68
+ // Original providerId (e.g., "minimax-coding-plan")
69
+ names.add(providerId);
70
+
71
+ // Uppercase version (e.g., "MINIMAX-CODING-PLAN")
72
+ names.add(providerId.toUpperCase());
73
+
74
+ // With underscores instead of hyphens (e.g., "minimax_coding_plan")
75
+ names.add(providerId.replace(/-/g, '_'));
76
+
77
+ // Uppercase with underscores (e.g., "MINIMAX_CODING_PLAN")
78
+ names.add(providerId.replace(/-/g, '_').toUpperCase());
79
+
80
+ // Just the first part (e.g., "minimax")
81
+ const firstPart = providerId.split('-')[0];
82
+ if (firstPart && firstPart !== providerId) {
83
+ names.add(firstPart);
84
+ names.add(firstPart.toUpperCase());
85
+ }
86
+
87
+ return Array.from(names);
88
+ };
89
+
90
+ const getHomeDir = (): string => {
91
+ return process.env.HOME || process.env.USERPROFILE || "";
92
+ };
93
+
94
+ const getAuthJsonPath = (): string => {
95
+ const home = getHomeDir();
96
+ return `${home}/.local/share/opencode/auth.json`;
97
+ };
98
+
99
+ const getOpenCodeJsonPath = (): string => {
100
+ const home = getHomeDir();
101
+ return `${home}/.config/opencode/opencode.json`;
102
+ };
103
+
104
+ const initClient = async () => {
105
+ const token = process.env.OP_SERVICE_ACCOUNT_TOKEN;
106
+ if (!token) {
107
+ debugLog("Missing OP_SERVICE_ACCOUNT_TOKEN");
108
+ return null;
109
+ }
110
+
111
+ try {
112
+ return await sdk.createClient({
113
+ auth: token,
114
+ integrationName: "opencode-1password-env",
115
+ integrationVersion: "1.0.0",
116
+ });
117
+ } catch (err) {
118
+ debugLog(`Failed to create client: ${err}`);
119
+ return null;
120
+ }
121
+ };
122
+
123
+ const readEnvironmentVariables = async (envId: string) => {
124
+ if (!opClient) return [];
125
+
126
+ try {
127
+ const { variables } = await opClient.environments.getVariables(envId);
128
+ debugLog(`Read ${variables?.length || 0} variables from environment ${envId}`);
129
+ return variables || [];
130
+ } catch (err) {
131
+ debugLog(`Failed to read environment ${envId}: ${err}`);
132
+ return [];
133
+ }
134
+ };
135
+
136
+ const getSecretsFromEnvironment = async (envId: string): Promise<Record<string, string>> => {
137
+ const vars = await readEnvironmentVariables(envId);
138
+ const secrets: Record<string, string> = {};
139
+ for (const v of vars) {
140
+ if (v.value) {
141
+ secrets[v.name] = v.value;
142
+ debugLog(`Found secret: ${v.name} (${v.value.length} chars)`);
143
+ }
144
+ }
145
+ return secrets;
146
+ };
147
+
148
+ const updateAuthJson = async (providerEnvId: string): Promise<void> => {
149
+ const authJsonPath = getAuthJsonPath();
150
+
151
+ try {
152
+ // Read current auth.json using cat
153
+ const catResult = await $`cat "${authJsonPath}"`;
154
+ const content = catResult.stdout;
155
+ let auth: AuthJson;
156
+
157
+ try {
158
+ auth = JSON.parse(content);
159
+ } catch {
160
+ debugLog("auth.json is not valid JSON, skipping");
161
+ return;
162
+ }
163
+
164
+ let modified = false;
165
+
166
+ for (const [providerId, authConfig] of Object.entries(auth)) {
167
+ if (authConfig.key && !authConfig.key.startsWith("{env:")) {
168
+ // Replace hardcoded key with env var reference
169
+ authConfig.key = `{env:${providerId}}`;
170
+ modified = true;
171
+ debugLog(`Updated auth.json - ${providerId} -> {env:${providerId}}`);
172
+ }
173
+ }
174
+
175
+ if (modified) {
176
+ // Write updated content using a temp file and mv
177
+ const newContent = JSON.stringify(auth, null, 2);
178
+ // Use node to write the file since $ heredocs can be tricky
179
+ await $`node -e "const fs=require('fs'); fs.writeFileSync('${authJsonPath}', JSON.stringify(${JSON.stringify(auth)}, null, 2));"`;
180
+ debugLog("auth.json updated to use environment variables");
181
+ }
182
+ } catch (err) {
183
+ debugLog(`Failed to update auth.json: ${err}`);
184
+ }
185
+ };
186
+
187
+ const updateOpenCodeJsonMCP = async (mcpEnvId: string): Promise<void> => {
188
+ const openCodeJsonPath = getOpenCodeJsonPath();
189
+
190
+ try {
191
+ // Read current opencode.json using cat
192
+ const catResult = await $`cat "${openCodeJsonPath}"`;
193
+ const content = catResult.stdout;
194
+ let config: OpenCodeConfig;
195
+
196
+ try {
197
+ config = JSON.parse(content);
198
+ } catch {
199
+ debugLog("opencode.json is not valid JSON, skipping");
200
+ return;
201
+ }
202
+
203
+ if (!config.mcp) {
204
+ debugLog("No MCP configuration found, skipping");
205
+ return;
206
+ }
207
+
208
+ let modified = false;
209
+
210
+ for (const [serverName, serverConfig] of Object.entries(config.mcp)) {
211
+ if (serverConfig?.environment) {
212
+ for (const [key, value] of Object.entries(serverConfig.environment)) {
213
+ if (typeof value === "string" && !value.startsWith("{env:") && !value.startsWith("$")) {
214
+ // Replace hardcoded value with env var reference
215
+ serverConfig.environment[key] = `{env:${key}}`;
216
+ modified = true;
217
+ debugLog(`Updated opencode.json - ${serverName}.${key} -> {env:${key}}`);
218
+ }
219
+ }
220
+ }
221
+ }
222
+
223
+ if (modified) {
224
+ // Write updated content
225
+ await $`node -e "const fs=require('fs'); fs.writeFileSync('${openCodeJsonPath}', JSON.stringify(${JSON.stringify(config)}, null, 2));"`;
226
+ debugLog("opencode.json MCP config updated to use environment variables");
227
+ }
228
+ } catch (err) {
229
+ debugLog(`Failed to update opencode.json: ${err}`);
230
+ }
231
+ };
232
+
233
+ const authenticateProviders = async (providerEnvId: string): Promise<void> => {
234
+ if (!opClient) return;
235
+
236
+ debugLog(`Reading provider secrets from environment ${providerEnvId}`);
237
+ const secrets = await getSecretsFromEnvironment(providerEnvId);
238
+ debugLog(`Found ${Object.keys(secrets).length} provider secrets`);
239
+
240
+ for (const [variableName, apiKey] of Object.entries(secrets)) {
241
+ if (!apiKey) continue;
242
+
243
+ const providerId = normalizeProviderId(variableName);
244
+ debugLog(`Processing ${variableName} -> ${providerId} (key length: ${apiKey.length})`);
245
+
246
+ // Set environment variables in multiple formats
247
+ const envVarNames = getAlternativeEnvVarNames(providerId);
248
+ for (const envVarName of envVarNames) {
249
+ process.env[envVarName] = apiKey;
250
+ debugLog(`Set process.env.${envVarName} = ${apiKey.substring(0, 8)}...`);
251
+ }
252
+
253
+ try {
254
+ // Authenticate with OpenCode runtime
255
+ debugLog(`Calling client.auth.set for ${providerId}`);
256
+ await client.auth.set({
257
+ path: { id: providerId },
258
+ body: { type: "api", key: apiKey },
259
+ });
260
+ debugLog(`✓ ${providerId} authenticated (from ${variableName})`);
261
+ } catch (err) {
262
+ debugLog(`Failed to authenticate ${providerId}: ${err}`);
263
+ if (err instanceof Error) {
264
+ debugLog(`Error message: ${err.message}`);
265
+ }
266
+ }
267
+ }
268
+ };
269
+
270
+ const getConfiguredMCPVars = (): Set<string> => {
271
+ const vars = new Set<string>();
272
+
273
+ try {
274
+ const config = client.config as OpenCodeConfig;
275
+ if (config?.mcp) {
276
+ for (const [, serverConfig] of Object.entries(config.mcp)) {
277
+ if (serverConfig?.environment) {
278
+ for (const [key] of Object.entries(serverConfig.environment)) {
279
+ if (key.startsWith("{env:")) {
280
+ const envVar = key.slice(5, -1);
281
+ vars.add(envVar);
282
+ } else {
283
+ vars.add(key);
284
+ }
285
+ }
286
+ }
287
+ }
288
+ }
289
+ } catch (err) {
290
+ debugLog(`Failed to read MCP config: ${err}`);
291
+ }
292
+
293
+ return vars;
294
+ };
295
+
296
+ const injectMCPSecrets = async (mcpEnvId: string): Promise<Record<string, string>> => {
297
+ const neededVars = getConfiguredMCPVars();
298
+ if (neededVars.size === 0) {
299
+ return {};
300
+ }
301
+
302
+ const secrets = await getSecretsFromEnvironment(mcpEnvId);
303
+ const toInject: Record<string, string> = {};
304
+
305
+ for (const varName of neededVars) {
306
+ if (secrets[varName]) {
307
+ toInject[varName] = secrets[varName];
308
+ }
309
+ }
310
+
311
+ return toInject;
312
+ };
313
+
314
+ return {
315
+ async event({ event }: { event: { type: string } }) {
316
+ debugLog(`Received event: ${event.type}`);
317
+ if (event.type === "server.connected") {
318
+ debugLog("Initializing...");
319
+
320
+ opClient = await initClient();
321
+ if (!opClient) {
322
+ debugLog("No client available");
323
+ return;
324
+ }
325
+
326
+ const configEnvId = process.env.OP_CONFIG_ENV_ID;
327
+ if (!configEnvId) {
328
+ debugLog("Missing OP_CONFIG_ENV_ID");
329
+ return;
330
+ }
331
+
332
+ debugLog(`Reading config from environment ${configEnvId}`);
333
+ const vars = await readEnvironmentVariables(configEnvId);
334
+ const configEnvIds: Record<string, string> = {};
335
+ for (const v of vars) {
336
+ if (v.name.endsWith("_ENV_ID") && v.value) {
337
+ configEnvIds[v.name] = v.value;
338
+ debugLog(`Found ${v.name} = ${v.value.substring(0, 10)}...`);
339
+ }
340
+ }
341
+
342
+ const providersEnvId = configEnvIds.OPENCODE_PROVIDERS_ENV_ID;
343
+ if (providersEnvId) {
344
+ // First update the config files to use env var references
345
+ await updateAuthJson(providersEnvId);
346
+ await authenticateProviders(providersEnvId);
347
+ }
348
+
349
+ const mcpEnvIdFromConfig = configEnvIds.OPENCODE_MCPS_ENV_ID;
350
+ if (mcpEnvIdFromConfig) {
351
+ mcpsEnvId = mcpEnvIdFromConfig;
352
+ await updateOpenCodeJsonMCP(mcpEnvIdFromConfig);
353
+ const toInject = await injectMCPSecrets(mcpEnvIdFromConfig);
354
+ if (Object.keys(toInject).length > 0) {
355
+ debugLog(`Injected ${Object.keys(toInject).join(", ")} for MCP`);
356
+ }
357
+ }
358
+
359
+ debugLog("Initialization complete");
360
+ }
361
+ },
362
+
363
+ async "shell.env"(input, output) {
364
+ if (!opClient || !mcpsEnvId) return;
365
+
366
+ const toInject = await injectMCPSecrets(mcpsEnvId);
367
+
368
+ for (const [varName, value] of Object.entries(toInject)) {
369
+ output.env[varName] = value;
370
+ debugLog(`Injected ${varName} into shell environment`);
371
+ }
372
+ },
373
+
374
+ async "tool.execute.before"(input) {
375
+ if (input.tool !== "read") return;
376
+
377
+ const fileArgs = input.args as { filePath?: string } | undefined;
378
+ if (fileArgs?.filePath && fileArgs.filePath.includes(".env")) {
379
+ debugLog(
380
+ "Warning - .env file access detected. Consider using 1Password for secrets management."
381
+ );
382
+ }
383
+ },
384
+ };
385
+ };
386
+
387
+ export default OnePasswordAuthPlugin;
package/index.ts CHANGED
@@ -1,6 +1,28 @@
1
1
  import type { Plugin } from "@opencode-ai/plugin";
2
2
  import * as sdk from "@1password/sdk";
3
3
 
4
+ // Debug logging to file
5
+ const debugLog = (message: string) => {
6
+ const timestamp = new Date().toISOString();
7
+ const logMessage = `[${timestamp}] 1Password: ${message}\n`;
8
+ console.log(logMessage.trim());
9
+
10
+ try {
11
+ // Try to write to a debug log file
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const home = process.env.HOME || process.env.USERPROFILE || '';
15
+ const logDir = path.join(home, '.opencode-1password-debug');
16
+ if (!fs.existsSync(logDir)) {
17
+ fs.mkdirSync(logDir, { recursive: true });
18
+ }
19
+ const logFile = path.join(logDir, 'debug.log');
20
+ fs.appendFileSync(logFile, logMessage);
21
+ } catch (err) {
22
+ // Ignore file logging errors
23
+ }
24
+ };
25
+
4
26
  interface MCPServerConfig {
5
27
  environment?: Record<string, string>;
6
28
  }
@@ -22,6 +44,49 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
22
44
  let opClient: Awaited<ReturnType<typeof sdk.createClient>> | null = null;
23
45
  let mcpsEnvId: string | undefined;
24
46
 
47
+ debugLog("Plugin loaded");
48
+
49
+ const normalizeProviderId = (variableName: string): string => {
50
+ // Convert common patterns to match auth.json provider IDs
51
+ // e.g., "MINIMAX_CODING_PLAN" -> "minimax-coding-plan"
52
+ // e.g., "OPENCODE" -> "opencode"
53
+ // e.g., "DEEPSEEK" -> "deepseek"
54
+
55
+ let normalized = variableName.toLowerCase();
56
+
57
+ // Convert underscores to hyphens
58
+ normalized = normalized.replace(/_/g, '-');
59
+
60
+ debugLog(`Normalized ${variableName} -> ${normalized}`);
61
+ return normalized;
62
+ };
63
+
64
+ const getAlternativeEnvVarNames = (providerId: string): string[] => {
65
+ // Generate all possible env var names that might be used
66
+ const names = new Set<string>();
67
+
68
+ // Original providerId (e.g., "minimax-coding-plan")
69
+ names.add(providerId);
70
+
71
+ // Uppercase version (e.g., "MINIMAX-CODING-PLAN")
72
+ names.add(providerId.toUpperCase());
73
+
74
+ // With underscores instead of hyphens (e.g., "minimax_coding_plan")
75
+ names.add(providerId.replace(/-/g, '_'));
76
+
77
+ // Uppercase with underscores (e.g., "MINIMAX_CODING_PLAN")
78
+ names.add(providerId.replace(/-/g, '_').toUpperCase());
79
+
80
+ // Just the first part (e.g., "minimax")
81
+ const firstPart = providerId.split('-')[0];
82
+ if (firstPart && firstPart !== providerId) {
83
+ names.add(firstPart);
84
+ names.add(firstPart.toUpperCase());
85
+ }
86
+
87
+ return Array.from(names);
88
+ };
89
+
25
90
  const getHomeDir = (): string => {
26
91
  return process.env.HOME || process.env.USERPROFILE || "";
27
92
  };
@@ -39,7 +104,7 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
39
104
  const initClient = async () => {
40
105
  const token = process.env.OP_SERVICE_ACCOUNT_TOKEN;
41
106
  if (!token) {
42
- console.error("1Password: Missing OP_SERVICE_ACCOUNT_TOKEN");
107
+ debugLog("Missing OP_SERVICE_ACCOUNT_TOKEN");
43
108
  return null;
44
109
  }
45
110
 
@@ -50,7 +115,7 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
50
115
  integrationVersion: "1.0.0",
51
116
  });
52
117
  } catch (err) {
53
- console.error("1Password: Failed to create client:", err);
118
+ debugLog(`Failed to create client: ${err}`);
54
119
  return null;
55
120
  }
56
121
  };
@@ -60,9 +125,10 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
60
125
 
61
126
  try {
62
127
  const { variables } = await opClient.environments.getVariables(envId);
128
+ debugLog(`Read ${variables?.length || 0} variables from environment ${envId}`);
63
129
  return variables || [];
64
130
  } catch (err) {
65
- console.error(`1Password: Failed to read environment ${envId}:`, err);
131
+ debugLog(`Failed to read environment ${envId}: ${err}`);
66
132
  return [];
67
133
  }
68
134
  };
@@ -73,6 +139,7 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
73
139
  for (const v of vars) {
74
140
  if (v.value) {
75
141
  secrets[v.name] = v.value;
142
+ debugLog(`Found secret: ${v.name} (${v.value.length} chars)`);
76
143
  }
77
144
  }
78
145
  return secrets;
@@ -90,7 +157,7 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
90
157
  try {
91
158
  auth = JSON.parse(content);
92
159
  } catch {
93
- console.log("1Password: auth.json is not valid JSON, skipping");
160
+ debugLog("auth.json is not valid JSON, skipping");
94
161
  return;
95
162
  }
96
163
 
@@ -101,7 +168,7 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
101
168
  // Replace hardcoded key with env var reference
102
169
  authConfig.key = `{env:${providerId}}`;
103
170
  modified = true;
104
- console.log(`1Password: Updated auth.json - ${providerId} -> {env:${providerId}}`);
171
+ debugLog(`Updated auth.json - ${providerId} -> {env:${providerId}}`);
105
172
  }
106
173
  }
107
174
 
@@ -110,10 +177,10 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
110
177
  const newContent = JSON.stringify(auth, null, 2);
111
178
  // Use node to write the file since $ heredocs can be tricky
112
179
  await $`node -e "const fs=require('fs'); fs.writeFileSync('${authJsonPath}', JSON.stringify(${JSON.stringify(auth)}, null, 2));"`;
113
- console.log("1Password: auth.json updated to use environment variables");
180
+ debugLog("auth.json updated to use environment variables");
114
181
  }
115
182
  } catch (err) {
116
- console.error("1Password: Failed to update auth.json:", err);
183
+ debugLog(`Failed to update auth.json: ${err}`);
117
184
  }
118
185
  };
119
186
 
@@ -129,12 +196,12 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
129
196
  try {
130
197
  config = JSON.parse(content);
131
198
  } catch {
132
- console.log("1Password: opencode.json is not valid JSON, skipping");
199
+ debugLog("opencode.json is not valid JSON, skipping");
133
200
  return;
134
201
  }
135
202
 
136
203
  if (!config.mcp) {
137
- console.log("1Password: No MCP configuration found, skipping");
204
+ debugLog("No MCP configuration found, skipping");
138
205
  return;
139
206
  }
140
207
 
@@ -147,7 +214,7 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
147
214
  // Replace hardcoded value with env var reference
148
215
  serverConfig.environment[key] = `{env:${key}}`;
149
216
  modified = true;
150
- console.log(`1Password: Updated opencode.json - ${serverName}.${key} -> {env:${key}}`);
217
+ debugLog(`Updated opencode.json - ${serverName}.${key} -> {env:${key}}`);
151
218
  }
152
219
  }
153
220
  }
@@ -156,29 +223,49 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
156
223
  if (modified) {
157
224
  // Write updated content
158
225
  await $`node -e "const fs=require('fs'); fs.writeFileSync('${openCodeJsonPath}', JSON.stringify(${JSON.stringify(config)}, null, 2));"`;
159
- console.log("1Password: opencode.json MCP config updated to use environment variables");
226
+ debugLog("opencode.json MCP config updated to use environment variables");
160
227
  }
161
228
  } catch (err) {
162
- console.error("1Password: Failed to update opencode.json:", err);
229
+ debugLog(`Failed to update opencode.json: ${err}`);
163
230
  }
164
231
  };
165
232
 
166
233
  const authenticateProviders = async (providerEnvId: string): Promise<void> => {
167
234
  if (!opClient) return;
168
235
 
236
+ debugLog(`Reading provider secrets from environment ${providerEnvId}`);
169
237
  const secrets = await getSecretsFromEnvironment(providerEnvId);
238
+ debugLog(`Found ${Object.keys(secrets).length} provider secrets`);
170
239
 
171
- for (const [providerId, apiKey] of Object.entries(secrets)) {
240
+ for (const [variableName, apiKey] of Object.entries(secrets)) {
172
241
  if (!apiKey) continue;
173
242
 
243
+ const providerId = normalizeProviderId(variableName);
244
+ debugLog(`Processing ${variableName} -> ${providerId} (key length: ${apiKey.length})`);
245
+
246
+ // Set environment variables in multiple formats
247
+ const envVarNames = getAlternativeEnvVarNames(providerId);
248
+ for (const envVarName of envVarNames) {
249
+ process.env[envVarName] = apiKey;
250
+ debugLog(`Set process.env.${envVarName} = ${apiKey.substring(0, 8)}...`);
251
+ }
252
+
174
253
  try {
175
- await client.auth.set({
254
+ // Authenticate with OpenCode runtime
255
+ debugLog(`Calling client.auth.set for ${providerId} with key length ${apiKey.length}`);
256
+ const result = await client.auth.set({
176
257
  path: { id: providerId },
177
258
  body: { type: "api", key: apiKey },
178
259
  });
179
- console.log(`1Password: ${providerId} authenticated`);
260
+ debugLog(`✓ ${providerId} authenticated (from ${variableName}) - Result: ${JSON.stringify(result)}`);
180
261
  } catch (err) {
181
- console.error(`1Password: Failed to authenticate ${providerId}:`, err);
262
+ debugLog(`Failed to authenticate ${providerId}: ${err}`);
263
+ if (err instanceof Error) {
264
+ debugLog(`Error message: ${err.message}`);
265
+ debugLog(`Error stack: ${err.stack}`);
266
+ }
267
+ // Also log to console for immediate visibility
268
+ console.error(`[1Password Plugin] Failed to authenticate ${providerId}: ${err}`);
182
269
  }
183
270
  }
184
271
  };
@@ -203,7 +290,7 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
203
290
  }
204
291
  }
205
292
  } catch (err) {
206
- console.error("1Password: Failed to read MCP config:", err);
293
+ debugLog(`Failed to read MCP config: ${err}`);
207
294
  }
208
295
 
209
296
  return vars;
@@ -227,49 +314,85 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
227
314
  return toInject;
228
315
  };
229
316
 
230
- return {
231
- async event({ event }: { event: { type: string } }) {
232
- if (event.type === "server.connected") {
233
- console.log("1Password: Initializing...");
317
+ // Initialize as soon as plugin loads (config hook runs early)
318
+ const initializePlugin = async () => {
319
+ debugLog("Initializing plugin...");
234
320
 
235
- opClient = await initClient();
236
- if (!opClient) {
237
- console.error("1Password: No client available");
238
- return;
239
- }
321
+ opClient = await initClient();
322
+ if (!opClient) {
323
+ debugLog("No client available");
324
+ return;
325
+ }
240
326
 
241
- const configEnvId = process.env.OP_CONFIG_ENV_ID;
242
- if (!configEnvId) {
243
- console.error("1Password: Missing OP_CONFIG_ENV_ID");
244
- return;
245
- }
327
+ const configEnvId = process.env.OP_CONFIG_ENV_ID;
328
+ if (!configEnvId) {
329
+ debugLog("Missing OP_CONFIG_ENV_ID");
330
+ return;
331
+ }
246
332
 
247
- const vars = await readEnvironmentVariables(configEnvId);
248
- const configEnvIds: Record<string, string> = {};
249
- for (const v of vars) {
250
- if (v.name.endsWith("_ENV_ID") && v.value) {
251
- configEnvIds[v.name] = v.value;
252
- }
253
- }
333
+ debugLog(`Reading config from environment ${configEnvId}`);
334
+ const vars = await readEnvironmentVariables(configEnvId);
335
+ const configEnvIds: Record<string, string> = {};
336
+ for (const v of vars) {
337
+ if (v.name.endsWith("_ENV_ID") && v.value) {
338
+ configEnvIds[v.name] = v.value;
339
+ debugLog(`Found ${v.name} = ${v.value.substring(0, 10)}...`);
340
+ }
341
+ }
254
342
 
255
- const providersEnvId = configEnvIds.OPENCODE_PROVIDERS_ENV_ID;
256
- if (providersEnvId) {
257
- // First update the config files to use env var references
258
- await updateAuthJson(providersEnvId);
259
- await authenticateProviders(providersEnvId);
260
- }
343
+ const providersEnvId = configEnvIds.OPENCODE_PROVIDERS_ENV_ID;
344
+ if (providersEnvId) {
345
+ // First update the config files to use env var references
346
+ await updateAuthJson(providersEnvId);
347
+ await authenticateProviders(providersEnvId);
348
+ }
349
+
350
+ const mcpEnvIdFromConfig = configEnvIds.OPENCODE_MCPS_ENV_ID;
351
+ if (mcpEnvIdFromConfig) {
352
+ mcpsEnvId = mcpEnvIdFromConfig;
353
+ await updateOpenCodeJsonMCP(mcpEnvIdFromConfig);
354
+ const toInject = await injectMCPSecrets(mcpEnvIdFromConfig);
355
+ if (Object.keys(toInject).length > 0) {
356
+ debugLog(`Injected ${Object.keys(toInject).join(", ")} for MCP`);
357
+ }
358
+ }
359
+
360
+ debugLog("Plugin initialization complete");
361
+ };
362
+
363
+ // Start initialization immediately
364
+ initializePlugin().catch(err => {
365
+ debugLog(`Failed to initialize plugin: ${err}`);
366
+ });
261
367
 
262
- const mcpEnvIdFromConfig = configEnvIds.OPENCODE_MCPS_ENV_ID;
263
- if (mcpEnvIdFromConfig) {
264
- mcpsEnvId = mcpEnvIdFromConfig;
265
- await updateOpenCodeJsonMCP(mcpEnvIdFromConfig);
266
- const toInject = await injectMCPSecrets(mcpEnvIdFromConfig);
267
- if (Object.keys(toInject).length > 0) {
268
- console.log(`1Password: Injected ${Object.keys(toInject).join(", ")} for MCP`);
368
+ return {
369
+ async config(config: any) {
370
+ debugLog("Config hook called");
371
+ // Config hook runs early, we could also initialize here
372
+ // but we're already initializing above
373
+ },
374
+
375
+ async event({ event }: { event: { type: string } }) {
376
+ debugLog(`Received event: ${event.type}`);
377
+ if (event.type === "server.connected") {
378
+ debugLog("Server connected - rechecking authentication");
379
+ // Re-authenticate providers in case they weren't set earlier
380
+ if (opClient) {
381
+ const configEnvId = process.env.OP_CONFIG_ENV_ID;
382
+ if (configEnvId) {
383
+ const vars = await readEnvironmentVariables(configEnvId);
384
+ const configEnvIds: Record<string, string> = {};
385
+ for (const v of vars) {
386
+ if (v.name.endsWith("_ENV_ID") && v.value) {
387
+ configEnvIds[v.name] = v.value;
388
+ }
389
+ }
390
+ const providersEnvId = configEnvIds.OPENCODE_PROVIDERS_ENV_ID;
391
+ if (providersEnvId) {
392
+ await authenticateProviders(providersEnvId);
393
+ }
269
394
  }
270
395
  }
271
-
272
- console.log("1Password: Initialization complete");
273
396
  }
274
397
  },
275
398
 
@@ -280,6 +403,7 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
280
403
 
281
404
  for (const [varName, value] of Object.entries(toInject)) {
282
405
  output.env[varName] = value;
406
+ debugLog(`Injected ${varName} into shell environment`);
283
407
  }
284
408
  },
285
409
 
@@ -288,8 +412,8 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
288
412
 
289
413
  const fileArgs = input.args as { filePath?: string } | undefined;
290
414
  if (fileArgs?.filePath && fileArgs.filePath.includes(".env")) {
291
- console.warn(
292
- "1Password: Warning - .env file access detected. Consider using 1Password for secrets management."
415
+ debugLog(
416
+ "Warning - .env file access detected. Consider using 1Password for secrets management."
293
417
  );
294
418
  }
295
419
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-1password-auth",
3
- "version": "1.0.3",
3
+ "version": "1.0.6",
4
4
  "description": "1Password integration for OpenCode - authenticate providers and inject MCP secrets from 1Password environments",
5
5
  "type": "module",
6
6
  "main": "index.ts",
package/setup.ps1 CHANGED
@@ -67,13 +67,13 @@ function Remove-RegistryValue {
67
67
  }
68
68
 
69
69
  function Get-OpenCodeAuthJsonPath {
70
- $home = if ($env:USERPROFILE) { $env:USERPROFILE } else { $env:USERPROFILE }
71
- return "$home/.local/share/opencode/auth.json"
70
+ $homeDir = if ($env:USERPROFILE) { $env:USERPROFILE } else { $env:USERPROFILE }
71
+ return "$homeDir/.local/share/opencode/auth.json"
72
72
  }
73
73
 
74
74
  function Get-OpenCodeConfigJsonPath {
75
- $home = if ($env:USERPROFILE) { $env:USERPROFILE } else { $env:USERPROFILE }
76
- return "$home/.config/opencode/opencode.json"
75
+ $homeDir = if ($env:USERPROFILE) { $env:USERPROFILE } else { $env:USERPROFILE }
76
+ return "$homeDir/.config/opencode/opencode.json"
77
77
  }
78
78
 
79
79
  function Update-ConfigFiles {