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