opencode-supabase 0.0.1 → 0.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/README.md +15 -194
- package/package.json +1 -1
- package/src/shared/api.ts +2 -0
- package/src/shared/cfg.ts +12 -13
- package/src/tui/dialog.tsx +10 -4
package/README.md
CHANGED
|
@@ -1,211 +1,32 @@
|
|
|
1
1
|
# opencode-supabase
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Supabase plugin for OpenCode.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+

|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Get started
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Requires OpenCode `>= 1.3.4`.
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
opencode plugin
|
|
12
|
+
opencode plugin opencode-supabase
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Launch `opencode` in your project, then run:
|
|
16
16
|
|
|
17
|
-
```bash
|
|
18
|
-
opencode plugin ../../opencode-supabase
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Optional manual setup if you want to wire the config yourself:
|
|
22
|
-
|
|
23
|
-
Absolute-path example:
|
|
24
|
-
|
|
25
|
-
`.opencode/opencode.jsonc`
|
|
26
|
-
|
|
27
|
-
```json
|
|
28
|
-
{
|
|
29
|
-
"plugin": ["/absolute/path/to/opencode-supabase"]
|
|
30
|
-
}
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
`.opencode/tui.jsonc`
|
|
34
|
-
|
|
35
|
-
```json
|
|
36
|
-
{
|
|
37
|
-
"plugin": ["/absolute/path/to/opencode-supabase"]
|
|
38
|
-
}
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Sibling-checkout relative example:
|
|
42
|
-
|
|
43
|
-
`.opencode/opencode.jsonc`
|
|
44
|
-
|
|
45
|
-
```json
|
|
46
|
-
{
|
|
47
|
-
"plugin": ["../../opencode-supabase"]
|
|
48
|
-
}
|
|
49
17
|
```
|
|
50
|
-
|
|
51
|
-
`.opencode/tui.jsonc`
|
|
52
|
-
|
|
53
|
-
```json
|
|
54
|
-
{
|
|
55
|
-
"plugin": ["../../opencode-supabase"]
|
|
56
|
-
}
|
|
18
|
+
/supabase
|
|
57
19
|
```
|
|
58
20
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
## Development
|
|
62
|
-
|
|
63
|
-
Install dependencies:
|
|
21
|
+
Connect your account and ask your agent about Supabase capabilities.
|
|
64
22
|
|
|
65
|
-
|
|
66
|
-
bun install
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
Run typecheck:
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
bun run typecheck
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## Reference Docs
|
|
76
|
-
|
|
77
|
-
For the Supabase Management API reference, see:
|
|
78
|
-
|
|
79
|
-
- https://supabase.com/docs/reference/api/introduction
|
|
80
|
-
|
|
81
|
-
## Supabase OAuth Broker
|
|
82
|
-
|
|
83
|
-
The broker is a single Supabase Edge Function that handles confidential token operations against `https://api.supabase.com/v1/oauth/token`. It exposes two endpoints:
|
|
84
|
-
|
|
85
|
-
- `POST /exchange` - exchange an authorization code for tokens
|
|
86
|
-
- `POST /refresh` - refresh an access token
|
|
87
|
-
|
|
88
|
-
The plugin owns browser authorization, PKCE, the local callback server, and local token storage. The broker holds `client_secret` and makes token requests using Basic auth.
|
|
89
|
-
|
|
90
|
-
## Local Setup
|
|
91
|
-
|
|
92
|
-
There are two separate environments to configure:
|
|
93
|
-
|
|
94
|
-
1. the local Supabase Edge Function runtime
|
|
95
|
-
2. the consumer project shell that launches `opencode`
|
|
96
|
-
|
|
97
|
-
### 1. Broker runtime environment
|
|
98
|
-
|
|
99
|
-
These variables are used by the local Supabase Edge Function.
|
|
100
|
-
|
|
101
|
-
Required:
|
|
102
|
-
|
|
103
|
-
```bash
|
|
104
|
-
OPENCODE_SUPABASE_OAUTH_CLIENT_ID=<your_supabase_oauth_app_client_id>
|
|
105
|
-
OPENCODE_SUPABASE_OAUTH_CLIENT_SECRET=<your_supabase_oauth_app_client_secret>
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
Optional:
|
|
109
|
-
|
|
110
|
-
```bash
|
|
111
|
-
# Defaults shown
|
|
112
|
-
OPENCODE_SUPABASE_OAUTH_TOKEN_URL=https://api.supabase.com/v1/oauth/token
|
|
113
|
-
OPENCODE_SUPABASE_ALLOWED_REDIRECT_HOSTS=localhost
|
|
114
|
-
OPENCODE_SUPABASE_ALLOWED_REDIRECT_PATHS=/auth/callback
|
|
115
|
-
```
|
|
23
|
+
## Available today
|
|
116
24
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
-
|
|
120
|
-
|
|
121
|
-
Committed template:
|
|
122
|
-
|
|
123
|
-
- `supabase/functions/.env.example`
|
|
124
|
-
|
|
125
|
-
Start the broker locally:
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
supabase functions serve opencode-supabase-broker --env-file supabase/functions/.env
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
The broker fails fast on the first request if required secrets are missing - callers receive a generic `500 server_error` JSON response. Detailed cause is logged for operators; no internal details are exposed to callers.
|
|
132
|
-
|
|
133
|
-
### 2. Consumer project / OpenCode environment
|
|
134
|
-
|
|
135
|
-
These variables must be present in the shell before launching `opencode` in the consumer repo.
|
|
136
|
-
|
|
137
|
-
Required:
|
|
138
|
-
|
|
139
|
-
```bash
|
|
140
|
-
export OPENCODE_SUPABASE_BROKER_URL=http://localhost:54321/functions/v1/opencode-supabase-broker
|
|
141
|
-
export OPENCODE_SUPABASE_OAUTH_CLIENT_ID=<your_supabase_oauth_app_client_id>
|
|
142
|
-
export OPENCODE_SUPABASE_OAUTH_PORT=14589
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
Notes:
|
|
146
|
-
|
|
147
|
-
- `OPENCODE_SUPABASE_BROKER_URL` is optional. If not provided, defaults to the official OpenCode broker.
|
|
148
|
-
- `OPENCODE_SUPABASE_OAUTH_CLIENT_ID` must match the OAuth app configured for the broker.
|
|
149
|
-
- `OPENCODE_SUPABASE_OAUTH_PORT` controls the local callback URL the plugin listens on.
|
|
150
|
-
- The callback path is `/auth/callback`, so the full local callback URL is `http://localhost:<port>/auth/callback`.
|
|
151
|
-
|
|
152
|
-
Your Supabase OAuth app must allow that redirect URI.
|
|
153
|
-
|
|
154
|
-
### Local development
|
|
155
|
-
|
|
156
|
-
The plugin inherits environment variables from the OpenCode CLI process. For local broker development, point the plugin at your local function URL before launching OpenCode:
|
|
157
|
-
|
|
158
|
-
```bash
|
|
159
|
-
export OPENCODE_SUPABASE_BROKER_URL=http://localhost:54321/functions/v1/opencode-supabase-broker
|
|
160
|
-
opencode
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
Quick local test flow:
|
|
164
|
-
|
|
165
|
-
1. create `supabase/functions/.env` from `supabase/functions/.env.example`
|
|
166
|
-
2. start the broker locally
|
|
167
|
-
3. export the consumer-project variables above
|
|
168
|
-
4. launch `opencode` in the consumer repo
|
|
169
|
-
5. run `/supabase`
|
|
170
|
-
|
|
171
|
-
### Deployment
|
|
172
|
-
|
|
173
|
-
Deploy the broker as a single Supabase Edge Function:
|
|
174
|
-
|
|
175
|
-
```bash
|
|
176
|
-
supabase functions deploy opencode-supabase-broker
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
Set secrets in the Supabase dashboard or via CLI:
|
|
180
|
-
|
|
181
|
-
```bash
|
|
182
|
-
supabase secrets set \
|
|
183
|
-
OPENCODE_SUPABASE_OAUTH_CLIENT_ID=<client_id> \
|
|
184
|
-
OPENCODE_SUPABASE_OAUTH_CLIENT_SECRET=<client_secret>
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
The plugin should call the deployed function URL at:
|
|
188
|
-
|
|
189
|
-
```
|
|
190
|
-
https://<project-ref>.supabase.co/functions/v1/opencode-supabase-broker/exchange
|
|
191
|
-
https://<project-ref>.supabase.co/functions/v1/opencode-supabase-broker/refresh
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
The plugin uses the official OpenCode broker by default. Override via `OPENCODE_SUPABASE_BROKER_URL` if self-hosting.
|
|
195
|
-
|
|
196
|
-
### Request and response shapes
|
|
197
|
-
|
|
198
|
-
Both endpoints accept JSON and return JSON. Error responses use a normalized shape:
|
|
199
|
-
|
|
200
|
-
```json
|
|
201
|
-
{
|
|
202
|
-
"error": {
|
|
203
|
-
"code": "invalid_request",
|
|
204
|
-
"message": "description of what was rejected"
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
```
|
|
25
|
+
- **Connect** your Supabase account from OpenCode
|
|
26
|
+
- **List** organizations and projects
|
|
27
|
+
- **Get** project API keys
|
|
28
|
+
- **Create** new Supabase projects
|
|
208
29
|
|
|
209
|
-
|
|
30
|
+
## Reference
|
|
210
31
|
|
|
211
|
-
|
|
32
|
+
- Supabase Management API: https://supabase.com/docs/reference/api/introduction
|
package/package.json
CHANGED
package/src/shared/api.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export const DEFAULT_SUPABASE_OAUTH_AUTHORIZE_URL = "https://api.supabase.com/v1/oauth/authorize";
|
|
2
2
|
export const DEFAULT_SUPABASE_API_BASE_URL = "https://api.supabase.com/v1";
|
|
3
3
|
export const DEFAULT_SUPABASE_BROKER_URL = "https://iaoxncwzemnfxcdwakzb.supabase.co/functions/v1/opencode-supabase-broker";
|
|
4
|
+
export const DEFAULT_SUPABASE_OAUTH_CLIENT_ID = "80c76733-1b96-424b-976b-5c2977c72008";
|
|
5
|
+
export const DEFAULT_SUPABASE_OAUTH_PORT = 14589;
|
|
4
6
|
|
|
5
7
|
import type { FetchLike, SupabaseSharedConfig } from "./types.ts";
|
|
6
8
|
|
package/src/shared/cfg.ts
CHANGED
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
DEFAULT_SUPABASE_API_BASE_URL,
|
|
5
5
|
DEFAULT_SUPABASE_BROKER_URL,
|
|
6
6
|
DEFAULT_SUPABASE_OAUTH_AUTHORIZE_URL,
|
|
7
|
+
DEFAULT_SUPABASE_OAUTH_CLIENT_ID,
|
|
8
|
+
DEFAULT_SUPABASE_OAUTH_PORT,
|
|
7
9
|
} from "./api.ts";
|
|
8
10
|
import type { SupabaseEnv, SupabaseSharedConfig } from "./types.ts";
|
|
9
11
|
|
|
@@ -23,13 +25,6 @@ function readPortOption(options: PluginOptions | undefined, key: string) {
|
|
|
23
25
|
return undefined;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
function requireString(value: string | undefined, key: string) {
|
|
27
|
-
if (!value) {
|
|
28
|
-
throw new Error(`Missing required Supabase config: ${key}`);
|
|
29
|
-
}
|
|
30
|
-
return value;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
28
|
function requirePort(value: number | string | undefined) {
|
|
34
29
|
if (value === undefined) {
|
|
35
30
|
throw new Error("Missing required Supabase config: oauthPort");
|
|
@@ -47,15 +42,19 @@ export function readSupabaseConfig(
|
|
|
47
42
|
options: PluginOptions | undefined,
|
|
48
43
|
env: SupabaseEnv = process.env,
|
|
49
44
|
): SupabaseSharedConfig {
|
|
50
|
-
const clientId =
|
|
51
|
-
readStringOption(options, "clientId") ??
|
|
52
|
-
|
|
53
|
-
|
|
45
|
+
const clientId =
|
|
46
|
+
readStringOption(options, "clientId") ??
|
|
47
|
+
readEnvString(env.OPENCODE_SUPABASE_OAUTH_CLIENT_ID) ??
|
|
48
|
+
DEFAULT_SUPABASE_OAUTH_CLIENT_ID;
|
|
54
49
|
const oauthPort = requirePort(
|
|
55
|
-
readPortOption(options, "oauthPort") ??
|
|
50
|
+
readPortOption(options, "oauthPort") ??
|
|
51
|
+
env.OPENCODE_SUPABASE_OAUTH_PORT ??
|
|
52
|
+
DEFAULT_SUPABASE_OAUTH_PORT,
|
|
56
53
|
);
|
|
57
54
|
const brokerBaseUrl =
|
|
58
|
-
readStringOption(options, "brokerBaseUrl") ??
|
|
55
|
+
readStringOption(options, "brokerBaseUrl") ??
|
|
56
|
+
readEnvString(env.OPENCODE_SUPABASE_BROKER_URL) ??
|
|
57
|
+
DEFAULT_SUPABASE_BROKER_URL;
|
|
59
58
|
|
|
60
59
|
return {
|
|
61
60
|
clientId,
|
package/src/tui/dialog.tsx
CHANGED
|
@@ -38,7 +38,9 @@ export function SupabaseDialog(props: SupabaseDialogProps) {
|
|
|
38
38
|
|
|
39
39
|
// Handle the response shape from the plugin API
|
|
40
40
|
if (authResponse.error) {
|
|
41
|
-
throw new Error(
|
|
41
|
+
throw new Error(
|
|
42
|
+
authResponse.error.message || "Failed to start OAuth authorization",
|
|
43
|
+
);
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
const authData = authResponse.data;
|
|
@@ -69,7 +71,9 @@ export function SupabaseDialog(props: SupabaseDialogProps) {
|
|
|
69
71
|
})) as unknown as ApiResponse<boolean>;
|
|
70
72
|
|
|
71
73
|
if (callbackResponse.error) {
|
|
72
|
-
throw new Error(
|
|
74
|
+
throw new Error(
|
|
75
|
+
callbackResponse.error.message || "OAuth callback failed",
|
|
76
|
+
);
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
const callbackSucceeded = callbackResponse.data === true;
|
|
@@ -78,14 +82,16 @@ export function SupabaseDialog(props: SupabaseDialogProps) {
|
|
|
78
82
|
setState({ type: "success" });
|
|
79
83
|
props.api.ui.toast({
|
|
80
84
|
variant: "success",
|
|
81
|
-
message:
|
|
85
|
+
message:
|
|
86
|
+
"Connected to Supabase! Tools are ready to use. Ask your agent about supabase.",
|
|
82
87
|
});
|
|
83
88
|
props.onClose();
|
|
84
89
|
} else {
|
|
85
90
|
throw new Error("OAuth authorization was denied");
|
|
86
91
|
}
|
|
87
92
|
} catch (error) {
|
|
88
|
-
const message =
|
|
93
|
+
const message =
|
|
94
|
+
error instanceof Error ? error.message : "Authorization failed";
|
|
89
95
|
setState({ type: "error", message });
|
|
90
96
|
props.api.ui.toast({
|
|
91
97
|
variant: "error",
|