@rtrentjones/greenlight 0.4.1 → 0.5.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/assets/skills/provider-cloudflare/SKILL.md +27 -26
- package/assets/skills/provider-gemini/SKILL.md +36 -50
- package/assets/skills/provider-github/SKILL.md +26 -25
- package/assets/skills/provider-hcp/SKILL.md +17 -18
- package/assets/skills/provider-neon/SKILL.md +28 -32
- package/assets/skills/provider-oci/SKILL.md +42 -53
- package/assets/skills/provider-supabase/SKILL.md +21 -16
- package/assets/skills/provider-vercel/SKILL.md +36 -33
- package/dist/bin.js +310 -131
- package/dist/{chunk-P6FRYOOV.js → chunk-OBWWE7GE.js} +14 -8
- package/dist/index.js +1 -1
- package/package.json +5 -5
|
@@ -1,45 +1,46 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: provider-cloudflare
|
|
3
|
-
description:
|
|
3
|
+
description: Cloudflare in a Greenlight setup — the always-on DNS/zone provider, the keepalive Worker host, the workers runtime. Use when wiring DNS, the keepalive Worker, a workers-target tool, or debugging a Cloudflare apply or token scope.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# provider-cloudflare
|
|
7
7
|
|
|
8
|
-
Cloudflare is the **always-on** provider
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
Cloudflare is the **always-on** provider: it owns the DNS zone for the domain (every tool's
|
|
9
|
+
`<name>.<domain>` CNAME), hosts the **keepalive** Worker (a Cron Trigger, immune to
|
|
10
|
+
repo-inactivity disable), and is the `target: workers` runtime for the blog and throwaway MCP
|
|
11
|
+
dev targets.
|
|
12
12
|
|
|
13
13
|
## Token — `CLOUDFLARE_API_TOKEN`
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
Create at dash → My Profile → API Tokens → Custom Token. Push it straight to GitHub Actions
|
|
23
|
-
with `greenlight secrets gather` (or `gh secret set CLOUDFLARE_API_TOKEN`) — Greenlight keeps
|
|
24
|
-
no local secret file. `greenlight add` verifies it against `/user/tokens/verify` (status must
|
|
25
|
-
be `active`) before you commit.
|
|
15
|
+
Scopes, creation, and the verify command live in
|
|
16
|
+
[tokens-reference.md](https://github.com/RTrentJones/greenlight/blob/main/docs/tokens-reference.md).
|
|
17
|
+
The short of it: **Account · Workers Scripts:Edit + Zone · DNS:Edit + Account · Account
|
|
18
|
+
Settings:Read** (+ **Account · Cloudflare Tunnel:Edit** only if any tool is `target: oci`).
|
|
19
|
+
Single store: **GitHub Actions secrets** (no local secret file). `greenlight add` fails fast on a
|
|
20
|
+
mis-scoped token — beyond `status: active` it probes `/accounts`, so a Zone-DNS-only token (which
|
|
21
|
+
can't see the account it needs) is rejected before you commit.
|
|
26
22
|
|
|
27
23
|
## Terraform modules
|
|
28
24
|
|
|
29
|
-
- `infra/modules/tool` — the subdomain DNS record. `proxied = target != "vercel"` (Vercel
|
|
30
|
-
|
|
25
|
+
- `infra/modules/tool` — the subdomain DNS record. `proxied = target != "vercel"` (Vercel needs an
|
|
26
|
+
unproxied CNAME to `cname.vercel-dns.com`; everything else is proxied).
|
|
31
27
|
- `infra/modules/keepalive` — `cloudflare_workers_script` + `cloudflare_workers_cron_trigger`,
|
|
32
|
-
self-contained (
|
|
33
|
-
|
|
34
|
-
on the account once (error 10063 if missing).
|
|
28
|
+
self-contained (bundled `worker.js`). **One** worker aggregates all targets via `targets_json`;
|
|
29
|
+
do not emit a worker per tool.
|
|
35
30
|
|
|
36
31
|
## MCP
|
|
37
32
|
|
|
38
33
|
`.mcp.json` wires `cloudflare` (Workers/DNS/R2/KV/D1/builds/observability) + `cloudflare-docs`.
|
|
39
|
-
Run `/mcp` to authenticate.
|
|
34
|
+
Run `/mcp` to authenticate.
|
|
40
35
|
|
|
41
36
|
## Gotchas
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
- **Account id from a scoped token (`/memberships` 403).** A scoped API token can't call
|
|
38
|
+
`/memberships` to discover the account id, so Workers/wrangler deploys that need it fail. Resolve
|
|
39
|
+
the account id **from the zone** in CI (the agent-lane deploy workflow does exactly this) or write
|
|
40
|
+
it into `wrangler.toml` at `add` time — never rely on `/memberships`.
|
|
41
|
+
- **workers.dev subdomain (error 10063).** A first Workers deploy needs a workers.dev subdomain
|
|
42
|
+
registered on the account once.
|
|
43
|
+
- **Wrangler vs Terraform DNS ownership.** A record managed by wrangler (a Workers custom domain)
|
|
44
|
+
collides with a Terraform `cloudflare_dns_record` of the same name — one owner per record.
|
|
45
|
+
- **`observability` block on `cloudflare_workers_script`** trips a provider bug (propagation_policy
|
|
46
|
+
conversion error) — leave it off.
|
|
@@ -1,78 +1,64 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: provider-gemini
|
|
3
|
-
description:
|
|
3
|
+
description: The `agent` lane in Greenlight — an autonomous cron-triggered Cloudflare Worker backed by Google Gemini (free tier). Use when building, deploying, or verifying an agent tool, or debugging its KV / account-id / seed wiring.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# provider-gemini
|
|
7
7
|
|
|
8
8
|
The `agent` lane is an **autonomous tool**: a Cloudflare Worker that wakes on a **cron trigger**,
|
|
9
|
-
calls **Gemini** (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
`agent` → target **workers**, data **none | kv** (kv holds the last output + run metadata).
|
|
9
|
+
calls **Gemini** (free tier), does low-stakes work, stores the result in KV, and exposes a tiny
|
|
10
|
+
HTTP surface. It's the keepalive-Worker pattern promoted to a user tool — free, always-available,
|
|
11
|
+
immune to repo-inactivity, no OCI box, no paid account. `agent` → target **workers**, data
|
|
12
|
+
**none | kv** (kv holds the last output + run metadata).
|
|
14
13
|
|
|
15
14
|
## Token — `GEMINI_API_KEY`
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
Creation + verify live in
|
|
17
|
+
[tokens-reference.md](https://github.com/RTrentJones/greenlight/blob/main/docs/tokens-reference.md).
|
|
18
|
+
**Free tier, no billing / no card** (Google AI Studio). One key serves every agent (shared, not
|
|
19
|
+
per-tool); stored as a **Cloudflare Worker secret** (`wrangler secret put`), never in the repo.
|
|
20
|
+
`RUN_TOKEN` (bearer-gates `POST /run`) is the second Worker secret.
|
|
21
21
|
|
|
22
22
|
## The model + call
|
|
23
23
|
|
|
24
|
-
`gemini-2.5-flash` (fast;
|
|
24
|
+
`gemini-2.5-flash` (fast; ~15 RPM / 1500 req/day free, so a daily cron is ~1/day).
|
|
25
25
|
|
|
26
26
|
```
|
|
27
27
|
POST https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${GEMINI_API_KEY}
|
|
28
|
-
{ "contents": [{ "parts": [{ "text": "<prompt>" }] }] }
|
|
29
|
-
→ candidates[0].content.parts[0].text
|
|
28
|
+
{ "contents": [{ "parts": [{ "text": "<prompt>" }] }] } → candidates[0].content.parts[0].text
|
|
30
29
|
```
|
|
31
30
|
|
|
32
31
|
## Deploy — emitted CI (push to main)
|
|
33
32
|
|
|
34
|
-
`greenlight add`
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
the first run, and verifies. So the only setup is **adding those two GitHub secrets**. (Local instead:
|
|
40
|
-
`pnpm exec wrangler deploy --env prod`.)
|
|
41
|
-
|
|
42
|
-
`wrangler.toml` carries the cron + KV binding + the per-env `custom_domain` route; no Terraform.
|
|
33
|
+
`greenlight add` emits `.github/workflows/deploy-<name>.yml`. On a push to main that touches
|
|
34
|
+
`tools/<name>` it: creates the KV namespace (find-or-create in CI), deploys the Worker (cron +
|
|
35
|
+
`custom_domain`), sets `GEMINI_API_KEY` + `RUN_TOKEN` Worker secrets from GitHub secrets, seeds the
|
|
36
|
+
first run, and verifies. The only manual setup is **adding those two GitHub secrets**. `wrangler.toml`
|
|
37
|
+
carries the cron + KV binding + per-env `custom_domain`; no Terraform. (Local: `wrangler deploy --env prod`.)
|
|
43
38
|
|
|
44
|
-
|
|
45
|
-
[triggers]
|
|
46
|
-
crons = ["0 13 * * *"] # daily; stays far under the free-tier quota
|
|
47
|
-
[[kv_namespaces]]
|
|
48
|
-
binding = "STATE"
|
|
49
|
-
[[routes]]
|
|
50
|
-
pattern = "<name>.<domain>"
|
|
51
|
-
custom_domain = true
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Surface
|
|
39
|
+
## Surface + verify
|
|
55
40
|
|
|
56
41
|
| route | purpose |
|
|
57
42
|
|---|---|
|
|
58
43
|
| `scheduled()` | the cron: prompt Gemini → `STATE.put(today, text + metadata)` |
|
|
59
|
-
| `GET /` |
|
|
44
|
+
| `GET /` | latest output (public, read-only) |
|
|
60
45
|
| `GET /status` | `{ ok, lastRun, model, preview }` — the **api-mode verify** target |
|
|
61
|
-
| `POST /run` | force a run — **bearer-gated** (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
deploy
|
|
46
|
+
| `POST /run` | force a run — **bearer-gated** (`RUN_TOKEN`); lets deploy/verify seed the first output |
|
|
47
|
+
|
|
48
|
+
`verify.config.ts` hits `/status` and asserts `ok` + a recent run. (Output *quality* is a future
|
|
49
|
+
`eval` mode — LLM-judged.)
|
|
50
|
+
|
|
51
|
+
## Gotchas
|
|
52
|
+
- **KV namespace as code (no manual id).** The deploy workflow does a **find-or-create** on the KV
|
|
53
|
+
namespace in CI and binds it — the `wrangler.toml` id stays a placeholder, never hand-filled.
|
|
54
|
+
- **Account id from a scoped token.** `add` resolves the Cloudflare account id into `wrangler.toml`
|
|
55
|
+
so wrangler skips the `/memberships` call a scoped token can't make (see provider-cloudflare).
|
|
56
|
+
- **Seed before verify.** The first cron may not have fired at deploy time, so `/status` would
|
|
57
|
+
(correctly) report no run — the deploy step `POST /run`s once to seed *before* verifying. Don't
|
|
58
|
+
reorder these.
|
|
59
|
+
- **No keepalive.** The cron *is* the heartbeat and the edge Worker is always-available — never add
|
|
60
|
+
an agent to `module.keepalive.targets_json`.
|
|
68
61
|
|
|
69
62
|
## Safety envelope
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
- **Bearer on `/run`**; the cron frequency stays far under the free-tier daily limit.
|
|
73
|
-
- Key is **secret-only** (a Worker secret), never committed or echoed.
|
|
74
|
-
|
|
75
|
-
## No keepalive
|
|
76
|
-
|
|
77
|
-
An agent needs no keepalive — the cron *is* its heartbeat and the Worker is always-available
|
|
78
|
-
(Cloudflare's edge, not a reclaimable box). Don't add it to `module.keepalive.targets_json`.
|
|
63
|
+
Low-stakes / read-only first agents (generate → store → serve, no destructive external actions);
|
|
64
|
+
bearer on `/run`; cron frequency far under the free-tier daily limit; key secret-only, never echoed.
|
|
@@ -1,43 +1,42 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: provider-github
|
|
3
|
-
description:
|
|
3
|
+
description: GitHub in a Greenlight setup — the single secret store (Actions secrets/environments), the repo/branch/protection module, the develop→main flow, OIDC-over-PAT. Use when setting tokens, wiring branch protection/environments, or debugging gh / secrets / CI auth.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# provider-github
|
|
7
7
|
|
|
8
|
-
GitHub is the **always-on** control plane: it holds the Actions **secrets** (provider tokens),
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
GitHub is the **always-on** control plane: it holds the Actions **secrets** (provider tokens), the
|
|
9
|
+
**environments** (beta/prod), branch protection, and runs the deploy/promote/infra workflows. The
|
|
10
|
+
`develop → main` flow is standardized (PR → preview, `develop` → beta, `main` → prod; promote is a
|
|
11
|
+
gated fast-forward).
|
|
12
12
|
|
|
13
|
-
## Token — `GITHUB_TOKEN` (usually
|
|
13
|
+
## Token — `GITHUB_TOKEN` (you usually don't set one)
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
for protection). Prefer **GitHub OIDC → cloud** over long-lived cloud tokens where supported.
|
|
15
|
+
In **CI**, Actions provides `github.token` automatically — `infra.yml` maps it to the `github`
|
|
16
|
+
provider; no PAT for single-repo infra. For **cross-repo** ops, use a fine-grained PAT with minimal
|
|
17
|
+
scopes; prefer **GitHub OIDC → cloud** over long-lived cloud tokens where supported. Full token table:
|
|
18
|
+
[tokens-reference.md](https://github.com/RTrentJones/greenlight/blob/main/docs/tokens-reference.md).
|
|
20
19
|
|
|
21
20
|
### Poly-repo deploy loop — the two option-B PATs
|
|
22
21
|
|
|
23
22
|
An adopted tool (submodule) and its wrapper hand off via two fine-grained PATs (`secrets gather`
|
|
24
|
-
pushes each to the right repo
|
|
25
|
-
|
|
26
|
-
- **`GREENLIGHT_DISPATCH_TOKEN`** — on the **tool** repo, scoped **Contents: write** on the
|
|
23
|
+
pushes each to the right repo):
|
|
24
|
+
- **`GREENLIGHT_DISPATCH_TOKEN`** — on the **tool** repo, scoped **Contents:write** on the
|
|
27
25
|
**wrapper** → the tool's build fires `repository_dispatch` so the wrapper deploys.
|
|
28
|
-
- **`GREENLIGHT_STATUS_TOKEN_<TOOL>`** — on the **wrapper** repo, scoped **Commit statuses:
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
- **`GREENLIGHT_STATUS_TOKEN_<TOOL>`** — on the **wrapper** repo, scoped **Commit statuses:write** on
|
|
27
|
+
the **tool** → the wrapper posts deploy/verify status back. **Per-tool suffix** (it lives on the
|
|
28
|
+
shared wrapper alongside other tools' tokens).
|
|
31
29
|
|
|
32
30
|
Provider creds (OCI/Cloudflare/…) live **only in the wrapper**; the tool repo holds just the
|
|
33
|
-
dispatch PAT (its build pushes to GHCR with the built-in `github.token`).
|
|
31
|
+
dispatch PAT (its build pushes to GHCR with the built-in `github.token`). Optimal end state: a tool
|
|
32
|
+
repo holds **only** `GREENLIGHT_DISPATCH_TOKEN`.
|
|
34
33
|
|
|
35
34
|
## Setting secrets
|
|
36
35
|
|
|
37
|
-
GitHub Actions secrets are the **single**
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
GitHub Actions secrets are the **single** store (no local secret file). `greenlight secrets gather
|
|
37
|
+
<tool> [--repo o/r] [--env <env>]` prompts with hidden input and pipes straight to `gh secret set`
|
|
38
|
+
(never on disk, in argv, or logs); `greenlight secrets check [<tool>]` lists the secrets a tool's
|
|
39
|
+
deploy needs and flags missing ones. Run `gh auth login` first.
|
|
41
40
|
|
|
42
41
|
## Terraform module — `infra/modules/repo`
|
|
43
42
|
|
|
@@ -45,6 +44,8 @@ Branches + protection + required checks (+ optional environments via the `tool`
|
|
|
45
44
|
`manage_github_environments`). For an **external** tool (code in its own repo), set
|
|
46
45
|
`manage_github_environments = false` so the wrapper's CI stays single-repo (no PAT needed).
|
|
47
46
|
|
|
48
|
-
##
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
## Gotchas
|
|
48
|
+
- **IDs are repo variables, not secrets.** Enumerable IDs (`CLOUDFLARE_ZONE_ID`, account ids) carry
|
|
49
|
+
no authority — store them as `vars.*` (referenced `${{ vars.X }}`), not secrets.
|
|
50
|
+
- **Protected `main`** blocks direct pushes/merges — use `gh pr create` + merge, or the gated
|
|
51
|
+
`greenlight promote` fast-forward. Keep to `develop` (not `development`) for the branch.
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: provider-hcp
|
|
3
|
-
description:
|
|
3
|
+
description: HCP Terraform in a Greenlight setup — the remote-state backend (free tier, no card) in Local execution mode (HCP stores state + locks; runs use CI creds). Use when setting up remote state, debugging a backend/init/locking issue, or CI apply-on-push.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# provider-hcp
|
|
7
7
|
|
|
8
|
-
HCP Terraform (app.terraform.io) is the **remote-state backend** for the wrapper's infra —
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
HCP Terraform (app.terraform.io) is the **remote-state backend** for the wrapper's infra — free
|
|
9
|
+
tier, **no credit card**. It replaces local state so CI can `terraform apply` on push with state
|
|
10
|
+
locking (no two applies racing).
|
|
11
11
|
|
|
12
12
|
## Execution mode — **Local**, deliberately
|
|
13
13
|
|
|
14
14
|
The workspace is set to **Local execution mode**: HCP **stores state + does locking only**;
|
|
15
|
-
`terraform` runs here / in CI with **our own provider creds** (
|
|
16
|
-
|
|
15
|
+
`terraform` runs here / in CI with **our own provider creds** (from GitHub Actions secrets). This
|
|
16
|
+
avoids uploading every provider token to HCP.
|
|
17
17
|
|
|
18
18
|
## Token — `TF_API_TOKEN`
|
|
19
19
|
|
|
20
|
-
HCP → Account Settings → Tokens
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
A **user** API token (HCP → Account Settings → Tokens). Verify command + table:
|
|
21
|
+
[tokens-reference.md](https://github.com/RTrentJones/greenlight/blob/main/docs/tokens-reference.md).
|
|
22
|
+
In CI it maps to the backend-auth env var **`TF_TOKEN_app_terraform_io`** (`infra.yml` does this).
|
|
23
23
|
|
|
24
24
|
## The `cloud{}` block
|
|
25
25
|
|
|
@@ -32,15 +32,14 @@ terraform {
|
|
|
32
32
|
}
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
Migrate local → HCP with a plain `terraform init` (answer `yes` to copy state). The
|
|
36
|
-
`-migrate-state` / `-force-copy` flags are **rejected** for the cloud backend — don't pass them.
|
|
37
|
-
|
|
38
35
|
## CI apply-on-push
|
|
39
36
|
|
|
40
|
-
`infra.yml` (on push to `main`, paths `infra/**`): map GH secrets → `TF_TOKEN_app_terraform_io`
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
`infra.yml` (on push to `main`, paths `infra/**`): map GH secrets → `TF_TOKEN_app_terraform_io` + the
|
|
38
|
+
provider tokens + `TF_VAR_*`, then setup-terraform (`terraform_wrapper: false`) → init → plan -out →
|
|
39
|
+
apply. The CLI only edits the `.tf`; CI applies.
|
|
43
40
|
|
|
44
|
-
##
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
## Gotchas
|
|
42
|
+
- **Migrate local → HCP** with a plain `terraform init` (answer `yes` to copy state). The
|
|
43
|
+
`-migrate-state` / `-force-copy` flags are **rejected** for the cloud backend — don't pass them.
|
|
44
|
+
- **Alternatives:** `docs/terraform-state.md` has the full backend chooser (HCP no-CC · OCI
|
|
45
|
+
S3-compat · R2 card-required · AWS · local).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: provider-neon
|
|
3
|
-
description:
|
|
3
|
+
description: Neon in a Greenlight setup — the `data: neon` store (serverless Postgres, branch-per-env, auto-resume → no keepalive). Use when wiring a tool's database, choosing Neon vs Supabase, or debugging a Neon apply or connection string.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# provider-neon
|
|
@@ -9,15 +9,16 @@ description: How Neon works in a Greenlight setup — the `data: neon` store (se
|
|
|
9
9
|
bundled. One Neon **project** per tool, **a branch per env** (git-style copy-on-write): `prod` is
|
|
10
10
|
the project's default branch; `beta` is a child branch (separate data, instant to create). Compute
|
|
11
11
|
**autosuspends and auto-resumes on the next connection**, so a Neon tool needs **no keepalive** —
|
|
12
|
-
that's the whole reason Neon is preferred over Supabase
|
|
13
|
-
unpause. Choose `supabase` only when you need bundled auth + storage + realtime together.
|
|
12
|
+
that's the whole reason Neon is preferred over Supabase (which pauses for 7 days and needs a manual
|
|
13
|
+
unpause). Choose `supabase` only when you need bundled auth + storage + realtime together.
|
|
14
14
|
|
|
15
15
|
## Token — `NEON_API_KEY`
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
Creation + verify live in
|
|
18
|
+
[tokens-reference.md](https://github.com/RTrentJones/greenlight/blob/main/docs/tokens-reference.md).
|
|
19
|
+
Account-level (configures the `neon` provider for every Neon tool, like `CLOUDFLARE_API_TOKEN`) —
|
|
20
|
+
**not** per-tool, and there is **no per-tool secret**: the role/password/connection strings are
|
|
21
|
+
module OUTPUTS, not inputs.
|
|
21
22
|
|
|
22
23
|
## Terraform module — `infra/modules/neon`
|
|
23
24
|
|
|
@@ -29,37 +30,32 @@ which is ephemeral/per-PR — created by CI, not Terraform). Outputs two per-env
|
|
|
29
30
|
The emitted `<name>.tf` wires `database_url["prod"]`/`["beta"]` into the Vercel env per target, so
|
|
30
31
|
prod and beta hit **different branches**. Pin the provider `kislerdm/neon ~> 0.13`.
|
|
31
32
|
|
|
32
|
-
## No keepalive
|
|
33
|
-
|
|
34
|
-
Do **not** add a Neon tool to `module.keepalive.targets_json`. Neon resumes on connect — a request
|
|
35
|
-
just wakes it. (`doctor` does not flag `data: neon` for keepalive; that exemption is intentional.)
|
|
36
|
-
|
|
37
33
|
## Schema as code / migrations
|
|
38
34
|
|
|
39
|
-
**Greenlight does NOT run migrations — by design.**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
-
|
|
45
|
-
|
|
46
|
-
failed migrate fails the build = a natural gate.
|
|
47
|
-
- **Greenlight's role**: the **dangerous-SQL gate**. Run `greenlight migrations scan` (no `<dir>` →
|
|
48
|
-
it auto-detects `supabase/migrations | migrations | drizzle/migrations | …`) in CI before the migrate.
|
|
49
|
-
|
|
50
|
-
See [docs/migrations.md](../../../docs/migrations.md).
|
|
35
|
+
**Greenlight does NOT run migrations — by design.** Schema lives in the tool (Drizzle/Prisma or
|
|
36
|
+
plain `.sql`); the app's own build runs its migrate against the wired **`DIRECT_URL`** (prod build →
|
|
37
|
+
prod branch, preview build → preview branch; a failed migrate fails the build = a natural gate). The
|
|
38
|
+
**native Neon↔Vercel integration** owns ephemeral per-PR preview branches (don't put those in
|
|
39
|
+
Terraform). Greenlight's only role is the **dangerous-SQL gate**: run `greenlight migrations scan`
|
|
40
|
+
(auto-detects `supabase/migrations | migrations | drizzle/migrations | …`) in CI before the migrate.
|
|
41
|
+
See [migrations.md](https://github.com/RTrentJones/greenlight/blob/main/docs/migrations.md).
|
|
51
42
|
|
|
52
43
|
## Sharing one DB + multi-account
|
|
53
|
-
|
|
54
|
-
- **One DB, many services**: a second tool sets `dataShareWith: '<owner>'` (or `add … --share <owner>`)
|
|
44
|
+
- **One DB, many services:** a second tool sets `dataShareWith: '<owner>'` (or `add … --share <owner>`)
|
|
55
45
|
— it creates no project and wires the owner's connection strings.
|
|
56
|
-
- **A second Neon account
|
|
57
|
-
provider authenticates that account. (A sharer can't also override
|
|
46
|
+
- **A second Neon account:** `tokenOverrides: { NEON_API_KEY: 'NEON_API_KEY_X' }` → an aliased `neon`
|
|
47
|
+
provider authenticates that account. (A sharer can't also override.)
|
|
58
48
|
|
|
59
49
|
## MCP
|
|
60
|
-
|
|
61
50
|
`.mcp.json` wires `neon` (hosted) with `Authorization: Bearer ${NEON_API_KEY}`. Run `/mcp` to auth.
|
|
62
51
|
|
|
63
|
-
##
|
|
64
|
-
|
|
65
|
-
|
|
52
|
+
## Gotchas
|
|
53
|
+
- **Free-tier `history_retention_seconds` cap (21600).** Neon's free plan caps history retention at
|
|
54
|
+
**21600s (6h)** — the module must not request more or the apply 400s. Point-in-time restore is
|
|
55
|
+
bounded to that window on free.
|
|
56
|
+
- **`pooler_enabled` is a non-issue.** Both connection strings are module outputs regardless — wire
|
|
57
|
+
`database_url` (pooled) to the app and `direct_url` to migrations; there's no pooler flag to toggle.
|
|
58
|
+
- **No keepalive.** Don't add a Neon tool to `module.keepalive.targets_json` — it resumes on connect,
|
|
59
|
+
and `doctor` intentionally does not flag `data: neon` for keepalive.
|
|
60
|
+
- **The blog stays `data: none`.** Neon's auto-resume makes it safe for *tools*, but the apex blog
|
|
61
|
+
uses D1/KV/external only (it must never depend on a store that can pause or error).
|
|
@@ -1,74 +1,63 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: provider-oci
|
|
3
|
-
description:
|
|
3
|
+
description: Oracle Cloud (OCI) in a Greenlight setup — the `target: oci` runtime for stateful MCP servers on a free-tier Ampere A1 Container Instance (build-via-GitHub→GHCR; Greenlight-owned compute + tunnel). Use when wiring or debugging an oci-target tool.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# provider-oci
|
|
7
7
|
|
|
8
8
|
OCI is the `target: oci` runtime for **stateful** services that don't fit serverless — the
|
|
9
|
-
canonical case is **BAMCP** (a stateful MCP server). The split: **the tool is provider-agnostic
|
|
10
|
-
|
|
9
|
+
canonical case is **BAMCP** (a stateful MCP server). The split: **the tool is provider-agnostic and
|
|
10
|
+
just builds a container via GitHub; Greenlight owns the OCI infra** (compute + tunnel + DNS),
|
|
11
11
|
configured in the wrapper.
|
|
12
12
|
|
|
13
13
|
## Free tier — A1 Container Instance + GHCR
|
|
14
14
|
|
|
15
|
-
The Always-Free path is an **OCI Container Instance** on **Ampere A1** (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
tool
|
|
20
|
-
|
|
21
|
-
## Provider-agnostic tool → GHCR
|
|
22
|
-
|
|
23
|
-
The tool repo (BAMCP) has ONE job: a GitHub Actions workflow that **builds + pushes the container
|
|
24
|
-
to GHCR**. No OCI, no SSH, no deploy logic — portable to any provider's infra.
|
|
15
|
+
The Always-Free path is an **OCI Container Instance** on **Ampere A1** (2 OCPU / 12 GB, shared
|
|
16
|
+
across VM / Bare-Metal / Container-Instances). No VM, no cloud-init, no SSH. The image comes from
|
|
17
|
+
**GHCR** (free); **OCI's own registry (OCIR) is paid** — that was the old-pipeline trap. The
|
|
18
|
+
instance runs the tool container + a **cloudflared sidecar** (shared netns → localhost), exposed at
|
|
19
|
+
`<name>.<domain>`. The tool repo's only job: a GitHub Actions workflow that builds + pushes to GHCR.
|
|
25
20
|
|
|
26
21
|
## Greenlight OCI infra (Terraform, in the wrapper)
|
|
27
22
|
|
|
28
23
|
`greenlight add/adopt` emits, per oci tool:
|
|
29
|
-
- **`oci-container-instance`**
|
|
30
|
-
|
|
31
|
-
- **`tunnel`**
|
|
32
|
-
- **`tool`** DNS
|
|
33
|
-
The `oci`
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
module creates the VCN + a public (egress-only) subnet, and the container-instance module looks the
|
|
47
|
-
AD up via an `oci_identity_availability_domains` data source. So the bootstrap is just "create one
|
|
48
|
-
API key" — Terraform can't create the credential it needs to authenticate, but it owns everything
|
|
49
|
-
after that. (Out-of-A1-capacity in one AD? set `availability_domain` on the instance module to pin
|
|
50
|
-
another — the only time you touch it.)
|
|
51
|
-
|
|
52
|
-
**Shortcut — feed the API-key config preview directly.** After *Add API key*, OCI shows a
|
|
53
|
-
"Configuration file preview" (the `[DEFAULT]` block) and you download the `.pem`. Pass both:
|
|
54
|
-
`greenlight secrets gather <tool> --repo <o/r> --oci-config ~/path/config [--oci-key ~/path/key.pem]`
|
|
55
|
-
— it auto-fills the 5 auth secrets (incl. the multi-line PEM, read from the file so it's never
|
|
56
|
-
pasted) and only prompts for the 3 placement values + the option-B deploy PATs.
|
|
24
|
+
- **`oci-container-instance`** — the container instance (tool image from GHCR + cloudflared sidecar
|
|
25
|
+
with the tunnel token), `CI.Standard.A1.Flex` within the free allotment, restart ALWAYS.
|
|
26
|
+
- **`tunnel`** — cloudflared tunnel + ingress `<name>.<domain> → http://localhost:8000` + token.
|
|
27
|
+
- **`tool`** DNS — CNAME → the tunnel.
|
|
28
|
+
- The `oci-network` module owns the VCN + public subnet; the instance module looks the AD up via a
|
|
29
|
+
data source. The `oci` provider is added to `infra/main.tf`.
|
|
30
|
+
|
|
31
|
+
## Token — OCI API-key auth
|
|
32
|
+
|
|
33
|
+
Auth values + the gather flow live in
|
|
34
|
+
[tokens-reference.md](https://github.com/RTrentJones/greenlight/blob/main/docs/tokens-reference.md).
|
|
35
|
+
The **only manual OCI inputs are the API-key auth values** (`TF_VAR_OCI_TENANCY_OCID`,
|
|
36
|
+
`TF_VAR_OCI_USER_OCID`, `TF_VAR_OCI_FINGERPRINT`, `TF_VAR_OCI_PRIVATE_KEY` PEM, `TF_VAR_OCI_REGION`;
|
|
37
|
+
`TF_VAR_OCI_COMPARTMENT_ID` optional → root). Auth is API-key request signing (no bearer → no
|
|
38
|
+
fetch-verify). **Shortcut:** `greenlight secrets gather <tool> --repo <o/r> --oci-config ~/config
|
|
39
|
+
[--oci-key ~/key.pem]` auto-fills the 5 auth secrets from OCI's "Configuration file preview" + the
|
|
40
|
+
`.pem` (the multi-line key is read from the file, never pasted).
|
|
57
41
|
|
|
58
42
|
## Deploy = restart (re-pull)
|
|
59
43
|
|
|
60
|
-
`greenlight deploy <tool>` (oci)
|
|
61
|
-
--container-instance-id <OCID>` — the instance re-pulls the latest GHCR image. The tool's CI
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
## Idle-reclaim — stay free, recover on alert
|
|
65
|
-
|
|
66
|
-
OCI Always-Free can reclaim idle compute. We **stay on the free tier** and accept that: the
|
|
67
|
-
instance runs restart-policy ALWAYS, keepalive health-checks it, and if it's ever stopped/reclaimed
|
|
68
|
-
the alert fires and a **re-apply / redeploy** restores it. **PAYG is NOT used** — it's an optional
|
|
69
|
-
last resort (see `docs/oci-payg-runbook.md`) only if reclaim ever becomes a recurring problem.
|
|
44
|
+
`greenlight deploy <tool>` (oci) runs `oci container-instances container-instance restart
|
|
45
|
+
--container-instance-id <OCID>` — the instance re-pulls the latest GHCR image. The tool's CI builds;
|
|
46
|
+
the deploy event fires the restart. The adapter does **not** build. The instance OCID is **not** a
|
|
47
|
+
manual input — the deploy workflow resolves it from OCI by display name (= the tool name).
|
|
70
48
|
|
|
71
49
|
## Verify
|
|
72
50
|
The tool is typically an **MCP server**: verify with `mode: mcp`, connect at `<name>.<domain>/mcp`
|
|
73
|
-
(FastMCP's `streamable_http_app()` serves `/mcp` by default
|
|
74
|
-
|
|
51
|
+
(FastMCP's `streamable_http_app()` serves `/mcp` by default). If auth gates `initialize`, supply a
|
|
52
|
+
token or use an `api`-mode 401 check. Keepalive health-checks `target: oci`.
|
|
53
|
+
|
|
54
|
+
## Gotchas
|
|
55
|
+
- **OCIR is paid — use GHCR.** Pulling from OCI's own registry bills; the free path is GHCR.
|
|
56
|
+
- **Idle-reclaim → stay free, recover on alert.** Always-Free can reclaim idle compute. We **stay on
|
|
57
|
+
the free tier**: restart-policy ALWAYS + keepalive health-check + alert → re-apply/redeploy
|
|
58
|
+
restores it. **PAYG is NOT used** (optional last resort; see `docs/oci-payg-runbook.md`). Never
|
|
59
|
+
imply the harness *prevents* reclaim — it only nags and recovers.
|
|
60
|
+
- **Out-of-A1-capacity in one AD** → set `availability_domain` on the instance module to pin another
|
|
61
|
+
AD (the only time you touch it).
|
|
62
|
+
- **Rotation.** The API signing key is the one long-lived credential — rotate it on a schedule (see
|
|
63
|
+
docs/security.md); nothing automated rotates it.
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: provider-supabase
|
|
3
|
-
description:
|
|
3
|
+
description: Supabase in a Greenlight setup — the `data: supabase` store (Postgres + auth + storage + realtime), schema-per-env, the 7-day pause trap solved by keepalive. Use when wiring a tool's database, debugging a paused project, or a Supabase apply.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# provider-supabase
|
|
7
7
|
|
|
8
8
|
`data: supabase` is for tools that need bundled **auth + storage + realtime** together
|
|
9
|
-
(HeistMind). One Supabase **project**, **schema-per-env** (beta/prod share the project —
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
(HeistMind). One Supabase **project**, **schema-per-env** (beta/prod share the project — NOT
|
|
10
|
+
project-per-env; branching is paid). The project is **imported**, not recreated: name/region are
|
|
11
|
+
replace-forcing, so the module sets `ignore_changes` to protect the live DB.
|
|
12
12
|
|
|
13
13
|
## Token — `SUPABASE_ACCESS_TOKEN`
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
import, so `import-placeholder` is fine for an
|
|
15
|
+
Creation + verify live in
|
|
16
|
+
[tokens-reference.md](https://github.com/RTrentJones/greenlight/blob/main/docs/tokens-reference.md).
|
|
17
|
+
Management API PAT, **account-scoped** (it manages every project in the account — keep it tight).
|
|
18
|
+
Single store: GitHub Actions secrets. The DB password (`TF_VAR_supabase_database_password`) is only
|
|
19
|
+
used if the project is *recreated* — ignored on import, so `import-placeholder` is fine for an
|
|
20
|
+
existing project.
|
|
20
21
|
|
|
21
22
|
## Terraform module — `infra/modules/supabase`
|
|
22
23
|
|
|
23
|
-
Single project, import-safe. Outputs `url`, `anon_key`, `service_role_key`, `project_ref` —
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
Single project, import-safe. Outputs `url`, `anon_key`, `service_role_key`, `project_ref` — feed
|
|
25
|
+
these straight into the consumer (e.g. the Vercel env block). To re-apply from fresh state, import
|
|
26
|
+
first: `terraform import module.<name>_supabase.supabase_project.this <ref>`.
|
|
26
27
|
|
|
27
28
|
## Keepalive — non-negotiable
|
|
28
29
|
|
|
@@ -32,10 +33,14 @@ Supabase **pauses a free project after ~7 days idle** — this is what takes too
|
|
|
32
33
|
The ping counts any HTTP response (even 401 on `/rest/v1/`) as alive.
|
|
33
34
|
|
|
34
35
|
## MCP
|
|
35
|
-
|
|
36
36
|
`.mcp.json` wires `supabase` (hosted, **read-only**): needs `SUPABASE_ACCESS_TOKEN` +
|
|
37
37
|
`SUPABASE_PROJECT_REF` in the env (Claude Code expands `${VAR}` in the url/header). Run `/mcp`.
|
|
38
38
|
|
|
39
|
-
##
|
|
40
|
-
The
|
|
41
|
-
external services. Supabase is per-tool, only when the bundled features are needed together.
|
|
39
|
+
## Gotchas
|
|
40
|
+
- **The blog must never use Supabase** for state (it must stay up unattended) — use D1/KV or
|
|
41
|
+
external services. Supabase is per-tool, only when the bundled features are needed together.
|
|
42
|
+
- **Migrations gate.** A supabase tool that owns `supabase/migrations` must run `greenlight
|
|
43
|
+
migrations scan` in the CI that applies them (before `supabase db push`) — `doctor` flags a
|
|
44
|
+
migrations dir whose workflows don't.
|
|
45
|
+
- **Forgetting keepalive** is the #1 silent-death cause for a supabase tool — `doctor` does not yet
|
|
46
|
+
assert it, so wire `targets_json` when you add the tool.
|