opencode-1password-auth 1.0.0 → 1.0.3
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/index.ts +114 -3
- package/package.json +1 -1
- package/setup.ps1 +284 -69
- package/setup.sh +205 -3
package/index.ts
CHANGED
|
@@ -9,10 +9,33 @@ interface OpenCodeConfig {
|
|
|
9
9
|
mcp?: Record<string, MCPServerConfig>;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
interface ProviderAuth {
|
|
13
|
+
type: string;
|
|
14
|
+
key: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface AuthJson {
|
|
18
|
+
[providerId: string]: ProviderAuth;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const OnePasswordAuthPlugin: Plugin = async ({ client, $ }) => {
|
|
13
22
|
let opClient: Awaited<ReturnType<typeof sdk.createClient>> | null = null;
|
|
14
23
|
let mcpsEnvId: string | undefined;
|
|
15
24
|
|
|
25
|
+
const getHomeDir = (): string => {
|
|
26
|
+
return process.env.HOME || process.env.USERPROFILE || "";
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const getAuthJsonPath = (): string => {
|
|
30
|
+
const home = getHomeDir();
|
|
31
|
+
return `${home}/.local/share/opencode/auth.json`;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const getOpenCodeJsonPath = (): string => {
|
|
35
|
+
const home = getHomeDir();
|
|
36
|
+
return `${home}/.config/opencode/opencode.json`;
|
|
37
|
+
};
|
|
38
|
+
|
|
16
39
|
const initClient = async () => {
|
|
17
40
|
const token = process.env.OP_SERVICE_ACCOUNT_TOKEN;
|
|
18
41
|
if (!token) {
|
|
@@ -55,6 +78,91 @@ export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
|
|
|
55
78
|
return secrets;
|
|
56
79
|
};
|
|
57
80
|
|
|
81
|
+
const updateAuthJson = async (providerEnvId: string): Promise<void> => {
|
|
82
|
+
const authJsonPath = getAuthJsonPath();
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
// Read current auth.json using cat
|
|
86
|
+
const catResult = await $`cat "${authJsonPath}"`;
|
|
87
|
+
const content = catResult.stdout;
|
|
88
|
+
let auth: AuthJson;
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
auth = JSON.parse(content);
|
|
92
|
+
} catch {
|
|
93
|
+
console.log("1Password: auth.json is not valid JSON, skipping");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let modified = false;
|
|
98
|
+
|
|
99
|
+
for (const [providerId, authConfig] of Object.entries(auth)) {
|
|
100
|
+
if (authConfig.key && !authConfig.key.startsWith("{env:")) {
|
|
101
|
+
// Replace hardcoded key with env var reference
|
|
102
|
+
authConfig.key = `{env:${providerId}}`;
|
|
103
|
+
modified = true;
|
|
104
|
+
console.log(`1Password: Updated auth.json - ${providerId} -> {env:${providerId}}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (modified) {
|
|
109
|
+
// Write updated content using a temp file and mv
|
|
110
|
+
const newContent = JSON.stringify(auth, null, 2);
|
|
111
|
+
// Use node to write the file since $ heredocs can be tricky
|
|
112
|
+
await $`node -e "const fs=require('fs'); fs.writeFileSync('${authJsonPath}', JSON.stringify(${JSON.stringify(auth)}, null, 2));"`;
|
|
113
|
+
console.log("1Password: auth.json updated to use environment variables");
|
|
114
|
+
}
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.error("1Password: Failed to update auth.json:", err);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const updateOpenCodeJsonMCP = async (mcpEnvId: string): Promise<void> => {
|
|
121
|
+
const openCodeJsonPath = getOpenCodeJsonPath();
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
// Read current opencode.json using cat
|
|
125
|
+
const catResult = await $`cat "${openCodeJsonPath}"`;
|
|
126
|
+
const content = catResult.stdout;
|
|
127
|
+
let config: OpenCodeConfig;
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
config = JSON.parse(content);
|
|
131
|
+
} catch {
|
|
132
|
+
console.log("1Password: opencode.json is not valid JSON, skipping");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!config.mcp) {
|
|
137
|
+
console.log("1Password: No MCP configuration found, skipping");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let modified = false;
|
|
142
|
+
|
|
143
|
+
for (const [serverName, serverConfig] of Object.entries(config.mcp)) {
|
|
144
|
+
if (serverConfig?.environment) {
|
|
145
|
+
for (const [key, value] of Object.entries(serverConfig.environment)) {
|
|
146
|
+
if (typeof value === "string" && !value.startsWith("{env:") && !value.startsWith("$")) {
|
|
147
|
+
// Replace hardcoded value with env var reference
|
|
148
|
+
serverConfig.environment[key] = `{env:${key}}`;
|
|
149
|
+
modified = true;
|
|
150
|
+
console.log(`1Password: Updated opencode.json - ${serverName}.${key} -> {env:${key}}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (modified) {
|
|
157
|
+
// Write updated content
|
|
158
|
+
await $`node -e "const fs=require('fs'); fs.writeFileSync('${openCodeJsonPath}', JSON.stringify(${JSON.stringify(config)}, null, 2));"`;
|
|
159
|
+
console.log("1Password: opencode.json MCP config updated to use environment variables");
|
|
160
|
+
}
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.error("1Password: Failed to update opencode.json:", err);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
58
166
|
const authenticateProviders = async (providerEnvId: string): Promise<void> => {
|
|
59
167
|
if (!opClient) return;
|
|
60
168
|
|
|
@@ -64,7 +172,7 @@ export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
|
|
|
64
172
|
if (!apiKey) continue;
|
|
65
173
|
|
|
66
174
|
try {
|
|
67
|
-
await
|
|
175
|
+
await client.auth.set({
|
|
68
176
|
path: { id: providerId },
|
|
69
177
|
body: { type: "api", key: apiKey },
|
|
70
178
|
});
|
|
@@ -79,7 +187,7 @@ export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
|
|
|
79
187
|
const vars = new Set<string>();
|
|
80
188
|
|
|
81
189
|
try {
|
|
82
|
-
const config =
|
|
190
|
+
const config = client.config as OpenCodeConfig;
|
|
83
191
|
if (config?.mcp) {
|
|
84
192
|
for (const [, serverConfig] of Object.entries(config.mcp)) {
|
|
85
193
|
if (serverConfig?.environment) {
|
|
@@ -146,12 +254,15 @@ export const OnePasswordAuthPlugin: Plugin = async (ctx) => {
|
|
|
146
254
|
|
|
147
255
|
const providersEnvId = configEnvIds.OPENCODE_PROVIDERS_ENV_ID;
|
|
148
256
|
if (providersEnvId) {
|
|
257
|
+
// First update the config files to use env var references
|
|
258
|
+
await updateAuthJson(providersEnvId);
|
|
149
259
|
await authenticateProviders(providersEnvId);
|
|
150
260
|
}
|
|
151
261
|
|
|
152
262
|
const mcpEnvIdFromConfig = configEnvIds.OPENCODE_MCPS_ENV_ID;
|
|
153
263
|
if (mcpEnvIdFromConfig) {
|
|
154
264
|
mcpsEnvId = mcpEnvIdFromConfig;
|
|
265
|
+
await updateOpenCodeJsonMCP(mcpEnvIdFromConfig);
|
|
155
266
|
const toInject = await injectMCPSecrets(mcpEnvIdFromConfig);
|
|
156
267
|
if (Object.keys(toInject).length > 0) {
|
|
157
268
|
console.log(`1Password: Injected ${Object.keys(toInject).join(", ")} for MCP`);
|
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,31 +66,184 @@ function Remove-RegistryValue {
|
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
function
|
|
69
|
-
|
|
69
|
+
function Get-OpenCodeAuthJsonPath {
|
|
70
|
+
$home = if ($env:USERPROFILE) { $env:USERPROFILE } else { $env:USERPROFILE }
|
|
71
|
+
return "$home/.local/share/opencode/auth.json"
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function Get-OpenCodeConfigJsonPath {
|
|
75
|
+
$home = if ($env:USERPROFILE) { $env:USERPROFILE } else { $env:USERPROFILE }
|
|
76
|
+
return "$home/.config/opencode/opencode.json"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function Update-ConfigFiles {
|
|
80
|
+
param([string]$Token, [string]$ProvidersEnvId, [string]$McpsEnvId)
|
|
70
81
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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}');
|
|
75
95
|
|
|
76
|
-
async function
|
|
96
|
+
async function getSecrets() {
|
|
77
97
|
const client = await sdk.createClient({
|
|
78
|
-
auth: '$Token',
|
|
79
|
-
integrationName: 'opencode-1password-setup
|
|
98
|
+
auth: '${Token}',
|
|
99
|
+
integrationName: 'opencode-1password-setup',
|
|
80
100
|
integrationVersion: '1.0.0'
|
|
81
101
|
});
|
|
82
102
|
|
|
83
|
-
|
|
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 }));
|
|
84
118
|
}
|
|
85
119
|
|
|
86
|
-
|
|
87
|
-
console.error('FAILED:', err.message);
|
|
88
|
-
process.exit(1);
|
|
89
|
-
});
|
|
120
|
+
getSecrets().catch(err => { console.error('FAILED:', err.message); process.exit(1); });
|
|
90
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
|
+
|
|
200
|
+
function Get-OpenCodeNodeModulesPath {
|
|
201
|
+
$paths = @(
|
|
202
|
+
"$env:USERPROFILE\.cache\opencode\node_modules",
|
|
203
|
+
"$env:USERPROFILE\.config\opencode\node_modules",
|
|
204
|
+
"$env:APPDATA\opencode\node_modules"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
foreach ($path in $paths) {
|
|
208
|
+
if (Test-Path "$path\@1password\sdk") {
|
|
209
|
+
return $path
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return $null
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function Test-1PasswordConnection {
|
|
216
|
+
param([string]$Token)
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
$nodeModulesPath = Get-OpenCodeNodeModulesPath
|
|
220
|
+
|
|
221
|
+
if (-not $nodeModulesPath) {
|
|
222
|
+
Write-Error "Could not find @1password/sdk in any node_modules directory"
|
|
223
|
+
return $false
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
$sdkPath = ($nodeModulesPath -replace '\\', '/') + "/@1password/sdk/dist/sdk.js"
|
|
227
|
+
|
|
228
|
+
# Create a temporary Node.js script file
|
|
229
|
+
$tempScript = [System.IO.Path]::GetTempFileName() -replace '\.tmp$', '.js'
|
|
230
|
+
|
|
231
|
+
$testScript = 'const sdk = require("' + $sdkPath + '");' + "`n" +
|
|
232
|
+
'async function test() {' + "`n" +
|
|
233
|
+
' const client = await sdk.createClient({' + "`n" +
|
|
234
|
+
' auth: "' + $Token + '",' + "`n" +
|
|
235
|
+
' integrationName: "opencode-1password-setup-test",' + "`n" +
|
|
236
|
+
' integrationVersion: "1.0.0"' + "`n" +
|
|
237
|
+
' });' + "`n" +
|
|
238
|
+
' console.log("SUCCESS");' + "`n" +
|
|
239
|
+
'}' + "`n" +
|
|
240
|
+
'test().catch(err => { console.error("FAILED:", err.message); process.exit(1); });'
|
|
241
|
+
|
|
242
|
+
$testScript | Out-File -FilePath $tempScript -Encoding UTF8 -NoNewline
|
|
243
|
+
|
|
244
|
+
$result = & node $tempScript 2>&1
|
|
245
|
+
Remove-Item $tempScript -ErrorAction SilentlyContinue
|
|
91
246
|
|
|
92
|
-
$result = node -e $testScript 2>&1
|
|
93
247
|
return $result -match "SUCCESS"
|
|
94
248
|
} catch {
|
|
95
249
|
return $false
|
|
@@ -100,36 +254,37 @@ function Get-1PasswordAudit {
|
|
|
100
254
|
param([string]$Token, [string]$ConfigEnvId)
|
|
101
255
|
|
|
102
256
|
try {
|
|
103
|
-
$
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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;
|
|
257
|
+
$nodeModulesPath = Get-OpenCodeNodeModulesPath
|
|
258
|
+
|
|
259
|
+
if (-not $nodeModulesPath) {
|
|
260
|
+
Write-Error "Could not find @1password/sdk in any node_modules directory"
|
|
261
|
+
return $null
|
|
120
262
|
}
|
|
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
263
|
|
|
132
|
-
$
|
|
264
|
+
$sdkPath = ($nodeModulesPath -replace '\\', '/') + "/@1password/sdk/dist/sdk.js"
|
|
265
|
+
|
|
266
|
+
$script = 'const sdk = require("' + $sdkPath + '");' + "`n" +
|
|
267
|
+
'async function audit() {' + "`n" +
|
|
268
|
+
' const client = await sdk.createClient({' + "`n" +
|
|
269
|
+
' auth: "' + $Token + '",' + "`n" +
|
|
270
|
+
' integrationName: "opencode-1password-setup-test",' + "`n" +
|
|
271
|
+
' integrationVersion: "1.0.0"' + "`n" +
|
|
272
|
+
' });' + "`n" +
|
|
273
|
+
' const { variables: configVars } = await client.environments.getVariables("' + $ConfigEnvId + '");' + "`n" +
|
|
274
|
+
' const envIds = {};' + "`n" +
|
|
275
|
+
' for (const v of configVars) {' + "`n" +
|
|
276
|
+
' if (v.name.endsWith("_ENV_ID") && v.value) {' + "`n" +
|
|
277
|
+
' envIds[v.name] = v.value;' + "`n" +
|
|
278
|
+
' }' + "`n" +
|
|
279
|
+
' }' + "`n" +
|
|
280
|
+
' console.log(JSON.stringify(envIds));' + "`n" +
|
|
281
|
+
'}' + "`n" +
|
|
282
|
+
'audit().catch(err => { console.error("FAILED:", err.message); process.exit(1); });'
|
|
283
|
+
|
|
284
|
+
$tempScript = [System.IO.Path]::GetTempFileName() -replace '\.tmp$', '.js'
|
|
285
|
+
$script | Out-File -FilePath $tempScript -Encoding UTF8 -NoNewline
|
|
286
|
+
$jsonResult = & node $tempScript 2>&1 | Out-String
|
|
287
|
+
Remove-Item $tempScript -ErrorAction SilentlyContinue
|
|
133
288
|
return $jsonResult | ConvertFrom-Json
|
|
134
289
|
} catch {
|
|
135
290
|
return $null
|
|
@@ -139,6 +294,8 @@ audit().catch(err => {
|
|
|
139
294
|
function Show-AuditReport {
|
|
140
295
|
param([string]$Token, [hashtable]$EnvIds)
|
|
141
296
|
|
|
297
|
+
$nodeModulesPath = Get-OpenCodeNodeModulesPath
|
|
298
|
+
|
|
142
299
|
Write-Host ""
|
|
143
300
|
Write-Host "========================================" -ForegroundColor Cyan
|
|
144
301
|
Write-Host " Configuration Audit Report" -ForegroundColor Cyan
|
|
@@ -163,30 +320,27 @@ function Show-AuditReport {
|
|
|
163
320
|
Write-Host "--------------------------------"
|
|
164
321
|
|
|
165
322
|
try {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
async function read() {
|
|
170
|
-
const client = await sdk.createClient({
|
|
171
|
-
auth: '$Token',
|
|
172
|
-
integrationName:
|
|
173
|
-
integrationVersion:
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
read().catch(err => {
|
|
185
|
-
console.error('Error:', err.message);
|
|
186
|
-
});
|
|
187
|
-
"@
|
|
323
|
+
# Use forward slashes to avoid JS escape sequence issues
|
|
324
|
+
$sdkPath = ($nodeModulesPath -replace '\\', '/') + "/@1password/sdk/dist/sdk.js"
|
|
325
|
+
$script = 'const sdk = require("' + $sdkPath + '");' + "`n" +
|
|
326
|
+
'async function read() {' + "`n" +
|
|
327
|
+
' const client = await sdk.createClient({' + "`n" +
|
|
328
|
+
' auth: "' + $Token + '",' + "`n" +
|
|
329
|
+
' integrationName: "opencode-1password-setup-test",' + "`n" +
|
|
330
|
+
' integrationVersion: "1.0.0"' + "`n" +
|
|
331
|
+
' });' + "`n" +
|
|
332
|
+
' const { variables } = await client.environments.getVariables("' + $envId + '");' + "`n" +
|
|
333
|
+
' for (const v of variables) {' + "`n" +
|
|
334
|
+
' const masked = v.value ? v.value.substring(0, 8) + "••••••••" : "(empty)";' + "`n" +
|
|
335
|
+
' console.log(v.name + "=" + masked);' + "`n" +
|
|
336
|
+
' }' + "`n" +
|
|
337
|
+
'}' + "`n" +
|
|
338
|
+
'read().catch(err => { console.error("Error:", err.message); });'
|
|
188
339
|
|
|
189
|
-
$
|
|
340
|
+
$tempScript = [System.IO.Path]::GetTempFileName() -replace '\.tmp$', '.js'
|
|
341
|
+
$script | Out-File -FilePath $tempScript -Encoding UTF8 -NoNewline
|
|
342
|
+
$result = & node $tempScript 2>&1
|
|
343
|
+
Remove-Item $tempScript -ErrorAction SilentlyContinue
|
|
190
344
|
foreach ($line in $result) {
|
|
191
345
|
if ($line -and $line -notmatch "^(Error|Error:)") {
|
|
192
346
|
Write-Host " $line" -ForegroundColor Gray
|
|
@@ -298,6 +452,66 @@ if ($Audit) {
|
|
|
298
452
|
exit 0
|
|
299
453
|
}
|
|
300
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
|
+
|
|
301
515
|
# Setup mode (default)
|
|
302
516
|
Write-Host "Setup Mode" -ForegroundColor Yellow
|
|
303
517
|
Write-Host "----------" -ForegroundColor Yellow
|
|
@@ -416,6 +630,7 @@ Write-Success "Setup complete!"
|
|
|
416
630
|
Write-Info "Restart OpenCode to activate the plugin."
|
|
417
631
|
Write-Host ""
|
|
418
632
|
Write-Host "Usage:" -ForegroundColor White
|
|
419
|
-
Write-Host " ./setup.ps1 -Audit
|
|
420
|
-
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"
|
|
421
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 ""
|