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.
- package/README.md +174 -0
- package/index.ts +188 -0
- package/package.json +13 -0
- package/setup.ps1 +421 -0
- 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 ""
|