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 +6 -4
- package/index-v2.ts +387 -0
- package/index.ts +179 -55
- 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,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
|
-
|
|
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 {
|
|
175
|
-
|
|
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
|
-
|
|
260
|
+
debugLog(`✓ ${providerId} authenticated (from ${variableName}) - Result: ${JSON.stringify(result)}`);
|
|
180
261
|
} catch (err) {
|
|
181
|
-
|
|
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
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
321
|
+
opClient = await initClient();
|
|
322
|
+
if (!opClient) {
|
|
323
|
+
debugLog("No client available");
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
240
326
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
292
|
-
"
|
|
415
|
+
debugLog(
|
|
416
|
+
"Warning - .env file access detected. Consider using 1Password for secrets management."
|
|
293
417
|
);
|
|
294
418
|
}
|
|
295
419
|
},
|
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 {
|