opencode-1password-auth 1.0.0

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.
Files changed (5) hide show
  1. package/README.md +174 -0
  2. package/index.ts +188 -0
  3. package/package.json +13 -0
  4. package/setup.ps1 +421 -0
  5. package/setup.sh +444 -0
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # OpenCode 1Password Auth Plugin
2
+
3
+ Authenticate LLM providers and inject MCP server secrets from 1Password environments.
4
+
5
+ ## Features
6
+
7
+ - **Provider Authentication**: Automatically authenticate OpenCode providers (MiniMax, DeepSeek, OpenCode, etc.) from 1Password
8
+ - **MCP Secret Injection**: Inject secrets into MCP server environments from 1Password
9
+ - **.env Access Warning**: Warns when .env files are accessed
10
+
11
+ ## Prerequisites
12
+
13
+ ### 1. 1Password Account
14
+
15
+ - 1Password account (西牙, Business, or Teams plan for service accounts)
16
+ - Ability to create environments and service accounts
17
+
18
+ ### 2. 1Password Service Account
19
+
20
+ Create a service account in 1Password:
21
+ 1. Go to **Settings > Service Accounts** in 1Password
22
+ 2. Create a new service account with **Read** permissions to your vaults
23
+ 3. Copy the token - you'll need it for `OP_SERVICE_ACCOUNT_TOKEN`
24
+
25
+ ### 3. 1Password Environments
26
+
27
+ Create these environments in 1Password:
28
+
29
+ **Config Environment** (holds environment IDs):
30
+ - `OPENCODE_PROVIDERS_ENV_ID` - Environment ID containing provider API keys
31
+ - `OPENCODE_MCPS_ENV_ID` - Environment ID containing MCP server secrets
32
+
33
+ **Providers Environment** (holds API keys):
34
+ - Provider IDs as variable names (e.g., `MINIMAX_CODING_PLAN`, `DEEPSEEK`, `OPENCODE`)
35
+ - API keys as values
36
+
37
+ **MCPS Environment** (holds MCP secrets):
38
+ - Secret names matching your MCP server config (e.g., `MINIMAX_API_KEY`, `MINIMAX_API_HOST`)
39
+
40
+ ## Setup
41
+
42
+ ### 1. Run Setup Script
43
+
44
+ Use the provided setup script to configure environment variables:
45
+
46
+ **Windows:**
47
+ ```powershell
48
+ .\setup.ps1
49
+ ```
50
+
51
+ **macOS/Linux:**
52
+ ```bash
53
+ ./setup.sh
54
+ ```
55
+
56
+ The script will:
57
+ - Prompt for your 1Password service account token
58
+ - Prompt for your bootstrap environment ID
59
+ - Save environment variables to your system
60
+ - Verify the 1Password connection works
61
+ - Show a full audit of your configuration
62
+
63
+ **Script Options:**
64
+ - `.\setup.ps1 -Audit` - View current configuration
65
+ - `.\setup.ps1 -Uninstall` - Remove environment variables
66
+
67
+ ### 2. Install Plugin
68
+
69
+ Add to your `opencode.json`:
70
+
71
+ ```json
72
+ {
73
+ "plugin": ["opencode-1password-auth"]
74
+ }
75
+ ```
76
+
77
+ OpenCode will automatically install the plugin on next startup.
78
+
79
+ ## Architecture: Config as Index
80
+
81
+ The plugin uses a **config environment as an index** to your other environments. This design:
82
+
83
+ - Keeps your actual secrets isolated in dedicated environments
84
+ - Allows you to switch configurations by changing only `OP_CONFIG_ENV_ID`
85
+ - Enables different configs for dev/staging/prod
86
+
87
+ ### Config Environment
88
+ ```
89
+ opencode-config
90
+ ├── OPENCODE_PROVIDERS_ENV_ID = abc123... (ID of providers env)
91
+ ├── OPENCODE_MCPS_ENV_ID = def456... (ID of mcps env)
92
+ └── OPENCODE_PLUGINS_ENV_ID = ghi789... (ID of plugins env, optional)
93
+ ```
94
+
95
+ To change configurations, simply update `OP_CONFIG_ENV_ID` to point to a different config environment.
96
+
97
+ ### Providers Environment
98
+ ```
99
+ opencode-providers
100
+ ├── MINIMAX_CODING_PLAN = your-minimax-api-key
101
+ ├── DEEPSEEK = your-deepseek-api-key
102
+ └── OPENCODE = your-opencode-api-key
103
+ ```
104
+
105
+ ### MCPS Environment
106
+ ```
107
+ opencode-mcps
108
+ ├── MINIMAX_API_KEY = your-minimax-api-key
109
+ └── MINIMAX_API_HOST = https://api.minimax.io
110
+ ```
111
+
112
+ ## MCP Configuration
113
+
114
+ In your `opencode.json`, use `{env:VARIABLE_NAME}` syntax to reference injected secrets:
115
+
116
+ ```json
117
+ {
118
+ "mcp": {
119
+ "MiniMax": {
120
+ "type": "local",
121
+ "command": ["uvx", "minimax-coding-plan-mcp"],
122
+ "environment": {
123
+ "MINIMAX_API_KEY": "{env:MINIMAX_API_KEY}",
124
+ "MINIMAX_API_HOST": "{env:MINIMAX_API_HOST}"
125
+ }
126
+ }
127
+ }
128
+ }
129
+ ```
130
+
131
+ ## How It Works
132
+
133
+ 1. On `server.connected` event, the plugin initializes the 1Password SDK
134
+ 2. Reads environment IDs from your config environment
135
+ 3. Authenticates providers using `client.auth.set()`
136
+ 4. Injects MCP secrets via the `shell.env` hook
137
+
138
+ ## Requirements
139
+
140
+ - OpenCode
141
+ - 1Password account with service account token
142
+ - Windows/macOS/Linux with environment variables set
143
+
144
+ ## Troubleshooting
145
+
146
+ **Plugin not loading?**
147
+ - Ensure environment variables are set system-wide (not just in terminal)
148
+ - Restart OpenCode completely after setting environment variables
149
+
150
+ **Can't read 1Password environments?**
151
+ - Verify service account has access to the vaults containing your environments
152
+ - Check that environment IDs in config are correct
153
+
154
+ ## Roadmap
155
+
156
+ The following features are planned for future releases:
157
+
158
+ ### Phase 2 - Enhanced Scripting
159
+ - [ ] **Automated service account creation** - Guide users through creating service accounts with correct permissions via 1Password API
160
+ - [ ] **Bootstrap environment management via scripts** - Add, list, remove environment references from the bootstrap environment through CLI
161
+
162
+ ### Phase 3 - Advanced Plugin Features
163
+ - [ ] **Dynamic environment detection** - Plugin automatically detects new environments added to bootstrap and integrates them
164
+ - [ ] **Config file injection** - Plugin replaces hardcoded API keys in OpenCode config files with `{env:VAR}` references
165
+ - [ ] **Custom environment support** - Support for additional environments beyond the standard opencode-providers, opencode-plugins, opencode-mcps pattern
166
+ - [ ] **Write support** - Ability to store secrets back to 1Password environments
167
+
168
+ ### Phase 4 - polish
169
+ - [ ] **Uninstall script** - Clean removal of environment variables and plugin configuration
170
+ - [ ] **Configuration validation** - Validate OpenCode config files reference correct environment variables
171
+
172
+ ## License
173
+
174
+ MIT
package/index.ts ADDED
@@ -0,0 +1,188 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ import * as sdk from "@1password/sdk";
3
+
4
+ interface MCPServerConfig {
5
+ environment?: Record<string, string>;
6
+ }
7
+
8
+ interface OpenCodeConfig {
9
+ mcp?: Record<string, MCPServerConfig>;
10
+ }
11
+
12
+ export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
13
+ let opClient: Awaited<ReturnType<typeof sdk.createClient>> | null = null;
14
+ let mcpsEnvId: string | undefined;
15
+
16
+ const initClient = async () => {
17
+ const token = process.env.OP_SERVICE_ACCOUNT_TOKEN;
18
+ if (!token) {
19
+ console.error("1Password: Missing OP_SERVICE_ACCOUNT_TOKEN");
20
+ return null;
21
+ }
22
+
23
+ try {
24
+ return await sdk.createClient({
25
+ auth: token,
26
+ integrationName: "opencode-1password-env",
27
+ integrationVersion: "1.0.0",
28
+ });
29
+ } catch (err) {
30
+ console.error("1Password: Failed to create client:", err);
31
+ return null;
32
+ }
33
+ };
34
+
35
+ const readEnvironmentVariables = async (envId: string) => {
36
+ if (!opClient) return [];
37
+
38
+ try {
39
+ const { variables } = await opClient.environments.getVariables(envId);
40
+ return variables || [];
41
+ } catch (err) {
42
+ console.error(`1Password: Failed to read environment ${envId}:`, err);
43
+ return [];
44
+ }
45
+ };
46
+
47
+ const getSecretsFromEnvironment = async (envId: string): Promise<Record<string, string>> => {
48
+ const vars = await readEnvironmentVariables(envId);
49
+ const secrets: Record<string, string> = {};
50
+ for (const v of vars) {
51
+ if (v.value) {
52
+ secrets[v.name] = v.value;
53
+ }
54
+ }
55
+ return secrets;
56
+ };
57
+
58
+ const authenticateProviders = async (providerEnvId: string): Promise<void> => {
59
+ if (!opClient) return;
60
+
61
+ const secrets = await getSecretsFromEnvironment(providerEnvId);
62
+
63
+ for (const [providerId, apiKey] of Object.entries(secrets)) {
64
+ if (!apiKey) continue;
65
+
66
+ try {
67
+ await ctx.client.auth.set({
68
+ path: { id: providerId },
69
+ body: { type: "api", key: apiKey },
70
+ });
71
+ console.log(`1Password: ✓ ${providerId} authenticated`);
72
+ } catch (err) {
73
+ console.error(`1Password: Failed to authenticate ${providerId}:`, err);
74
+ }
75
+ }
76
+ };
77
+
78
+ const getConfiguredMCPVars = (): Set<string> => {
79
+ const vars = new Set<string>();
80
+
81
+ try {
82
+ const config = ctx.config as OpenCodeConfig;
83
+ if (config?.mcp) {
84
+ for (const [, serverConfig] of Object.entries(config.mcp)) {
85
+ if (serverConfig?.environment) {
86
+ for (const [key] of Object.entries(serverConfig.environment)) {
87
+ if (key.startsWith("{env:")) {
88
+ const envVar = key.slice(5, -1);
89
+ vars.add(envVar);
90
+ } else {
91
+ vars.add(key);
92
+ }
93
+ }
94
+ }
95
+ }
96
+ }
97
+ } catch (err) {
98
+ console.error("1Password: Failed to read MCP config:", err);
99
+ }
100
+
101
+ return vars;
102
+ };
103
+
104
+ const injectMCPSecrets = async (mcpEnvId: string): Promise<Record<string, string>> => {
105
+ const neededVars = getConfiguredMCPVars();
106
+ if (neededVars.size === 0) {
107
+ return {};
108
+ }
109
+
110
+ const secrets = await getSecretsFromEnvironment(mcpEnvId);
111
+ const toInject: Record<string, string> = {};
112
+
113
+ for (const varName of neededVars) {
114
+ if (secrets[varName]) {
115
+ toInject[varName] = secrets[varName];
116
+ }
117
+ }
118
+
119
+ return toInject;
120
+ };
121
+
122
+ return {
123
+ async event({ event }: { event: { type: string } }) {
124
+ if (event.type === "server.connected") {
125
+ console.log("1Password: Initializing...");
126
+
127
+ opClient = await initClient();
128
+ if (!opClient) {
129
+ console.error("1Password: No client available");
130
+ return;
131
+ }
132
+
133
+ const configEnvId = process.env.OP_CONFIG_ENV_ID;
134
+ if (!configEnvId) {
135
+ console.error("1Password: Missing OP_CONFIG_ENV_ID");
136
+ return;
137
+ }
138
+
139
+ const vars = await readEnvironmentVariables(configEnvId);
140
+ const configEnvIds: Record<string, string> = {};
141
+ for (const v of vars) {
142
+ if (v.name.endsWith("_ENV_ID") && v.value) {
143
+ configEnvIds[v.name] = v.value;
144
+ }
145
+ }
146
+
147
+ const providersEnvId = configEnvIds.OPENCODE_PROVIDERS_ENV_ID;
148
+ if (providersEnvId) {
149
+ await authenticateProviders(providersEnvId);
150
+ }
151
+
152
+ const mcpEnvIdFromConfig = configEnvIds.OPENCODE_MCPS_ENV_ID;
153
+ if (mcpEnvIdFromConfig) {
154
+ mcpsEnvId = mcpEnvIdFromConfig;
155
+ const toInject = await injectMCPSecrets(mcpEnvIdFromConfig);
156
+ if (Object.keys(toInject).length > 0) {
157
+ console.log(`1Password: Injected ${Object.keys(toInject).join(", ")} for MCP`);
158
+ }
159
+ }
160
+
161
+ console.log("1Password: Initialization complete");
162
+ }
163
+ },
164
+
165
+ async "shell.env"(input, output) {
166
+ if (!opClient || !mcpsEnvId) return;
167
+
168
+ const toInject = await injectMCPSecrets(mcpsEnvId);
169
+
170
+ for (const [varName, value] of Object.entries(toInject)) {
171
+ output.env[varName] = value;
172
+ }
173
+ },
174
+
175
+ async "tool.execute.before"(input) {
176
+ if (input.tool !== "read") return;
177
+
178
+ const fileArgs = input.args as { filePath?: string } | undefined;
179
+ if (fileArgs?.filePath && fileArgs.filePath.includes(".env")) {
180
+ console.warn(
181
+ "1Password: Warning - .env file access detected. Consider using 1Password for secrets management."
182
+ );
183
+ }
184
+ },
185
+ };
186
+ };
187
+
188
+ export default OnePasswordAuthPlugin;
package/package.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "opencode-1password-auth",
3
+ "version": "1.0.0",
4
+ "description": "1Password integration for OpenCode - authenticate providers and inject MCP secrets from 1Password environments",
5
+ "type": "module",
6
+ "main": "index.ts",
7
+ "keywords": ["opencode", "opencode-plugin", "1password", "mcp", "secrets"],
8
+ "author": "",
9
+ "license": "MIT",
10
+ "dependencies": {
11
+ "@1password/sdk": "0.4.1-beta.1"
12
+ }
13
+ }
package/setup.ps1 ADDED
@@ -0,0 +1,421 @@
1
+ # OpenCode 1Password Auth Plugin - Setup Script
2
+ # This script helps configure the environment variables needed for the plugin
3
+
4
+ param(
5
+ [switch]$Uninstall,
6
+ [switch]$Audit
7
+ )
8
+
9
+ # Colors for output
10
+ function Write-Banner {
11
+ Write-Host ""
12
+ Write-Host "========================================" -ForegroundColor Cyan
13
+ Write-Host " OpenCode 1Password Auth Setup" -ForegroundColor Cyan
14
+ Write-Host "========================================" -ForegroundColor Cyan
15
+ Write-Host ""
16
+ }
17
+
18
+ function Write-Success {
19
+ param([string]$Message)
20
+ Write-Host "[OK] " -ForegroundColor Green -NoNewline
21
+ Write-Host $Message
22
+ }
23
+
24
+ function Write-Error {
25
+ param([string]$Message)
26
+ Write-Host "[ERROR] " -ForegroundColor Red -NoNewline
27
+ Write-Host $Message
28
+ }
29
+
30
+ function Write-Info {
31
+ param([string]$Message)
32
+ Write-Host "[INFO] " -ForegroundColor Yellow -NoNewline
33
+ Write-Host $Message
34
+ }
35
+
36
+ function Get-RegistryValue {
37
+ param([string]$Name, [string]$Scope)
38
+
39
+ if ($Scope -eq "Machine") {
40
+ return [System.Environment]::GetEnvironmentVariable($Name, "Machine")
41
+ } else {
42
+ return [System.Environment]::GetEnvironmentVariable($Name, "User")
43
+ }
44
+ }
45
+
46
+ function Set-RegistryValue {
47
+ param([string]$Name, [string]$Value, [string]$Scope)
48
+
49
+ try {
50
+ [System.Environment]::SetEnvironmentVariable($Name, $Value, $Scope)
51
+ return $true
52
+ } catch {
53
+ return $false
54
+ }
55
+ }
56
+
57
+ function Remove-RegistryValue {
58
+ param([string]$Name, [string]$Scope)
59
+
60
+ try {
61
+ [System.Environment]::SetEnvironmentVariable($Name, $null, $Scope)
62
+ return $true
63
+ } catch {
64
+ return $false
65
+ }
66
+ }
67
+
68
+ function Test-1PasswordConnection {
69
+ param([string]$Token)
70
+
71
+ try {
72
+ # Create a temporary Node.js script to test the SDK
73
+ $testScript = @"
74
+ const sdk = require('@1password/sdk');
75
+
76
+ async function test() {
77
+ const client = await sdk.createClient({
78
+ auth: '$Token',
79
+ integrationName: 'opencode-1password-setup-test',
80
+ integrationVersion: '1.0.0'
81
+ });
82
+
83
+ console.log('SUCCESS');
84
+ }
85
+
86
+ test().catch(err => {
87
+ console.error('FAILED:', err.message);
88
+ process.exit(1);
89
+ });
90
+ "@
91
+
92
+ $result = node -e $testScript 2>&1
93
+ return $result -match "SUCCESS"
94
+ } catch {
95
+ return $false
96
+ }
97
+ }
98
+
99
+ function Get-1PasswordAudit {
100
+ param([string]$Token, [string]$ConfigEnvId)
101
+
102
+ try {
103
+ $script = @"
104
+ const sdk = require('@1password/sdk');
105
+
106
+ async function audit() {
107
+ const client = await sdk.createClient({
108
+ auth: '$Token',
109
+ integrationName: 'opencode-1password-setup-test',
110
+ integrationVersion: '1.0.0'
111
+ });
112
+
113
+ // Read bootstrap environment
114
+ const { variables: configVars } = await client.environments.getVariables('$ConfigEnvId');
115
+
116
+ const envIds = {};
117
+ for (const v of configVars) {
118
+ if (v.name.endsWith('_ENV_ID') && v.value) {
119
+ envIds[v.name] = v.value;
120
+ }
121
+ }
122
+
123
+ console.log(JSON.stringify(envIds));
124
+ }
125
+
126
+ audit().catch(err => {
127
+ console.error('FAILED:', err.message);
128
+ process.exit(1);
129
+ });
130
+ "@
131
+
132
+ $jsonResult = node -e $script 2>&1 | Out-String
133
+ return $jsonResult | ConvertFrom-Json
134
+ } catch {
135
+ return $null
136
+ }
137
+ }
138
+
139
+ function Show-AuditReport {
140
+ param([string]$Token, [hashtable]$EnvIds)
141
+
142
+ Write-Host ""
143
+ Write-Host "========================================" -ForegroundColor Cyan
144
+ Write-Host " Configuration Audit Report" -ForegroundColor Cyan
145
+ Write-Host "========================================" -ForegroundColor Cyan
146
+ Write-Host ""
147
+
148
+ # Display bootstrap environment IDs
149
+ Write-Host "Bootstrap Environment References:" -ForegroundColor White
150
+ Write-Host "--------------------------------"
151
+
152
+ foreach ($key in $EnvIds.Keys) {
153
+ Write-Host " $($key) = $($EnvIds[$key].Substring(0, 10))..." -ForegroundColor Gray
154
+ }
155
+ Write-Host ""
156
+
157
+ # For each referenced environment, show its contents
158
+ foreach ($key in $EnvIds.Keys) {
159
+ $envId = $EnvIds[$key]
160
+ $envName = ($key -replace 'OPENCODE_', '') -replace '_ENV_ID', ''
161
+
162
+ Write-Host "$envName Environment:" -ForegroundColor White
163
+ Write-Host "--------------------------------"
164
+
165
+ try {
166
+ $script = @"
167
+ const sdk = require('@1password/sdk');
168
+
169
+ async function read() {
170
+ const client = await sdk.createClient({
171
+ auth: '$Token',
172
+ integrationName: 'opencode-1password-setup-test',
173
+ integrationVersion: '1.0.0'
174
+ });
175
+
176
+ const { variables } = await client.environments.getVariables('$envId');
177
+
178
+ for (const v of variables) {
179
+ const masked = v.value ? v.value.substring(0, 8) + '••••••••' : '(empty)';
180
+ console.log(v.name + '=' + masked);
181
+ }
182
+ }
183
+
184
+ read().catch(err => {
185
+ console.error('Error:', err.message);
186
+ });
187
+ "@
188
+
189
+ $result = node -e $script 2>&1
190
+ foreach ($line in $result) {
191
+ if ($line -and $line -notmatch "^(Error|Error:)") {
192
+ Write-Host " $line" -ForegroundColor Gray
193
+ } elseif ($line -match "Error:") {
194
+ Write-Host " $line" -ForegroundColor Red
195
+ }
196
+ }
197
+ } catch {
198
+ Write-Host " Could not read environment" -ForegroundColor Red
199
+ }
200
+ Write-Host ""
201
+ }
202
+ }
203
+
204
+ # Main script execution
205
+ Write-Banner
206
+
207
+ if ($Uninstall) {
208
+ # Uninstall mode
209
+ Write-Host "Uninstall Mode" -ForegroundColor Yellow
210
+ Write-Host "--------------" -ForegroundColor Yellow
211
+ Write-Host ""
212
+
213
+ $currentToken = Get-RegistryValue -Name "OP_SERVICE_ACCOUNT_TOKEN" -Scope "Machine"
214
+ $currentConfigId = Get-RegistryValue -Name "OP_CONFIG_ENV_ID" -Scope "Machine"
215
+
216
+ if (-not $currentToken -and -not $currentConfigId) {
217
+ $currentToken = Get-RegistryValue -Name "OP_SERVICE_ACCOUNT_TOKEN" -Scope "User"
218
+ $currentConfigId = Get-RegistryValue -Name "OP_CONFIG_ENV_ID" -Scope "User"
219
+ }
220
+
221
+ if (-not $currentToken -and -not $currentConfigId) {
222
+ Write-Info "No environment variables found. Nothing to uninstall."
223
+ exit 0
224
+ }
225
+
226
+ Write-Host "Found the following environment variables:" -ForegroundColor White
227
+ if ($currentToken) { Write-Host " OP_SERVICE_ACCOUNT_TOKEN = $($currentToken.Substring(0, 8))..." -ForegroundColor Gray }
228
+ if ($currentConfigId) { Write-Host " OP_CONFIG_ENV_ID = $($currentConfigId.Substring(0, 8))..." -ForegroundColor Gray }
229
+ Write-Host ""
230
+
231
+ $confirm = Read-Host "Remove these environment variables? (y/N)"
232
+
233
+ if ($confirm -eq 'y' -or $confirm -eq 'Y') {
234
+ $removed = 0
235
+
236
+ if ($currentToken) {
237
+ if (Remove-RegistryValue -Name "OP_SERVICE_ACCOUNT_TOKEN" -Scope "Machine") { $removed++ }
238
+ if (Remove-RegistryValue -Name "OP_SERVICE_ACCOUNT_TOKEN" -Scope "User") { $removed++ }
239
+ }
240
+
241
+ if ($currentConfigId) {
242
+ if (Remove-RegistryValue -Name "OP_CONFIG_ENV_ID" -Scope "Machine") { $removed++ }
243
+ if (Remove-RegistryValue -Name "OP_CONFIG_ENV_ID" -Scope "User") { $removed++ }
244
+ }
245
+
246
+ Write-Success "Removed $removed environment variable(s)."
247
+ Write-Info "Please restart any OpenCode sessions for changes to take effect."
248
+ } else {
249
+ Write-Info "Uninstall cancelled."
250
+ }
251
+
252
+ exit 0
253
+ }
254
+
255
+ if ($Audit) {
256
+ # Audit mode
257
+ Write-Host "Audit Mode" -ForegroundColor Yellow
258
+ Write-Host "----------" -ForegroundColor Yellow
259
+ Write-Host ""
260
+
261
+ $token = Get-RegistryValue -Name "OP_SERVICE_ACCOUNT_TOKEN" -Scope "Machine"
262
+ $configId = Get-RegistryValue -Name "OP_CONFIG_ENV_ID" -Scope "Machine"
263
+
264
+ if (-not $token) {
265
+ $token = Get-RegistryValue -Name "OP_SERVICE_ACCOUNT_TOKEN" -Scope "User"
266
+ $configId = Get-RegistryValue -Name "OP_CONFIG_ENV_ID" -Scope "User"
267
+ }
268
+
269
+ if (-not $token -or -not $configId) {
270
+ Write-Error "Environment variables not set. Run setup first."
271
+ exit 1
272
+ }
273
+
274
+ Write-Info "Testing 1Password connection..."
275
+
276
+ if (Test-1PasswordConnection -Token $token) {
277
+ Write-Success "1Password connection successful!"
278
+ } else {
279
+ Write-Error "Failed to connect to 1Password. Check your service account token."
280
+ exit 1
281
+ }
282
+
283
+ Write-Info "Reading configuration from 1Password..."
284
+
285
+ $envIds = Get-1PasswordAudit -Token $token -ConfigEnvId $configId
286
+
287
+ if ($envIds) {
288
+ $hashtable = @{}
289
+ foreach ($prop in $envIds.PSObject.Properties) {
290
+ $hashtable[$prop.Name] = $prop.Value
291
+ }
292
+ Show-AuditReport -Token $token -EnvIds $hashtable
293
+ } else {
294
+ Write-Error "Failed to read bootstrap environment."
295
+ exit 1
296
+ }
297
+
298
+ exit 0
299
+ }
300
+
301
+ # Setup mode (default)
302
+ Write-Host "Setup Mode" -ForegroundColor Yellow
303
+ Write-Host "----------" -ForegroundColor Yellow
304
+ Write-Host ""
305
+
306
+ # Check existing values
307
+ $existingToken = Get-RegistryValue -Name "OP_SERVICE_ACCOUNT_TOKEN" -Scope "Machine"
308
+ $existingConfigId = Get-RegistryValue -Name "OP_CONFIG_ENV_ID" -Scope "Machine"
309
+
310
+ if (-not $existingToken) {
311
+ $existingToken = Get-RegistryValue -Name "OP_SERVICE_ACCOUNT_TOKEN" -Scope "User"
312
+ $existingConfigId = Get-RegistryValue -Name "OP_CONFIG_ENV_ID" -Scope "User"
313
+ }
314
+
315
+ $isUpdate = $existingToken -and $existingConfigId
316
+
317
+ if ($isUpdate) {
318
+ Write-Host "Found existing configuration:" -ForegroundColor White
319
+ Write-Host " OP_SERVICE_ACCOUNT_TOKEN = $($existingToken.Substring(0, 8))..." -ForegroundColor Gray
320
+ Write-Host " OP_CONFIG_ENV_ID = $($existingConfigId.Substring(0, 8))..." -ForegroundColor Gray
321
+ Write-Host ""
322
+
323
+ $confirm = Read-Host "Update existing values? (y/N)"
324
+
325
+ if ($confirm -ne 'y' -and $confirm -ne 'Y') {
326
+ Write-Info "Setup cancelled."
327
+ exit 0
328
+ }
329
+ }
330
+
331
+ # Prompt for new values
332
+ Write-Host "Enter your 1Password credentials:" -ForegroundColor White
333
+ Write-Host ""
334
+
335
+ if (-not $isUpdate) {
336
+ $token = Read-Host -AsSecureString " OP_SERVICE_ACCOUNT_TOKEN (service account token)"
337
+ $token = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($token))
338
+ } else {
339
+ $token = Read-Host -AsSecureString " OP_SERVICE_ACCOUNT_TOKEN (leave blank to keep existing)"
340
+ $token = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($token))
341
+ if (-not $token) { $token = $existingToken }
342
+ }
343
+
344
+ if (-not $isUpdate) {
345
+ $configId = Read-Host " OP_CONFIG_ENV_ID (bootstrap environment ID)"
346
+ } else {
347
+ $configId = Read-Host " OP_CONFIG_ENV_ID (leave blank to keep existing)"
348
+ if (-not $configId) { $configId = $existingConfigId }
349
+ }
350
+
351
+ if (-not $token -or -not $configId) {
352
+ Write-Error "Both token and config ID are required."
353
+ exit 1
354
+ }
355
+
356
+ Write-Host ""
357
+ Write-Host "Summary:" -ForegroundColor White
358
+ Write-Host " OP_SERVICE_ACCOUNT_TOKEN = $($token.Substring(0, 8))..." -ForegroundColor Gray
359
+ Write-Host " OP_CONFIG_ENV_ID = $($configId.Substring(0, 8))..." -ForegroundColor Gray
360
+ Write-Host ""
361
+
362
+ # Ask about scope
363
+ Write-Host "Where should these be saved?" -ForegroundColor White
364
+ Write-Host " [1] Current user only (no admin required)"
365
+ Write-Host " [2] System-wide (requires administrator privileges)"
366
+ $scopeChoice = Read-Host "Choice"
367
+
368
+ if ($scopeChoice -eq "2") {
369
+ $scope = "Machine"
370
+ } else {
371
+ $scope = "User"
372
+ }
373
+
374
+ Write-Host ""
375
+ Write-Info "Saving to $scope scope..."
376
+
377
+ if (Set-RegistryValue -Name "OP_SERVICE_ACCOUNT_TOKEN" -Value $token -Scope $scope) {
378
+ Write-Success "Saved OP_SERVICE_ACCOUNT_TOKEN"
379
+ } else {
380
+ Write-Error "Failed to save OP_SERVICE_ACCOUNT_TOKEN"
381
+ exit 1
382
+ }
383
+
384
+ if (Set-RegistryValue -Name "OP_CONFIG_ENV_ID" -Value $configId -Scope $scope) {
385
+ Write-Success "Saved OP_CONFIG_ENV_ID"
386
+ } else {
387
+ Write-Error "Failed to save OP_CONFIG_ENV_ID"
388
+ exit 1
389
+ }
390
+
391
+ Write-Host ""
392
+ Write-Info "Testing 1Password connection..."
393
+
394
+ if (Test-1PasswordConnection -Token $token) {
395
+ Write-Success "1Password connection successful!"
396
+ } else {
397
+ Write-Error "Failed to connect to 1Password. Check your service account token."
398
+ Write-Info "Environment variables were saved. You may need to restart your terminal."
399
+ exit 1
400
+ }
401
+
402
+ Write-Info "Reading configuration..."
403
+
404
+ $envIds = Get-1PasswordAudit -Token $token -ConfigEnvId $configId
405
+
406
+ if ($envIds) {
407
+ $hashtable = @{}
408
+ foreach ($prop in $envIds.PSObject.Properties) {
409
+ $hashtable[$prop.Name] = $prop.Value
410
+ }
411
+ Show-AuditReport -Token $token -EnvIds $hashtable
412
+ }
413
+
414
+ Write-Host ""
415
+ Write-Success "Setup complete!"
416
+ Write-Info "Restart OpenCode to activate the plugin."
417
+ Write-Host ""
418
+ Write-Host "Usage:" -ForegroundColor White
419
+ Write-Host " ./setup.ps1 -Audit Show current configuration"
420
+ Write-Host " ./setup.ps1 -Uninstall Remove environment variables"
421
+ Write-Host ""
package/setup.sh ADDED
@@ -0,0 +1,444 @@
1
+ #!/bin/bash
2
+
3
+ # OpenCode 1Password Auth Plugin - Setup Script
4
+ # This script helps configure the environment variables needed for the plugin
5
+
6
+ set -e
7
+
8
+ # Colors for output
9
+ RED='\033[0;31m'
10
+ GREEN='\033[0;32m'
11
+ YELLOW='\033[1;33m'
12
+ CYAN='\033[0;36m'
13
+ GRAY='\033[0;90m'
14
+ NC='\033[0m' # No Color
15
+
16
+ # Functions
17
+ log_banner() {
18
+ echo ""
19
+ echo -e "${CYAN}========================================${NC}"
20
+ echo -e "${CYAN} OpenCode 1Password Auth Setup${NC}"
21
+ echo -e "${CYAN}========================================${NC}"
22
+ echo ""
23
+ }
24
+
25
+ log_success() {
26
+ echo -e "${GREEN}[OK]${NC} $1"
27
+ }
28
+
29
+ log_error() {
30
+ echo -e "${RED}[ERROR]${NC} $1"
31
+ }
32
+
33
+ log_info() {
34
+ echo -e "${YELLOW}[INFO]${NC} $1"
35
+ }
36
+
37
+ get_existing_values() {
38
+ if [[ "$OSTYPE" == "darwin"* ]] || [[ "$OSTYPE" == "linux-gnu"* ]]; then
39
+ OP_SERVICE_ACCOUNT_TOKEN="${OP_SERVICE_ACCOUNT_TOKEN:-}"
40
+ OP_CONFIG_ENV_ID="${OP_CONFIG_ENV_ID:-}"
41
+
42
+ # Check shell RC files
43
+ if [[ -f "$HOME/.zshrc" ]] && grep -q "OP_SERVICE_ACCOUNT_TOKEN" "$HOME/.zshrc" 2>/dev/null; then
44
+ source "$HOME/.zshrc" 2>/dev/null
45
+ fi
46
+ if [[ -f "$HOME/.bashrc" ]] && grep -q "OP_SERVICE_ACCOUNT_TOKEN" "$HOME/.bashrc" 2>/dev/null; then
47
+ source "$HOME/.bashrc" 2>/dev/null
48
+ fi
49
+ if [[ -f "$HOME/.bash_profile" ]] && grep -q "OP_SERVICE_ACCOUNT_TOKEN" "$HOME/.bash_profile" 2>/dev/null; then
50
+ source "$HOME/.bash_profile" 2>/dev/null
51
+ fi
52
+ fi
53
+ }
54
+
55
+ save_to_rc() {
56
+ local var_name="$1"
57
+ local var_value="$2"
58
+ local rc_file="$3"
59
+
60
+ # Remove existing entry if present
61
+ if [[ -f "$rc_file" ]]; then
62
+ sed -i.bak "/export ${var_name}=/d" "$rc_file"
63
+ fi
64
+
65
+ # Add new entry
66
+ echo "export ${var_name}='${var_value}'" >> "$rc_file"
67
+ log_success "Added ${var_name} to ${rc_file}"
68
+ }
69
+
70
+ remove_from_rc() {
71
+ local var_name="$1"
72
+ local rc_file="$2"
73
+
74
+ if [[ -f "$rc_file" ]]; then
75
+ sed -i.bak "/export ${var_name}=/d" "$rc_file"
76
+ log_success "Removed ${var_name} from ${rc_file}"
77
+ fi
78
+ }
79
+
80
+ test_connection() {
81
+ local token="$1"
82
+
83
+ # Create temporary test script
84
+ cat > /tmp/test_1p_$$.js << 'EOF'
85
+ const sdk = require('@1password/sdk');
86
+
87
+ async function test() {
88
+ const token = process.argv[1];
89
+ const client = await sdk.createClient({
90
+ auth: token,
91
+ integrationName: 'opencode-1password-setup-test',
92
+ integrationVersion: '1.0.0'
93
+ });
94
+ console.log('SUCCESS');
95
+ }
96
+
97
+ test().catch(err => {
98
+ console.error('FAILED:', err.message);
99
+ process.exit(1);
100
+ });
101
+ EOF
102
+
103
+ result=$(node /tmp/test_1p_$$.js "$token" 2>&1)
104
+ rm -f /tmp/test_1p_$$.js
105
+
106
+ if [[ "$result" == "SUCCESS" ]]; then
107
+ return 0
108
+ else
109
+ return 1
110
+ fi
111
+ }
112
+
113
+ get_audit_data() {
114
+ local token="$1"
115
+ local config_id="$2"
116
+
117
+ cat > /tmp/audit_1p_$$.js << 'EOF'
118
+ const sdk = require('@1password/sdk');
119
+
120
+ async function audit() {
121
+ const token = process.argv[1];
122
+ const configId = process.argv[2];
123
+
124
+ const client = await sdk.createClient({
125
+ auth: token,
126
+ integrationName: 'opencode-1password-setup-test',
127
+ integrationVersion: '1.0.0'
128
+ });
129
+
130
+ const { variables } = await client.environments.getVariables(configId);
131
+
132
+ const envIds = {};
133
+ for (const v of variables) {
134
+ if (v.name.endsWith('_ENV_ID') && v.value) {
135
+ envIds[v.name] = v.value;
136
+ }
137
+ }
138
+
139
+ console.log(JSON.stringify(envIds));
140
+ }
141
+
142
+ audit().catch(err => {
143
+ console.error('FAILED:', err.message);
144
+ process.exit(1);
145
+ });
146
+ EOF
147
+
148
+ result=$(node /tmp/audit_1p_$$.js "$token" "$config_id" 2>&1)
149
+ rm -f /tmp/audit_1p_$$.js
150
+ echo "$result"
151
+ }
152
+
153
+ show_audit_report() {
154
+ local token="$1"
155
+ local env_ids_json="$2"
156
+
157
+ echo ""
158
+ echo -e "${CYAN}========================================${NC}"
159
+ echo -e "${CYAN} Configuration Audit Report${NC}"
160
+ echo -e "${CYAN}========================================${NC}"
161
+ echo ""
162
+
163
+ echo -e "${WHITE}Bootstrap Environment References:${NC}"
164
+ echo "--------------------------------"
165
+
166
+ # Parse JSON manually (macOS doesn't have jq by default)
167
+ echo "$env_ids_json" | sed 's/[{}"]//g' | tr ',' '\n' | while IFS=':' read -r key value; do
168
+ if [[ -n "$key" && -n "$value" ]]; then
169
+ masked="${value:0:10}..."
170
+ echo -e " ${key} = ${masked}"
171
+ fi
172
+ done
173
+
174
+ echo ""
175
+
176
+ # For each referenced environment, show its contents
177
+ echo "$env_ids_json" | sed 's/[{}"]//g' | tr ',' '\n' | while IFS=':' read -r key value; do
178
+ if [[ -n "$key" && -n "$value" && "$key" != *"{"* ]]; then
179
+ env_id="$value"
180
+ env_name=$(echo "$key" | sed 's/OPENCODE_//' | sed 's/_ENV_ID//')
181
+
182
+ echo -e "${WHITE}${env_name} Environment:${NC}"
183
+ echo "--------------------------------"
184
+
185
+ # Read and display this environment
186
+ cat > /tmp/read_env_$$.js << 'EOFINNER'
187
+ const sdk = require('@1password/sdk');
188
+
189
+ async function readEnv() {
190
+ const token = process.argv[1];
191
+ const envId = process.argv[2];
192
+
193
+ const client = await sdk.createClient({
194
+ auth: token,
195
+ integrationName: 'opencode-1password-setup-test',
196
+ integrationVersion: '1.0.0'
197
+ });
198
+
199
+ const { variables } = await client.environments.getVariables(envId);
200
+
201
+ for (const v of variables) {
202
+ const masked = v.value ? v.value.substring(0, 8) + '••••••••' : '(empty)';
203
+ console.log(v.name + '=' + masked);
204
+ }
205
+ }
206
+
207
+ readEnv().catch(err => {
208
+ console.error('Error:', err.message);
209
+ });
210
+ EOFINNER
211
+
212
+ node /tmp/read_env_$$.js "$token" "$env_id" 2>&1 | while IFS='=' read -r name masked; do
213
+ if [[ -n "$name" ]]; then
214
+ echo -e " ${name}=${masked}"
215
+ fi
216
+ done
217
+
218
+ rm -f /tmp/read_env_$$.js
219
+ echo ""
220
+ fi
221
+ done
222
+ }
223
+
224
+ # Parse arguments
225
+ UNINSTALL=false
226
+ AUDIT=false
227
+
228
+ while [[ $# -gt 0 ]]; do
229
+ case $1 in
230
+ -u|--uninstall)
231
+ UNINSTALL=true
232
+ shift
233
+ ;;
234
+ -a|--audit)
235
+ AUDIT=true
236
+ shift
237
+ ;;
238
+ *)
239
+ echo "Usage: $0 [--uninstall|--audit]"
240
+ exit 1
241
+ ;;
242
+ esac
243
+ done
244
+
245
+ # Main script execution
246
+ log_banner
247
+
248
+ if [[ "$UNINSTALL" == true ]]; then
249
+ echo -e "${YELLOW}Uninstall Mode${NC}"
250
+ echo -e "${YELLOW}------------${NC}"
251
+ echo ""
252
+
253
+ get_existing_values
254
+
255
+ if [[ -z "$OP_SERVICE_ACCOUNT_TOKEN" && -z "$OP_CONFIG_ENV_ID" ]]; then
256
+ log_info "No environment variables found. Nothing to uninstall."
257
+ exit 0
258
+ fi
259
+
260
+ echo "Found the following environment variables:"
261
+ if [[ -n "$OP_SERVICE_ACCOUNT_TOKEN" ]]; then
262
+ echo " OP_SERVICE_ACCOUNT_TOKEN = ${OP_SERVICE_ACCOUNT_TOKEN:0:8}..."
263
+ fi
264
+ if [[ -n "$OP_CONFIG_ENV_ID" ]]; then
265
+ echo " OP_CONFIG_ENV_ID = ${OP_CONFIG_ENV_ID:0:8}..."
266
+ fi
267
+ echo ""
268
+
269
+ read -p "Remove these environment variables? (y/N) " -n 1 -r
270
+ echo ""
271
+
272
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
273
+ # Remove from all RC files
274
+ for rc in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.bash_profile"; do
275
+ if [[ -f "$rc" ]]; then
276
+ remove_from_rc "OP_SERVICE_ACCOUNT_TOKEN" "$rc"
277
+ remove_from_rc "OP_CONFIG_ENV_ID" "$rc"
278
+ fi
279
+ done
280
+
281
+ # Unset in current session
282
+ unset OP_SERVICE_ACCOUNT_TOKEN OP_CONFIG_ENV_ID
283
+
284
+ log_success "Removed environment variables."
285
+ log_info "Please restart any OpenCode sessions for changes to take effect."
286
+ else
287
+ log_info "Uninstall cancelled."
288
+ fi
289
+
290
+ exit 0
291
+ fi
292
+
293
+ if [[ "$AUDIT" == true ]]; then
294
+ echo -e "${YELLOW}Audit Mode${NC}"
295
+ echo -e "${YELLOW}----------${NC}"
296
+ echo ""
297
+
298
+ get_existing_values
299
+
300
+ if [[ -z "$OP_SERVICE_ACCOUNT_TOKEN" || -z "$OP_CONFIG_ENV_ID" ]]; then
301
+ log_error "Environment variables not set. Run setup first."
302
+ exit 1
303
+ fi
304
+
305
+ log_info "Testing 1Password connection..."
306
+
307
+ if test_connection "$OP_SERVICE_ACCOUNT_TOKEN"; then
308
+ log_success "1Password connection successful!"
309
+ else
310
+ log_error "Failed to connect to 1Password. Check your service account token."
311
+ exit 1
312
+ fi
313
+
314
+ log_info "Reading configuration..."
315
+
316
+ env_ids_json=$(get_audit_data "$OP_SERVICE_ACCOUNT_TOKEN" "$OP_CONFIG_ENV_ID")
317
+
318
+ if [[ -n "$env_ids_json" && "$env_ids_json" != "FAILED:"* ]]; then
319
+ show_audit_report "$OP_SERVICE_ACCOUNT_TOKEN" "$env_ids_json"
320
+ else
321
+ log_error "Failed to read bootstrap environment."
322
+ exit 1
323
+ fi
324
+
325
+ exit 0
326
+ fi
327
+
328
+ # Setup mode (default)
329
+ echo -e "${YELLOW}Setup Mode${NC}"
330
+ echo -e "${YELLOW}----------${NC}"
331
+ echo ""
332
+
333
+ get_existing_values
334
+
335
+ IS_UPDATE=false
336
+ if [[ -n "$OP_SERVICE_ACCOUNT_TOKEN" && -n "$OP_CONFIG_ENV_ID" ]]; then
337
+ IS_UPDATE=true
338
+ echo "Found existing configuration:"
339
+ echo " OP_SERVICE_ACCOUNT_TOKEN = ${OP_SERVICE_ACCOUNT_TOKEN:0:8}..."
340
+ echo " OP_CONFIG_ENV_ID = ${OP_CONFIG_ENV_ID:0:8}..."
341
+ echo ""
342
+
343
+ read -p "Update existing values? (y/N) " -n 1 -r
344
+ echo ""
345
+
346
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
347
+ log_info "Setup cancelled."
348
+ exit 0
349
+ fi
350
+ fi
351
+
352
+ # Prompt for new values
353
+ echo "Enter your 1Password credentials:"
354
+ echo ""
355
+
356
+ if [[ "$IS_UPDATE" == false ]]; then
357
+ read -s -p " OP_SERVICE_ACCOUNT_TOKEN: " OP_SERVICE_ACCOUNT_TOKEN
358
+ else
359
+ read -s -p " OP_SERVICE_ACCOUNT_TOKEN (leave blank to keep): " NEW_TOKEN
360
+ if [[ -n "$NEW_TOKEN" ]]; then
361
+ OP_SERVICE_ACCOUNT_TOKEN="$NEW_TOKEN"
362
+ fi
363
+ fi
364
+ echo ""
365
+
366
+ if [[ "$IS_UPDATE" == false ]]; then
367
+ read -p " OP_CONFIG_ENV_ID: " OP_CONFIG_ENV_ID
368
+ else
369
+ read -p " OP_CONFIG_ENV_ID (leave blank to keep): " NEW_CONFIG_ID
370
+ if [[ -n "$NEW_CONFIG_ID" ]]; then
371
+ OP_CONFIG_ENV_ID="$NEW_CONFIG_ID"
372
+ fi
373
+ fi
374
+
375
+ if [[ -z "$OP_SERVICE_ACCOUNT_TOKEN" || -z "$OP_CONFIG_ENV_ID" ]]; then
376
+ log_error "Both token and config ID are required."
377
+ exit 1
378
+ fi
379
+
380
+ echo ""
381
+ echo "Summary:"
382
+ echo " OP_SERVICE_ACCOUNT_TOKEN = ${OP_SERVICE_ACCOUNT_TOKEN:0:8}..."
383
+ echo " OP_CONFIG_ENV_ID = ${OP_CONFIG_ENV_ID:0:8}..."
384
+ echo ""
385
+
386
+ # Detect shell and RC file
387
+ if [[ -n "$ZSH_VERSION" ]]; then
388
+ SHELL_RC="$HOME/.zshrc"
389
+ elif [[ -n "$BASH_VERSION" ]]; then
390
+ SHELL_RC="$HOME/.bashrc"
391
+ else
392
+ SHELL_RC="$HOME/.profile"
393
+ fi
394
+
395
+ echo "Where should these be saved?"
396
+ echo " [1] Current shell only (no file modification)"
397
+ echo " [2] Shell RC file (~/.zshrc or ~/.bashrc)"
398
+ read -p "Choice [1]: " scope_choice
399
+
400
+ if [[ "$scope_choice" == "2" ]]; then
401
+ log_info "Saving to ${SHELL_RC}..."
402
+
403
+ save_to_rc "OP_SERVICE_ACCOUNT_TOKEN" "$OP_SERVICE_ACCOUNT_TOKEN" "$SHELL_RC"
404
+ save_to_rc "OP_CONFIG_ENV_ID" "$OP_CONFIG_ENV_ID" "$SHELL_RC"
405
+
406
+ # Also export to current session
407
+ export OP_SERVICE_ACCOUNT_TOKEN
408
+ export OP_CONFIG_ENV_ID
409
+
410
+ log_info "Added to ${SHELL_RC}. Restart your shell or run:"
411
+ echo " source ${SHELL_RC}"
412
+ else
413
+ export OP_SERVICE_ACCOUNT_TOKEN
414
+ export OP_CONFIG_ENV_ID
415
+ log_info "Exported to current shell only."
416
+ fi
417
+
418
+ echo ""
419
+ log_info "Testing 1Password connection..."
420
+
421
+ if test_connection "$OP_SERVICE_ACCOUNT_TOKEN"; then
422
+ log_success "1Password connection successful!"
423
+ else
424
+ log_error "Failed to connect to 1Password. Check your service account token."
425
+ log_info "Environment variables were saved. You may need to restart your terminal."
426
+ exit 1
427
+ fi
428
+
429
+ log_info "Reading configuration..."
430
+
431
+ env_ids_json=$(get_audit_data "$OP_SERVICE_ACCOUNT_TOKEN" "$OP_CONFIG_ENV_ID")
432
+
433
+ if [[ -n "$env_ids_json" && "$env_ids_json" != "FAILED:"* ]]; then
434
+ show_audit_report "$OP_SERVICE_ACCOUNT_TOKEN" "$env_ids_json"
435
+ fi
436
+
437
+ echo ""
438
+ log_success "Setup complete!"
439
+ log_info "Restart OpenCode to activate the plugin."
440
+ echo ""
441
+ echo "Usage:"
442
+ echo " ./setup.sh --audit Show current configuration"
443
+ echo " ./setup.sh --uninstall Remove environment variables"
444
+ echo ""