opencode-1password-auth 1.0.7 → 1.0.9

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/index.ts DELETED
@@ -1,423 +0,0 @@
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} with key length ${apiKey.length}`);
256
- const result = await client.auth.set({
257
- path: { id: providerId },
258
- body: { type: "api", key: apiKey },
259
- });
260
- debugLog(`✓ ${providerId} authenticated (from ${variableName}) - Result: ${JSON.stringify(result)}`);
261
- } catch (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}`);
269
- }
270
- }
271
- };
272
-
273
- const getConfiguredMCPVars = (): Set<string> => {
274
- const vars = new Set<string>();
275
-
276
- try {
277
- const config = client.config as OpenCodeConfig;
278
- if (config?.mcp) {
279
- for (const [, serverConfig] of Object.entries(config.mcp)) {
280
- if (serverConfig?.environment) {
281
- for (const [key] of Object.entries(serverConfig.environment)) {
282
- if (key.startsWith("{env:")) {
283
- const envVar = key.slice(5, -1);
284
- vars.add(envVar);
285
- } else {
286
- vars.add(key);
287
- }
288
- }
289
- }
290
- }
291
- }
292
- } catch (err) {
293
- debugLog(`Failed to read MCP config: ${err}`);
294
- }
295
-
296
- return vars;
297
- };
298
-
299
- const injectMCPSecrets = async (mcpEnvId: string): Promise<Record<string, string>> => {
300
- const neededVars = getConfiguredMCPVars();
301
- if (neededVars.size === 0) {
302
- return {};
303
- }
304
-
305
- const secrets = await getSecretsFromEnvironment(mcpEnvId);
306
- const toInject: Record<string, string> = {};
307
-
308
- for (const varName of neededVars) {
309
- if (secrets[varName]) {
310
- toInject[varName] = secrets[varName];
311
- }
312
- }
313
-
314
- return toInject;
315
- };
316
-
317
- // Initialize as soon as plugin loads (config hook runs early)
318
- const initializePlugin = async () => {
319
- debugLog("Initializing plugin...");
320
-
321
- opClient = await initClient();
322
- if (!opClient) {
323
- debugLog("No client available");
324
- return;
325
- }
326
-
327
- const configEnvId = process.env.OP_CONFIG_ENV_ID;
328
- if (!configEnvId) {
329
- debugLog("Missing OP_CONFIG_ENV_ID");
330
- return;
331
- }
332
-
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
- }
342
-
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
- });
367
-
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
- }
394
- }
395
- }
396
- }
397
- },
398
-
399
- async "shell.env"(input, output) {
400
- if (!opClient || !mcpsEnvId) return;
401
-
402
- const toInject = await injectMCPSecrets(mcpsEnvId);
403
-
404
- for (const [varName, value] of Object.entries(toInject)) {
405
- output.env[varName] = value;
406
- debugLog(`Injected ${varName} into shell environment`);
407
- }
408
- },
409
-
410
- async "tool.execute.before"(input) {
411
- if (input.tool !== "read") return;
412
-
413
- const fileArgs = input.args as { filePath?: string } | undefined;
414
- if (fileArgs?.filePath && fileArgs.filePath.includes(".env")) {
415
- debugLog(
416
- "Warning - .env file access detected. Consider using 1Password for secrets management."
417
- );
418
- }
419
- },
420
- };
421
- };
422
-
423
- export default OnePasswordAuthPlugin;