opencode-1password-auth 1.0.1 → 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 +157 -44
- package/package.json +1 -1
- package/setup.ps1 +196 -3
- package/setup.sh +205 -3
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,8 +1,27 @@
|
|
|
1
1
|
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
2
|
import * as sdk from "@1password/sdk";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
+
};
|
|
6
25
|
|
|
7
26
|
interface MCPServerConfig {
|
|
8
27
|
environment?: Record<string, string>;
|
|
@@ -21,17 +40,71 @@ interface AuthJson {
|
|
|
21
40
|
[providerId: string]: ProviderAuth;
|
|
22
41
|
}
|
|
23
42
|
|
|
24
|
-
const
|
|
25
|
-
const OPENCODE_JSON_PATH = path.join(os.homedir(), ".config", "opencode", "opencode.json");
|
|
26
|
-
|
|
27
|
-
export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
|
|
43
|
+
export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
|
|
28
44
|
let opClient: Awaited<ReturnType<typeof sdk.createClient>> | null = null;
|
|
29
45
|
let mcpsEnvId: string | undefined;
|
|
30
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
|
+
|
|
31
104
|
const initClient = async () => {
|
|
32
105
|
const token = process.env.OP_SERVICE_ACCOUNT_TOKEN;
|
|
33
106
|
if (!token) {
|
|
34
|
-
|
|
107
|
+
debugLog("Missing OP_SERVICE_ACCOUNT_TOKEN");
|
|
35
108
|
return null;
|
|
36
109
|
}
|
|
37
110
|
|
|
@@ -42,7 +115,7 @@ export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
|
|
|
42
115
|
integrationVersion: "1.0.0",
|
|
43
116
|
});
|
|
44
117
|
} catch (err) {
|
|
45
|
-
|
|
118
|
+
debugLog(`Failed to create client: ${err}`);
|
|
46
119
|
return null;
|
|
47
120
|
}
|
|
48
121
|
};
|
|
@@ -52,9 +125,10 @@ export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
|
|
|
52
125
|
|
|
53
126
|
try {
|
|
54
127
|
const { variables } = await opClient.environments.getVariables(envId);
|
|
128
|
+
debugLog(`Read ${variables?.length || 0} variables from environment ${envId}`);
|
|
55
129
|
return variables || [];
|
|
56
130
|
} catch (err) {
|
|
57
|
-
|
|
131
|
+
debugLog(`Failed to read environment ${envId}: ${err}`);
|
|
58
132
|
return [];
|
|
59
133
|
}
|
|
60
134
|
};
|
|
@@ -65,20 +139,28 @@ export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
|
|
|
65
139
|
for (const v of vars) {
|
|
66
140
|
if (v.value) {
|
|
67
141
|
secrets[v.name] = v.value;
|
|
142
|
+
debugLog(`Found secret: ${v.name} (${v.value.length} chars)`);
|
|
68
143
|
}
|
|
69
144
|
}
|
|
70
145
|
return secrets;
|
|
71
146
|
};
|
|
72
147
|
|
|
73
148
|
const updateAuthJson = async (providerEnvId: string): Promise<void> => {
|
|
74
|
-
|
|
75
|
-
console.log("1Password: auth.json not found, skipping");
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
149
|
+
const authJsonPath = getAuthJsonPath();
|
|
78
150
|
|
|
79
151
|
try {
|
|
80
|
-
|
|
81
|
-
const
|
|
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
|
+
|
|
82
164
|
let modified = false;
|
|
83
165
|
|
|
84
166
|
for (const [providerId, authConfig] of Object.entries(auth)) {
|
|
@@ -86,31 +168,40 @@ export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
|
|
|
86
168
|
// Replace hardcoded key with env var reference
|
|
87
169
|
authConfig.key = `{env:${providerId}}`;
|
|
88
170
|
modified = true;
|
|
89
|
-
|
|
171
|
+
debugLog(`Updated auth.json - ${providerId} -> {env:${providerId}}`);
|
|
90
172
|
}
|
|
91
173
|
}
|
|
92
174
|
|
|
93
175
|
if (modified) {
|
|
94
|
-
|
|
95
|
-
|
|
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");
|
|
96
181
|
}
|
|
97
182
|
} catch (err) {
|
|
98
|
-
|
|
183
|
+
debugLog(`Failed to update auth.json: ${err}`);
|
|
99
184
|
}
|
|
100
185
|
};
|
|
101
186
|
|
|
102
187
|
const updateOpenCodeJsonMCP = async (mcpEnvId: string): Promise<void> => {
|
|
103
|
-
|
|
104
|
-
console.log("1Password: opencode.json not found, skipping");
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
188
|
+
const openCodeJsonPath = getOpenCodeJsonPath();
|
|
107
189
|
|
|
108
190
|
try {
|
|
109
|
-
|
|
110
|
-
const
|
|
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
|
+
}
|
|
111
202
|
|
|
112
203
|
if (!config.mcp) {
|
|
113
|
-
|
|
204
|
+
debugLog("No MCP configuration found, skipping");
|
|
114
205
|
return;
|
|
115
206
|
}
|
|
116
207
|
|
|
@@ -123,37 +214,55 @@ export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
|
|
|
123
214
|
// Replace hardcoded value with env var reference
|
|
124
215
|
serverConfig.environment[key] = `{env:${key}}`;
|
|
125
216
|
modified = true;
|
|
126
|
-
|
|
217
|
+
debugLog(`Updated opencode.json - ${serverName}.${key} -> {env:${key}}`);
|
|
127
218
|
}
|
|
128
219
|
}
|
|
129
220
|
}
|
|
130
221
|
}
|
|
131
222
|
|
|
132
223
|
if (modified) {
|
|
133
|
-
|
|
134
|
-
|
|
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");
|
|
135
227
|
}
|
|
136
228
|
} catch (err) {
|
|
137
|
-
|
|
229
|
+
debugLog(`Failed to update opencode.json: ${err}`);
|
|
138
230
|
}
|
|
139
231
|
};
|
|
140
232
|
|
|
141
233
|
const authenticateProviders = async (providerEnvId: string): Promise<void> => {
|
|
142
234
|
if (!opClient) return;
|
|
143
235
|
|
|
236
|
+
debugLog(`Reading provider secrets from environment ${providerEnvId}`);
|
|
144
237
|
const secrets = await getSecretsFromEnvironment(providerEnvId);
|
|
238
|
+
debugLog(`Found ${Object.keys(secrets).length} provider secrets`);
|
|
145
239
|
|
|
146
|
-
for (const [
|
|
240
|
+
for (const [variableName, apiKey] of Object.entries(secrets)) {
|
|
147
241
|
if (!apiKey) continue;
|
|
148
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
|
+
|
|
149
253
|
try {
|
|
150
|
-
|
|
254
|
+
// Authenticate with OpenCode runtime
|
|
255
|
+
debugLog(`Calling client.auth.set for ${providerId}`);
|
|
256
|
+
await client.auth.set({
|
|
151
257
|
path: { id: providerId },
|
|
152
258
|
body: { type: "api", key: apiKey },
|
|
153
259
|
});
|
|
154
|
-
|
|
260
|
+
debugLog(`✓ ${providerId} authenticated (from ${variableName})`);
|
|
155
261
|
} catch (err) {
|
|
156
|
-
|
|
262
|
+
debugLog(`Failed to authenticate ${providerId}: ${err}`);
|
|
263
|
+
if (err instanceof Error) {
|
|
264
|
+
debugLog(`Error message: ${err.message}`);
|
|
265
|
+
}
|
|
157
266
|
}
|
|
158
267
|
}
|
|
159
268
|
};
|
|
@@ -162,7 +271,7 @@ export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
|
|
|
162
271
|
const vars = new Set<string>();
|
|
163
272
|
|
|
164
273
|
try {
|
|
165
|
-
const config =
|
|
274
|
+
const config = client.config as OpenCodeConfig;
|
|
166
275
|
if (config?.mcp) {
|
|
167
276
|
for (const [, serverConfig] of Object.entries(config.mcp)) {
|
|
168
277
|
if (serverConfig?.environment) {
|
|
@@ -178,7 +287,7 @@ export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
|
|
|
178
287
|
}
|
|
179
288
|
}
|
|
180
289
|
} catch (err) {
|
|
181
|
-
|
|
290
|
+
debugLog(`Failed to read MCP config: ${err}`);
|
|
182
291
|
}
|
|
183
292
|
|
|
184
293
|
return vars;
|
|
@@ -204,26 +313,29 @@ export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
|
|
|
204
313
|
|
|
205
314
|
return {
|
|
206
315
|
async event({ event }: { event: { type: string } }) {
|
|
316
|
+
debugLog(`Received event: ${event.type}`);
|
|
207
317
|
if (event.type === "server.connected") {
|
|
208
|
-
|
|
318
|
+
debugLog("Initializing...");
|
|
209
319
|
|
|
210
320
|
opClient = await initClient();
|
|
211
321
|
if (!opClient) {
|
|
212
|
-
|
|
322
|
+
debugLog("No client available");
|
|
213
323
|
return;
|
|
214
324
|
}
|
|
215
325
|
|
|
216
326
|
const configEnvId = process.env.OP_CONFIG_ENV_ID;
|
|
217
327
|
if (!configEnvId) {
|
|
218
|
-
|
|
328
|
+
debugLog("Missing OP_CONFIG_ENV_ID");
|
|
219
329
|
return;
|
|
220
330
|
}
|
|
221
331
|
|
|
332
|
+
debugLog(`Reading config from environment ${configEnvId}`);
|
|
222
333
|
const vars = await readEnvironmentVariables(configEnvId);
|
|
223
334
|
const configEnvIds: Record<string, string> = {};
|
|
224
335
|
for (const v of vars) {
|
|
225
336
|
if (v.name.endsWith("_ENV_ID") && v.value) {
|
|
226
337
|
configEnvIds[v.name] = v.value;
|
|
338
|
+
debugLog(`Found ${v.name} = ${v.value.substring(0, 10)}...`);
|
|
227
339
|
}
|
|
228
340
|
}
|
|
229
341
|
|
|
@@ -240,11 +352,11 @@ export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
|
|
|
240
352
|
await updateOpenCodeJsonMCP(mcpEnvIdFromConfig);
|
|
241
353
|
const toInject = await injectMCPSecrets(mcpEnvIdFromConfig);
|
|
242
354
|
if (Object.keys(toInject).length > 0) {
|
|
243
|
-
|
|
355
|
+
debugLog(`Injected ${Object.keys(toInject).join(", ")} for MCP`);
|
|
244
356
|
}
|
|
245
357
|
}
|
|
246
358
|
|
|
247
|
-
|
|
359
|
+
debugLog("Initialization complete");
|
|
248
360
|
}
|
|
249
361
|
},
|
|
250
362
|
|
|
@@ -255,6 +367,7 @@ export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
|
|
|
255
367
|
|
|
256
368
|
for (const [varName, value] of Object.entries(toInject)) {
|
|
257
369
|
output.env[varName] = value;
|
|
370
|
+
debugLog(`Injected ${varName} into shell environment`);
|
|
258
371
|
}
|
|
259
372
|
},
|
|
260
373
|
|
|
@@ -263,8 +376,8 @@ export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
|
|
|
263
376
|
|
|
264
377
|
const fileArgs = input.args as { filePath?: string } | undefined;
|
|
265
378
|
if (fileArgs?.filePath && fileArgs.filePath.includes(".env")) {
|
|
266
|
-
|
|
267
|
-
"
|
|
379
|
+
debugLog(
|
|
380
|
+
"Warning - .env file access detected. Consider using 1Password for secrets management."
|
|
268
381
|
);
|
|
269
382
|
}
|
|
270
383
|
},
|
package/package.json
CHANGED
package/setup.ps1
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
param(
|
|
5
5
|
[switch]$Uninstall,
|
|
6
|
-
[switch]$Audit
|
|
6
|
+
[switch]$Audit,
|
|
7
|
+
[switch]$UpdateConfig
|
|
7
8
|
)
|
|
8
9
|
|
|
9
10
|
# Colors for output
|
|
@@ -65,6 +66,137 @@ function Remove-RegistryValue {
|
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
function Get-OpenCodeAuthJsonPath {
|
|
70
|
+
$homeDir = if ($env:USERPROFILE) { $env:USERPROFILE } else { $env:USERPROFILE }
|
|
71
|
+
return "$homeDir/.local/share/opencode/auth.json"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function Get-OpenCodeConfigJsonPath {
|
|
75
|
+
$homeDir = if ($env:USERPROFILE) { $env:USERPROFILE } else { $env:USERPROFILE }
|
|
76
|
+
return "$homeDir/.config/opencode/opencode.json"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function Update-ConfigFiles {
|
|
80
|
+
param([string]$Token, [string]$ProvidersEnvId, [string]$McpsEnvId)
|
|
81
|
+
|
|
82
|
+
$nodeModulesPath = Get-OpenCodeNodeModulesPath
|
|
83
|
+
if (-not $nodeModulesPath) {
|
|
84
|
+
Write-Error "Could not find @1password/sdk in any node_modules directory"
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
$sdkPath = ($nodeModulesPath -replace '\\', '/') + "/@1password/sdk/dist/sdk.js"
|
|
89
|
+
|
|
90
|
+
# Get provider secrets from 1Password
|
|
91
|
+
Write-Info "Reading provider secrets from 1Password..."
|
|
92
|
+
|
|
93
|
+
$script = @"
|
|
94
|
+
const sdk = require('${sdkPath}');
|
|
95
|
+
|
|
96
|
+
async function getSecrets() {
|
|
97
|
+
const client = await sdk.createClient({
|
|
98
|
+
auth: '${Token}',
|
|
99
|
+
integrationName: 'opencode-1password-setup',
|
|
100
|
+
integrationVersion: '1.0.0'
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const providers = {};
|
|
104
|
+
const { variables: providerVars } = await client.environments.getVariables('${ProvidersEnvId}');
|
|
105
|
+
for (const v of providerVars) {
|
|
106
|
+
if (v.value) providers[v.name] = v.value;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const mcps = {};
|
|
110
|
+
if ('${McpsEnvId}') {
|
|
111
|
+
const { variables: mcpVars } = await client.environments.getVariables('${McpsEnvId}');
|
|
112
|
+
for (const v of mcpVars) {
|
|
113
|
+
if (v.value) mcps[v.name] = v.value;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
console.log(JSON.stringify({ providers, mcps }));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
getSecrets().catch(err => { console.error('FAILED:', err.message); process.exit(1); });
|
|
121
|
+
"@
|
|
122
|
+
|
|
123
|
+
$tempScript = [System.IO.Path]::GetTempFileName() -replace '\.tmp$', '.js'
|
|
124
|
+
$script | Out-File -FilePath $tempScript -Encoding UTF8 -NoNewline
|
|
125
|
+
|
|
126
|
+
$result = & node $tempScript 2>&1 | Out-String
|
|
127
|
+
Remove-Item $tempScript -ErrorAction SilentlyContinue
|
|
128
|
+
|
|
129
|
+
if ($result -match "FAILED:") {
|
|
130
|
+
Write-Error "Failed to read secrets from 1Password: $($result -replace 'FAILED:', '')"
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
$secrets = $result | ConvertFrom-Json
|
|
135
|
+
|
|
136
|
+
# Update auth.json
|
|
137
|
+
$authJsonPath = Get-OpenCodeAuthJsonPath
|
|
138
|
+
Write-Info "Updating auth.json..."
|
|
139
|
+
|
|
140
|
+
if (Test-Path $authJsonPath) {
|
|
141
|
+
$authContent = Get-Content $authJsonPath -Raw -Encoding UTF8
|
|
142
|
+
$auth = $authContent | ConvertFrom-Json
|
|
143
|
+
|
|
144
|
+
$modified = $false
|
|
145
|
+
foreach ($providerId in $auth.PSObject.Properties.Name) {
|
|
146
|
+
$authConfig = $auth.$providerId
|
|
147
|
+
if ($authConfig.key -and -not $authConfig.key.StartsWith("{env:")) {
|
|
148
|
+
$authConfig.key = "{env:$providerId}"
|
|
149
|
+
$modified = $true
|
|
150
|
+
Write-Success "Updated $providerId -> {env:$providerId}"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if ($modified) {
|
|
155
|
+
$auth | ConvertTo-Json -Depth 10 | Set-Content $authJsonPath -Encoding UTF8 -NoNewline
|
|
156
|
+
Write-Success "auth.json updated"
|
|
157
|
+
} else {
|
|
158
|
+
Write-Info "auth.json already uses environment variable references"
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
Write-Info "auth.json not found at $authJsonPath"
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# Update opencode.json MCP config
|
|
165
|
+
$configJsonPath = Get-OpenCodeConfigJsonPath
|
|
166
|
+
Write-Info "Updating opencode.json MCP config..."
|
|
167
|
+
|
|
168
|
+
if (Test-Path $configJsonPath) {
|
|
169
|
+
$configContent = Get-Content $configJsonPath -Raw -Encoding UTF8
|
|
170
|
+
$config = $configContent | ConvertFrom-Json
|
|
171
|
+
|
|
172
|
+
$modified = $false
|
|
173
|
+
if ($config.mcp) {
|
|
174
|
+
foreach ($serverName in $config.mcp.PSObject.Properties.Name) {
|
|
175
|
+
$serverConfig = $config.mcp.$serverName
|
|
176
|
+
if ($serverConfig.environment) {
|
|
177
|
+
foreach ($key in $serverConfig.environment.PSObject.Properties.Name) {
|
|
178
|
+
$value = $serverConfig.environment.$key
|
|
179
|
+
if ($value -and -not $value.StartsWith("{env:") -and -not $value.StartsWith("$")) {
|
|
180
|
+
$serverConfig.environment.$key = "{env:$key}"
|
|
181
|
+
$modified = $true
|
|
182
|
+
Write-Success "Updated $serverName.$key -> {env:$key}"
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if ($modified) {
|
|
190
|
+
$config | ConvertTo-Json -Depth 10 | Set-Content $configJsonPath -Encoding UTF8 -NoNewline
|
|
191
|
+
Write-Success "opencode.json updated"
|
|
192
|
+
} else {
|
|
193
|
+
Write-Info "opencode.json already uses environment variable references"
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
Write-Info "opencode.json not found at $configJsonPath"
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
68
200
|
function Get-OpenCodeNodeModulesPath {
|
|
69
201
|
$paths = @(
|
|
70
202
|
"$env:USERPROFILE\.cache\opencode\node_modules",
|
|
@@ -320,6 +452,66 @@ if ($Audit) {
|
|
|
320
452
|
exit 0
|
|
321
453
|
}
|
|
322
454
|
|
|
455
|
+
if ($UpdateConfig) {
|
|
456
|
+
# Update Config mode
|
|
457
|
+
Write-Host "Update Config Mode" -ForegroundColor Yellow
|
|
458
|
+
Write-Host "------------------" -ForegroundColor Yellow
|
|
459
|
+
Write-Host ""
|
|
460
|
+
|
|
461
|
+
$token = Get-RegistryValue -Name "OP_SERVICE_ACCOUNT_TOKEN" -Scope "Machine"
|
|
462
|
+
$configId = Get-RegistryValue -Name "OP_CONFIG_ENV_ID" -Scope "Machine"
|
|
463
|
+
|
|
464
|
+
if (-not $token) {
|
|
465
|
+
$token = Get-RegistryValue -Name "OP_SERVICE_ACCOUNT_TOKEN" -Scope "User"
|
|
466
|
+
$configId = Get-RegistryValue -Name "OP_CONFIG_ENV_ID" -Scope "User"
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (-not $token -or -not $configId) {
|
|
470
|
+
Write-Error "Environment variables not set. Run setup first."
|
|
471
|
+
exit 1
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
Write-Info "Testing 1Password connection..."
|
|
475
|
+
|
|
476
|
+
if (Test-1PasswordConnection -Token $token) {
|
|
477
|
+
Write-Success "1Password connection successful!"
|
|
478
|
+
} else {
|
|
479
|
+
Write-Error "Failed to connect to 1Password. Check your service account token."
|
|
480
|
+
exit 1
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
Write-Info "Reading configuration from 1Password..."
|
|
484
|
+
|
|
485
|
+
$envIds = Get-1PasswordAudit -Token $token -ConfigEnvId $configId
|
|
486
|
+
|
|
487
|
+
if ($envIds) {
|
|
488
|
+
$providersEnvId = $null
|
|
489
|
+
$mcpsEnvId = $null
|
|
490
|
+
|
|
491
|
+
foreach ($prop in $envIds.PSObject.Properties) {
|
|
492
|
+
if ($prop.Name -eq "OPENCODE_PROVIDERS_ENV_ID") {
|
|
493
|
+
$providersEnvId = $prop.Value
|
|
494
|
+
} elseif ($prop.Name -eq "OPENCODE_MCPS_ENV_ID") {
|
|
495
|
+
$mcpsEnvId = $prop.Value
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if ($providersEnvId) {
|
|
500
|
+
Write-Info "Updating config files to use environment variables..."
|
|
501
|
+
Update-ConfigFiles -Token $token -ProvidersEnvId $providersEnvId -McpsEnvId $mcpsEnvId
|
|
502
|
+
Write-Success "Config update complete!"
|
|
503
|
+
} else {
|
|
504
|
+
Write-Error "Could not find OPENCODE_PROVIDERS_ENV_ID in bootstrap environment"
|
|
505
|
+
exit 1
|
|
506
|
+
}
|
|
507
|
+
} else {
|
|
508
|
+
Write-Error "Failed to read bootstrap environment."
|
|
509
|
+
exit 1
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
exit 0
|
|
513
|
+
}
|
|
514
|
+
|
|
323
515
|
# Setup mode (default)
|
|
324
516
|
Write-Host "Setup Mode" -ForegroundColor Yellow
|
|
325
517
|
Write-Host "----------" -ForegroundColor Yellow
|
|
@@ -438,6 +630,7 @@ Write-Success "Setup complete!"
|
|
|
438
630
|
Write-Info "Restart OpenCode to activate the plugin."
|
|
439
631
|
Write-Host ""
|
|
440
632
|
Write-Host "Usage:" -ForegroundColor White
|
|
441
|
-
Write-Host " ./setup.ps1 -Audit
|
|
442
|
-
Write-Host " ./setup.ps1 -
|
|
633
|
+
Write-Host " ./setup.ps1 -Audit Show current configuration"
|
|
634
|
+
Write-Host " ./setup.ps1 -UpdateConfig Update config files to use {env:VAR} references"
|
|
635
|
+
Write-Host " ./setup.ps1 -Uninstall Remove environment variables"
|
|
443
636
|
Write-Host ""
|
package/setup.sh
CHANGED
|
@@ -221,9 +221,161 @@ EOFINNER
|
|
|
221
221
|
done
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
update_config_files() {
|
|
225
|
+
local token="$1"
|
|
226
|
+
local providers_env_id="$2"
|
|
227
|
+
local mcps_env_id="$3"
|
|
228
|
+
|
|
229
|
+
log_info "Reading secrets from 1Password..."
|
|
230
|
+
|
|
231
|
+
# Get provider and MCP secrets
|
|
232
|
+
cat > /tmp/update_config_$$.js << 'EOF'
|
|
233
|
+
const sdk = require('@1password/sdk');
|
|
234
|
+
|
|
235
|
+
async function getSecrets() {
|
|
236
|
+
const token = process.argv[1];
|
|
237
|
+
const providersEnvId = process.argv[2];
|
|
238
|
+
const mcpsEnvId = process.argv[3];
|
|
239
|
+
|
|
240
|
+
const client = await sdk.createClient({
|
|
241
|
+
auth: token,
|
|
242
|
+
integrationName: 'opencode-1password-setup',
|
|
243
|
+
integrationVersion: '1.0.0'
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const providers = {};
|
|
247
|
+
const { variables: providerVars } = await client.environments.getVariables(providersEnvId);
|
|
248
|
+
for (const v of providerVars) {
|
|
249
|
+
if (v.value) providers[v.name] = v.value;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const mcps = {};
|
|
253
|
+
if (mcpsEnvId) {
|
|
254
|
+
const { variables: mcpVars } = await client.environments.getVariables(mcpsEnvId);
|
|
255
|
+
for (const v of mcpVars) {
|
|
256
|
+
if (v.value) mcps[v.name] = v.value;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
console.log(JSON.stringify({ providers, mcps }));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
getSecrets().catch(err => {
|
|
264
|
+
console.error('FAILED:', err.message);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
});
|
|
267
|
+
EOF
|
|
268
|
+
|
|
269
|
+
result=$(node /tmp/update_config_$$.js "$token" "$providers_env_id" "$mcps_env_id" 2>&1)
|
|
270
|
+
rm -f /tmp/update_config_$$.js
|
|
271
|
+
|
|
272
|
+
if [[ "$result" == "FAILED:"* ]]; then
|
|
273
|
+
log_error "Failed to read secrets from 1Password: $result"
|
|
274
|
+
return 1
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
# Get home directory
|
|
278
|
+
home="${HOME:-}"
|
|
279
|
+
if [[ -z "$home" ]]; then
|
|
280
|
+
home=$(eval echo ~)
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
# Update auth.json
|
|
284
|
+
auth_json_path="$home/.local/share/opencode/auth.json"
|
|
285
|
+
log_info "Updating auth.json..."
|
|
286
|
+
|
|
287
|
+
if [[ -f "$auth_json_path" ]]; then
|
|
288
|
+
# Read auth.json and update
|
|
289
|
+
auth_content=$(cat "$auth_json_path")
|
|
290
|
+
# Use node to parse and update JSON
|
|
291
|
+
cat > /tmp/update_auth_$$.js << 'EOFJ'
|
|
292
|
+
const fs = require('fs');
|
|
293
|
+
const authPath = process.argv[1];
|
|
294
|
+
const auth = JSON.parse(fs.readFileSync(authPath, 'utf8'));
|
|
295
|
+
|
|
296
|
+
let modified = false;
|
|
297
|
+
for (const [providerId, authConfig] of Object.entries(auth)) {
|
|
298
|
+
if (authConfig.key && !authConfig.key.startsWith('{env:')) {
|
|
299
|
+
authConfig.key = '{env:' + providerId + '}';
|
|
300
|
+
modified = true;
|
|
301
|
+
console.log('Updated ' + providerId + ' -> {env:' + providerId + '}');
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (modified) {
|
|
306
|
+
fs.writeFileSync(authPath, JSON.stringify(auth, null, 2));
|
|
307
|
+
console.log('auth.json updated');
|
|
308
|
+
} else {
|
|
309
|
+
console.log('auth.json already uses environment variable references');
|
|
310
|
+
}
|
|
311
|
+
EOFJ
|
|
312
|
+
|
|
313
|
+
update_result=$(node /tmp/update_auth_$$.js "$auth_json_path" 2>&1)
|
|
314
|
+
rm -f /tmp/update_auth_$$.js
|
|
315
|
+
echo "$update_result" | while read line; do
|
|
316
|
+
if [[ "$line" == Updated* ]] || [[ "$line" == *updated* ]]; then
|
|
317
|
+
log_success "$(echo "$line" | sed 's/Updated/Updated/')"
|
|
318
|
+
else
|
|
319
|
+
log_info "$line"
|
|
320
|
+
fi
|
|
321
|
+
done
|
|
322
|
+
else
|
|
323
|
+
log_info "auth.json not found at $auth_json_path"
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
# Update opencode.json MCP config
|
|
327
|
+
config_json_path="$home/.config/opencode/opencode.json"
|
|
328
|
+
log_info "Updating opencode.json MCP config..."
|
|
329
|
+
|
|
330
|
+
if [[ -f "$config_json_path" ]]; then
|
|
331
|
+
cat > /tmp/update_mcp_$$.js << 'EOFMCP'
|
|
332
|
+
const fs = require('fs');
|
|
333
|
+
const configPath = process.argv[1];
|
|
334
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
335
|
+
|
|
336
|
+
let modified = false;
|
|
337
|
+
if (config.mcp) {
|
|
338
|
+
for (const [serverName, serverConfig] of Object.entries(config.mcp)) {
|
|
339
|
+
if (serverConfig && serverConfig.environment) {
|
|
340
|
+
for (const [key, value] of Object.entries(serverConfig.environment)) {
|
|
341
|
+
if (value && !value.startsWith('{env:') && !value.startsWith('$')) {
|
|
342
|
+
serverConfig.environment[key] = '{env:' + key + '}';
|
|
343
|
+
modified = true;
|
|
344
|
+
console.log('Updated ' + serverName + '.' + key + ' -> {env:' + key + '}');
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (modified) {
|
|
352
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
353
|
+
console.log('opencode.json updated');
|
|
354
|
+
} else {
|
|
355
|
+
console.log('opencode.json already uses environment variable references');
|
|
356
|
+
}
|
|
357
|
+
EOFMCP
|
|
358
|
+
|
|
359
|
+
update_result=$(node /tmp/update_mcp_$$.js "$config_json_path" 2>&1)
|
|
360
|
+
rm -f /tmp/update_mcp_$$.js
|
|
361
|
+
echo "$update_result" | while read line; do
|
|
362
|
+
if [[ "$line" == Updated* ]] || [[ "$line" == *updated* ]]; then
|
|
363
|
+
log_success "$(echo "$line" | sed 's/Updated/Updated/')"
|
|
364
|
+
else
|
|
365
|
+
log_info "$line"
|
|
366
|
+
fi
|
|
367
|
+
done
|
|
368
|
+
else
|
|
369
|
+
log_info "opencode.json not found at $config_json_path"
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
log_success "Config update complete!"
|
|
373
|
+
}
|
|
374
|
+
|
|
224
375
|
# Parse arguments
|
|
225
376
|
UNINSTALL=false
|
|
226
377
|
AUDIT=false
|
|
378
|
+
UPDATE_CONFIG=false
|
|
227
379
|
|
|
228
380
|
while [[ $# -gt 0 ]]; do
|
|
229
381
|
case $1 in
|
|
@@ -235,8 +387,12 @@ while [[ $# -gt 0 ]]; do
|
|
|
235
387
|
AUDIT=true
|
|
236
388
|
shift
|
|
237
389
|
;;
|
|
390
|
+
-c|--update-config)
|
|
391
|
+
UPDATE_CONFIG=true
|
|
392
|
+
shift
|
|
393
|
+
;;
|
|
238
394
|
*)
|
|
239
|
-
echo "Usage: $0 [--uninstall|--audit]"
|
|
395
|
+
echo "Usage: $0 [--uninstall|--audit|--update-config]"
|
|
240
396
|
exit 1
|
|
241
397
|
;;
|
|
242
398
|
esac
|
|
@@ -325,6 +481,51 @@ if [[ "$AUDIT" == true ]]; then
|
|
|
325
481
|
exit 0
|
|
326
482
|
fi
|
|
327
483
|
|
|
484
|
+
if [[ "$UPDATE_CONFIG" == true ]]; then
|
|
485
|
+
echo -e "${YELLOW}Update Config Mode${NC}"
|
|
486
|
+
echo -e "${YELLOW}------------------${NC}"
|
|
487
|
+
echo ""
|
|
488
|
+
|
|
489
|
+
get_existing_values
|
|
490
|
+
|
|
491
|
+
if [[ -z "$OP_SERVICE_ACCOUNT_TOKEN" || -z "$OP_CONFIG_ENV_ID" ]]; then
|
|
492
|
+
log_error "Environment variables not set. Run setup first."
|
|
493
|
+
exit 1
|
|
494
|
+
fi
|
|
495
|
+
|
|
496
|
+
log_info "Testing 1Password connection..."
|
|
497
|
+
|
|
498
|
+
if test_connection "$OP_SERVICE_ACCOUNT_TOKEN"; then
|
|
499
|
+
log_success "1Password connection successful!"
|
|
500
|
+
else
|
|
501
|
+
log_error "Failed to connect to 1Password. Check your service account token."
|
|
502
|
+
exit 1
|
|
503
|
+
fi
|
|
504
|
+
|
|
505
|
+
log_info "Reading configuration from 1Password..."
|
|
506
|
+
|
|
507
|
+
env_ids_json=$(get_audit_data "$OP_SERVICE_ACCOUNT_TOKEN" "$OP_CONFIG_ENV_ID")
|
|
508
|
+
|
|
509
|
+
if [[ -n "$env_ids_json" && "$env_ids_json" != "FAILED:"* ]]; then
|
|
510
|
+
# Parse JSON to extract env IDs
|
|
511
|
+
providers_env_id=$(echo "$env_ids_json" | sed -n 's/.*"OPENCODE_PROVIDERS_ENV_ID":"\([^"]*\)".*/\1/p')
|
|
512
|
+
mcps_env_id=$(echo "$env_ids_json" | sed -n 's/.*"OPENCODE_MCPS_ENV_ID":"\([^"]*\)".*/\1/p')
|
|
513
|
+
|
|
514
|
+
if [[ -n "$providers_env_id" ]]; then
|
|
515
|
+
log_info "Updating config files to use environment variables..."
|
|
516
|
+
update_config_files "$OP_SERVICE_ACCOUNT_TOKEN" "$providers_env_id" "$mcps_env_id"
|
|
517
|
+
else
|
|
518
|
+
log_error "Could not find OPENCODE_PROVIDERS_ENV_ID in bootstrap environment"
|
|
519
|
+
exit 1
|
|
520
|
+
fi
|
|
521
|
+
else
|
|
522
|
+
log_error "Failed to read bootstrap environment."
|
|
523
|
+
exit 1
|
|
524
|
+
fi
|
|
525
|
+
|
|
526
|
+
exit 0
|
|
527
|
+
fi
|
|
528
|
+
|
|
328
529
|
# Setup mode (default)
|
|
329
530
|
echo -e "${YELLOW}Setup Mode${NC}"
|
|
330
531
|
echo -e "${YELLOW}----------${NC}"
|
|
@@ -439,6 +640,7 @@ log_success "Setup complete!"
|
|
|
439
640
|
log_info "Restart OpenCode to activate the plugin."
|
|
440
641
|
echo ""
|
|
441
642
|
echo "Usage:"
|
|
442
|
-
echo " ./setup.sh --audit
|
|
443
|
-
echo " ./setup.sh --
|
|
643
|
+
echo " ./setup.sh --audit Show current configuration"
|
|
644
|
+
echo " ./setup.sh --update-config Update config files to use {env:VAR} references"
|
|
645
|
+
echo " ./setup.sh --uninstall Remove environment variables"
|
|
444
646
|
echo ""
|