opencode-1password-auth 1.0.3 → 1.0.5
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 +6 -4
- package/index-v2.ts +387 -0
- package/index.ts +111 -23
- package/package.json +1 -1
- package/setup.ps1 +4 -4
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
|
-
-
|
|
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
|
-
├──
|
|
101
|
-
├──
|
|
102
|
-
└──
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
180
|
+
debugLog("auth.json updated to use environment variables");
|
|
114
181
|
}
|
|
115
182
|
} catch (err) {
|
|
116
|
-
|
|
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
|
-
|
|
199
|
+
debugLog("opencode.json is not valid JSON, skipping");
|
|
133
200
|
return;
|
|
134
201
|
}
|
|
135
202
|
|
|
136
203
|
if (!config.mcp) {
|
|
137
|
-
|
|
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
|
-
|
|
217
|
+
debugLog(`Updated opencode.json - ${serverName}.${key} -> {env:${key}}`);
|
|
151
218
|
}
|
|
152
219
|
}
|
|
153
220
|
}
|
|
@@ -156,29 +223,46 @@ 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
|
-
|
|
226
|
+
debugLog("opencode.json MCP config updated to use environment variables");
|
|
160
227
|
}
|
|
161
228
|
} catch (err) {
|
|
162
|
-
|
|
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 [
|
|
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 {
|
|
254
|
+
// Authenticate with OpenCode runtime
|
|
255
|
+
debugLog(`Calling client.auth.set for ${providerId}`);
|
|
175
256
|
await client.auth.set({
|
|
176
257
|
path: { id: providerId },
|
|
177
258
|
body: { type: "api", key: apiKey },
|
|
178
259
|
});
|
|
179
|
-
|
|
260
|
+
debugLog(`✓ ${providerId} authenticated (from ${variableName})`);
|
|
180
261
|
} catch (err) {
|
|
181
|
-
|
|
262
|
+
debugLog(`Failed to authenticate ${providerId}: ${err}`);
|
|
263
|
+
if (err instanceof Error) {
|
|
264
|
+
debugLog(`Error message: ${err.message}`);
|
|
265
|
+
}
|
|
182
266
|
}
|
|
183
267
|
}
|
|
184
268
|
};
|
|
@@ -203,7 +287,7 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
|
|
|
203
287
|
}
|
|
204
288
|
}
|
|
205
289
|
} catch (err) {
|
|
206
|
-
|
|
290
|
+
debugLog(`Failed to read MCP config: ${err}`);
|
|
207
291
|
}
|
|
208
292
|
|
|
209
293
|
return vars;
|
|
@@ -229,26 +313,29 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
|
|
|
229
313
|
|
|
230
314
|
return {
|
|
231
315
|
async event({ event }: { event: { type: string } }) {
|
|
316
|
+
debugLog(`Received event: ${event.type}`);
|
|
232
317
|
if (event.type === "server.connected") {
|
|
233
|
-
|
|
318
|
+
debugLog("Initializing...");
|
|
234
319
|
|
|
235
320
|
opClient = await initClient();
|
|
236
321
|
if (!opClient) {
|
|
237
|
-
|
|
322
|
+
debugLog("No client available");
|
|
238
323
|
return;
|
|
239
324
|
}
|
|
240
325
|
|
|
241
326
|
const configEnvId = process.env.OP_CONFIG_ENV_ID;
|
|
242
327
|
if (!configEnvId) {
|
|
243
|
-
|
|
328
|
+
debugLog("Missing OP_CONFIG_ENV_ID");
|
|
244
329
|
return;
|
|
245
330
|
}
|
|
246
331
|
|
|
332
|
+
debugLog(`Reading config from environment ${configEnvId}`);
|
|
247
333
|
const vars = await readEnvironmentVariables(configEnvId);
|
|
248
334
|
const configEnvIds: Record<string, string> = {};
|
|
249
335
|
for (const v of vars) {
|
|
250
336
|
if (v.name.endsWith("_ENV_ID") && v.value) {
|
|
251
337
|
configEnvIds[v.name] = v.value;
|
|
338
|
+
debugLog(`Found ${v.name} = ${v.value.substring(0, 10)}...`);
|
|
252
339
|
}
|
|
253
340
|
}
|
|
254
341
|
|
|
@@ -265,11 +352,11 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
|
|
|
265
352
|
await updateOpenCodeJsonMCP(mcpEnvIdFromConfig);
|
|
266
353
|
const toInject = await injectMCPSecrets(mcpEnvIdFromConfig);
|
|
267
354
|
if (Object.keys(toInject).length > 0) {
|
|
268
|
-
|
|
355
|
+
debugLog(`Injected ${Object.keys(toInject).join(", ")} for MCP`);
|
|
269
356
|
}
|
|
270
357
|
}
|
|
271
358
|
|
|
272
|
-
|
|
359
|
+
debugLog("Initialization complete");
|
|
273
360
|
}
|
|
274
361
|
},
|
|
275
362
|
|
|
@@ -280,6 +367,7 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
|
|
|
280
367
|
|
|
281
368
|
for (const [varName, value] of Object.entries(toInject)) {
|
|
282
369
|
output.env[varName] = value;
|
|
370
|
+
debugLog(`Injected ${varName} into shell environment`);
|
|
283
371
|
}
|
|
284
372
|
},
|
|
285
373
|
|
|
@@ -288,8 +376,8 @@ export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
|
|
|
288
376
|
|
|
289
377
|
const fileArgs = input.args as { filePath?: string } | undefined;
|
|
290
378
|
if (fileArgs?.filePath && fileArgs.filePath.includes(".env")) {
|
|
291
|
-
|
|
292
|
-
"
|
|
379
|
+
debugLog(
|
|
380
|
+
"Warning - .env file access detected. Consider using 1Password for secrets management."
|
|
293
381
|
);
|
|
294
382
|
}
|
|
295
383
|
},
|
package/package.json
CHANGED
package/setup.ps1
CHANGED
|
@@ -67,13 +67,13 @@ function Remove-RegistryValue {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
function Get-OpenCodeAuthJsonPath {
|
|
70
|
-
$
|
|
71
|
-
return "$
|
|
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
|
-
$
|
|
76
|
-
return "$
|
|
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 {
|