@withmata/blueprints 0.7.0 → 3.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/.claude/skills/audit/SKILL.md +6 -6
- package/.claude/skills/blueprint-catalog/SKILL.md +4 -0
- package/.claude/skills/scaffold-env/SKILL.md +10 -13
- package/.claude/skills/scaffold-foundation/SKILL.md +5 -6
- package/.claude/skills/scaffold-infisical/SKILL.md +173 -0
- package/ENGINEERING.md +1 -1
- package/blueprints/features/env-t3/BLUEPRINT.md +26 -33
- package/blueprints/features/secrets-infisical/BLUEPRINT.md +295 -0
- package/blueprints/features/secrets-infisical/files/docs/INFISICAL.md +119 -0
- package/blueprints/features/secrets-infisical/files/scripts/infisical-map.sh +115 -0
- package/blueprints/features/secrets-infisical/files/scripts/infisical-sync.sh +89 -0
- package/dist/index.js +11 -2
- package/package.json +1 -1
- package/blueprints/features/env-t3/files/nextjs/.env.example +0 -17
- package/blueprints/features/env-t3/files/server/.env.example +0 -14
|
@@ -76,7 +76,7 @@ Regardless of whether project context exists, explore the actual project to unde
|
|
|
76
76
|
### 3f. Environment & Validation
|
|
77
77
|
|
|
78
78
|
- How are env vars handled? t3-env with client/server split, single env.ts, raw `process.env`, dotenv only?
|
|
79
|
-
-
|
|
79
|
+
- Are the Zod schema files well-commented as the variable registry? (No `.env.example` — values come from Infisical or the platform.)
|
|
80
80
|
- Is there build-time validation (env imported in `next.config.ts`)?
|
|
81
81
|
|
|
82
82
|
### 3g. Code Quality
|
|
@@ -172,10 +172,10 @@ For each installed blueprint with updates, show detailed information. Use plain
|
|
|
172
172
|
│ File: src/env/server.ts
|
|
173
173
|
│
|
|
174
174
|
│ • Clearer documentation for teammates
|
|
175
|
-
│ The
|
|
176
|
-
│
|
|
177
|
-
│
|
|
178
|
-
│ File:
|
|
175
|
+
│ The env schema files now document each variable
|
|
176
|
+
│ (purpose, local/production hints) — the schema is
|
|
177
|
+
│ the registry; values come from Infisical.
|
|
178
|
+
│ File: src/env/server.ts
|
|
179
179
|
│
|
|
180
180
|
│ To apply: /scaffold-env
|
|
181
181
|
└─────────────────────────────────────────────────────
|
|
@@ -316,7 +316,7 @@ What was done
|
|
|
316
316
|
Blueprint upgrades:
|
|
317
317
|
✓ foundation — consolidated maintenance rules,
|
|
318
318
|
cleaned up legacy patterns
|
|
319
|
-
✓ env-t3 — updated server.ts
|
|
319
|
+
✓ env-t3 — updated server.ts schema (the registry)
|
|
320
320
|
✓ tailwind-v4 — updated shared-styles.css, added
|
|
321
321
|
tailwind-scrollbar-hide
|
|
322
322
|
|
|
@@ -21,6 +21,7 @@ user-invocable: false
|
|
|
21
21
|
| Environment Variables | Feature | T3 Env + Zod: validated, type-safe env vars for Next.js (client/server split) and backend apps. | None | `/scaffold-env` |
|
|
22
22
|
| Tailwind v4 Design System | Feature | Full Tailwind v4 upgrade: shadcn tokens, shadows, easing, animations, sidebar/chart tokens, dark mode. Auto-detects `/design-system` output. | Foundation (recommended) | `/scaffold-tailwind` |
|
|
23
23
|
| UI Shared Components | Feature | 31 base components (Base UI + Phosphor): compound patterns, form system, app shell, data display. Auto-installs Tailwind. | Tailwind v4 (auto-installed) | `/scaffold-ui` |
|
|
24
|
+
| Secrets (Infisical) | Feature | Centralized secret management: /shared domain folders with environment-aware references, placeholder injection, and `infisical run` wiring. | Environment Variables (env-t3) | `/scaffold-infisical` |
|
|
24
25
|
|
|
25
26
|
## Project Context
|
|
26
27
|
|
|
@@ -50,6 +51,8 @@ Suggest a blueprint when you observe these signals. Be helpful, not pushy — me
|
|
|
50
51
|
|
|
51
52
|
**`/scaffold-ui`** — User mentions shared components, UI package, component library, shadcn in monorepo, or needs to share components across apps. Recommend `/scaffold-tailwind` first if not installed.
|
|
52
53
|
|
|
54
|
+
**`/scaffold-infisical`** — User mentions Infisical, secret management, shared secrets across apps, secret references, rotating keys in one place, or has the same secret duplicated across multiple `.env`/Infisical folders. Requires `/scaffold-env` first (reads the T3 schemas).
|
|
55
|
+
|
|
53
56
|
**`/audit`** — User asks "what should I do next?", wants to understand their project's state, or has hand-rolled infrastructure that a blueprint could improve.
|
|
54
57
|
|
|
55
58
|
## Lifecycle & Dependencies
|
|
@@ -64,6 +67,7 @@ Discovery → Foundation → Features is the recommended flow, but blueprints ca
|
|
|
64
67
|
- **Need marketing pages:** Run `/copywrite-landing` for landing pages, `/copywrite` for other pages (pricing, about, features)
|
|
65
68
|
- **Dependencies:** `/scaffold-auth` works best after `/scaffold-db` (auth builds on db patterns). `/scaffold-ui` auto-installs `/scaffold-tailwind`. `/scaffold-tailwind` auto-detects design system tokens.
|
|
66
69
|
- **Environment setup:** `/scaffold-env` is recommended for all projects — it's standalone
|
|
70
|
+
- **Secret management:** after `/scaffold-env`, run `/scaffold-infisical` to move secret delivery into Infisical with a shared single-source-of-truth structure (env-aware references). Best for multi-app/multi-environment projects
|
|
67
71
|
|
|
68
72
|
## Important
|
|
69
73
|
|
|
@@ -54,7 +54,6 @@ If `env-t3` is found in `.project-context.md` under `## Installed Blueprints`, s
|
|
|
54
54
|
|
|
55
55
|
Updated templates:
|
|
56
56
|
☐ src/env/server.ts — updated Zod patterns
|
|
57
|
-
☐ .env.example — new documentation sections
|
|
58
57
|
|
|
59
58
|
New files available:
|
|
60
59
|
☐ apis/server/src/env.ts — backend env validation (not set up previously)
|
|
@@ -100,7 +99,6 @@ Find all applications that need env setup:
|
|
|
100
99
|
For each app found:
|
|
101
100
|
- Does `env.ts` already exist? (Foundation blueprint creates a basic one)
|
|
102
101
|
- Does `src/env/` directory exist? (Already has the split pattern)
|
|
103
|
-
- Does `.env.example` exist?
|
|
104
102
|
- Does `next.config.ts` already import env files?
|
|
105
103
|
- Are `@t3-oss/env-nextjs` or `@t3-oss/env-core` already installed?
|
|
106
104
|
|
|
@@ -144,9 +142,8 @@ For each Next.js app:
|
|
|
144
142
|
3. Update `next.config.ts` — add imports at the top
|
|
145
143
|
|
|
146
144
|
**For all Next.js apps:**
|
|
147
|
-
4. Create `.env.
|
|
148
|
-
5.
|
|
149
|
-
6. If other blueprints are installed, merge their env vars into the appropriate schema
|
|
145
|
+
4. Create `.env.local` (empty file)
|
|
146
|
+
5. If other blueprints are installed, merge their env vars into the appropriate schema
|
|
150
147
|
|
|
151
148
|
### 4b. Backend Apps
|
|
152
149
|
|
|
@@ -157,9 +154,8 @@ For each backend app:
|
|
|
157
154
|
```typescript
|
|
158
155
|
import { env } from "./env.js";
|
|
159
156
|
```
|
|
160
|
-
3. Create `.env.
|
|
161
|
-
4.
|
|
162
|
-
5. If the app has hardcoded `process.env.PORT` or similar, refactor to use `env.PORT`
|
|
157
|
+
3. Create `.env.local` (empty file)
|
|
158
|
+
4. If the app has hardcoded `process.env.PORT` or similar, refactor to use `env.PORT`
|
|
163
159
|
|
|
164
160
|
### 4c. Dependency Installation
|
|
165
161
|
|
|
@@ -209,11 +205,11 @@ Then append:
|
|
|
209
205
|
|
|
210
206
|
```markdown
|
|
211
207
|
### env-t3 maintenance
|
|
212
|
-
-
|
|
208
|
+
- The Zod schema files (client.ts/server.ts/env.ts) are the registry of env vars — keep them well-commented. There is no .env.example; values come from Infisical or the platform.
|
|
209
|
+
- After adding a new env var: add it to the env schema file (client.ts or server.ts) and, if used locally, .env.local
|
|
213
210
|
- NEXT_PUBLIC_* vars go in env/client.ts with explicit runtimeEnv mapping; server vars go in env/server.ts
|
|
214
|
-
- After removing an env var: remove from schema
|
|
211
|
+
- After removing an env var: remove from the schema and .env.local
|
|
215
212
|
- Never access process.env directly — always use the typed env objects (clientEnv, serverEnv, or env)
|
|
216
|
-
- Keep .env.example documented with section headers and local/production hints
|
|
217
213
|
```
|
|
218
214
|
|
|
219
215
|
If a `### env-t3 maintenance` section already exists in the file, skip — do not duplicate.
|
|
@@ -253,9 +249,10 @@ import { env } from "./env.js";
|
|
|
253
249
|
|
|
254
250
|
### Next Steps
|
|
255
251
|
|
|
256
|
-
- Fill in `.env.local` with your actual values (
|
|
257
|
-
- As you add features, add their env vars to the schema files
|
|
252
|
+
- Fill in `.env.local` with your actual values (the env schema files document what's needed), or run via `infisical run` once `/scaffold-infisical` is set up
|
|
253
|
+
- As you add features, add their env vars to the schema files (the registry)
|
|
258
254
|
- Run `bun dev` to verify env validation passes
|
|
255
|
+
- For multi-app/multi-environment projects, run `/scaffold-infisical` next to centralize secret delivery (a /shared single source of truth with environment-aware references) instead of per-app `.env` files
|
|
259
256
|
|
|
260
257
|
## Important Notes
|
|
261
258
|
|
|
@@ -205,9 +205,8 @@ Follow the `env-t3` blueprint's integration steps:
|
|
|
205
205
|
import "./src/env/server";
|
|
206
206
|
import "./src/env/client";
|
|
207
207
|
```
|
|
208
|
-
4. Create `apps/web/.env.
|
|
209
|
-
5.
|
|
210
|
-
6. Ensure `@t3-oss/env-nextjs` and `zod` are in `apps/web/package.json` dependencies
|
|
208
|
+
4. Create `apps/web/.env.local` (empty)
|
|
209
|
+
5. Ensure `@t3-oss/env-nextjs` and `zod` are in `apps/web/package.json` dependencies
|
|
211
210
|
|
|
212
211
|
### 5.5b. Frontend UI Setup (if opted in)
|
|
213
212
|
|
|
@@ -318,11 +317,11 @@ Then append sections for each blueprint applied in this run. Foundation always a
|
|
|
318
317
|
- Keep `turbo.json` tasks with `cache: false` for any task with side effects (migrations, code generation)
|
|
319
318
|
|
|
320
319
|
### env-t3 maintenance
|
|
321
|
-
-
|
|
320
|
+
- The Zod schema files (client.ts/server.ts/env.ts) are the registry of env vars — keep them well-commented. There is no .env.example; values come from Infisical or the platform.
|
|
321
|
+
- After adding a new env var: add it to the env schema file (client.ts or server.ts) and, if used locally, .env.local
|
|
322
322
|
- NEXT_PUBLIC_* vars go in env/client.ts with explicit runtimeEnv mapping; server vars go in env/server.ts
|
|
323
|
-
- After removing an env var: remove from schema
|
|
323
|
+
- After removing an env var: remove from the schema and .env.local
|
|
324
324
|
- Never access process.env directly — always use the typed env objects (clientEnv, serverEnv, or env)
|
|
325
|
-
- Keep .env.example documented with section headers and local/production hints
|
|
326
325
|
```
|
|
327
326
|
|
|
328
327
|
If frontend UI was applied, also append:
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: scaffold-infisical
|
|
3
|
+
description: Scaffold Infisical secret management — a /shared domain structure with environment-aware references, placeholder injection, and infisical run wiring — driven by the project's T3 env schemas
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Scaffold Infisical Secrets Blueprint
|
|
7
|
+
|
|
8
|
+
Scaffold the `secrets-infisical` blueprint into the current project. Read the full blueprint first, then adapt it to this project's stack. This blueprint **depends on `env-t3`** — it reads the T3 Zod schemas to learn which variables exist, then builds the Infisical structure around them.
|
|
9
|
+
|
|
10
|
+
## Step 1: Read the Blueprint
|
|
11
|
+
|
|
12
|
+
Read the full BLUEPRINT.md and all template files:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
~/.withmata/blueprints/blueprints/features/secrets-infisical/BLUEPRINT.md
|
|
16
|
+
~/.withmata/blueprints/blueprints/features/secrets-infisical/files/**/*
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
If not found at these paths, run `npx @withmata/blueprints` to install.
|
|
20
|
+
|
|
21
|
+
## Step 1.5: Read Project Context
|
|
22
|
+
|
|
23
|
+
Check if `.project-context.md` exists in the target project root:
|
|
24
|
+
|
|
25
|
+
- **If it exists**: read it to understand:
|
|
26
|
+
- Confirm `env-t3` is installed. If NOT, **stop** and tell the user to run `/scaffold-env` first (hard dependency — the T3 schemas are the source of truth for the variable list).
|
|
27
|
+
- Note `db-drizzle-postgres` / `auth-better-auth` — they define the natural domains (databases, auth).
|
|
28
|
+
- npm scope, workspace layout, project type.
|
|
29
|
+
- If `secrets-infisical` is already listed → switch to upgrade mode (Step 1.6).
|
|
30
|
+
|
|
31
|
+
- **If it does not exist**: env-t3 cannot be confirmed installed. Ask the user whether the project has T3 env schemas; if not, point them to `/scaffold-env` first.
|
|
32
|
+
|
|
33
|
+
## Step 1.6: Upgrade Mode
|
|
34
|
+
|
|
35
|
+
If `secrets-infisical` is found in `.project-context.md` under `## Installed Blueprints`:
|
|
36
|
+
|
|
37
|
+
1. Read the recorded domain map, environments, and apps wired.
|
|
38
|
+
2. Re-read the T3 schemas; diff against the recorded shared keys.
|
|
39
|
+
3. For each NEW var: classify (shared vs app-native), pick its domain, plan a placeholder in `/shared/<domain>` for every env + an env-aware reference in each consuming app folder.
|
|
40
|
+
4. Present additions; never delete user secrets without confirmation.
|
|
41
|
+
5. Update `scripts/infisical-map.sh`, re-run the sync script in dry-run, then apply.
|
|
42
|
+
6. Update `installed_at`; skip to Step 9 (Output Summary).
|
|
43
|
+
|
|
44
|
+
## Step 0: Preconditions (verify before any changes)
|
|
45
|
+
|
|
46
|
+
- `infisical --version` works (else: `brew install infisical/get-cli/infisical`).
|
|
47
|
+
- `infisical login` done.
|
|
48
|
+
- An Infisical project exists and `infisical init` has produced a committed `.infisical.json` in each app/package dir (workspaceId only — no secrets).
|
|
49
|
+
- The project's environments are known (default `dev`, `staging`, `prod`). Confirm with the user; capture the exact environment **slugs** — these go into the reference prefix and MUST match Infisical's env slugs.
|
|
50
|
+
|
|
51
|
+
## Step 2: Understand the Target Project
|
|
52
|
+
|
|
53
|
+
- Detect project type (monorepo vs single-repo) — reuse the same detection as scaffold-env (`turbo.json`, `"workspaces"`, `pnpm-workspace.yaml`, etc.).
|
|
54
|
+
- Discover apps/packages that need secrets (`apps/web`, `apps/server`, `packages/db`, …).
|
|
55
|
+
- **Read the T3 env schemas** in each (`env/client.ts`, `env/server.ts`, backend `env.ts`, plus how `packages/db` reads `process.env`) → the full variable list per app.
|
|
56
|
+
|
|
57
|
+
## Step 3: Derive and Confirm the Plan (do not apply yet)
|
|
58
|
+
|
|
59
|
+
- Classify each var:
|
|
60
|
+
- **app-native** (`NEXT_PUBLIC_*`, `PORT`, single-app config) → real value in the app folder, never referenced.
|
|
61
|
+
- **shared** (DB URLs, API keys, anything >1 app uses, or a domain's canonical key) → goes in `/shared/<domain>` + an env-aware reference in each consuming app.
|
|
62
|
+
- When ambiguous, ask.
|
|
63
|
+
- Group shared vars into domains (derive from installed blueprints + schema groups; see the BLUEPRINT domain table).
|
|
64
|
+
- Build a dry-run plan: folders, shared placeholders, app references, app-native vars left in place.
|
|
65
|
+
- **Present the domain map + plan and ask the user to confirm/edit.** Encourage verbose, self-describing names (`IMAGE_BUCKET_ENDPOINT`, not `STORAGE_ENDPOINT`). Offer an optional rename/cleanup sub-step if the project has vague names or dead vars.
|
|
66
|
+
|
|
67
|
+
## Step 4: Generate the Sync Script + Data Map
|
|
68
|
+
|
|
69
|
+
- Write `scripts/infisical-sync.sh` (idempotent, dry-run by default, `APPLY=1` to apply, loops the environments list) and `scripts/infisical-map.sh` (the confirmed `ENVIRONMENTS`, `DOMAINS`, `APP_PATHS`, `SHARED_MAP`, `REF_MAP`).
|
|
70
|
+
- The reference helper MUST emit the env-prefixed form, substituting the current loop environment's slug:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
REF() { printf '${%s.shared.%s.%s}' "$ENV" "$1" "$2"; } # $ENV is the slug
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- Shared placeholders use the `REPLACE_ME` sentinel (or sensible non-secret defaults). Never overwrite an existing value.
|
|
77
|
+
- `chmod +x scripts/infisical-sync.sh`.
|
|
78
|
+
|
|
79
|
+
## Step 5: Apply
|
|
80
|
+
|
|
81
|
+
- Run dry-run (`APPLY=0`), show the plan, get the OK, then run with `APPLY=1`. The script loops every environment.
|
|
82
|
+
|
|
83
|
+
## Step 6: Wire package.json
|
|
84
|
+
|
|
85
|
+
- Add/adjust `infisical run --path=/apps/<app> --env=<env>` wrappers in dev/run scripts; add `:dev` variants for `packages/db` scripts. Leave production scripts clean (the host injects Infisical).
|
|
86
|
+
|
|
87
|
+
## Step 7: Verify (do not skip)
|
|
88
|
+
|
|
89
|
+
For at least the dev environment, resolve a reference end to end:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
infisical run --path=/apps/server --env=dev -- printenv CORE_DATABASE_URL
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
- Prints a value → good.
|
|
96
|
+
- Prints the literal `${dev.shared...}` → the reference is malformed or the shared key/folder is missing. Fix before continuing. (Most common past failure: the bare `${shared...}` form without the env slug.)
|
|
97
|
+
|
|
98
|
+
## Step 8: Record + Maintenance Skill
|
|
99
|
+
|
|
100
|
+
Append an entry to `.project-context.md` under `## Installed Blueprints`:
|
|
101
|
+
|
|
102
|
+
```yaml
|
|
103
|
+
### secrets-infisical
|
|
104
|
+
blueprint: secrets-infisical
|
|
105
|
+
installed_at: <date>
|
|
106
|
+
choices:
|
|
107
|
+
placeholder_sentinel: REPLACE_ME
|
|
108
|
+
script_language: bash
|
|
109
|
+
environments:
|
|
110
|
+
- <env slugs>
|
|
111
|
+
domain_map:
|
|
112
|
+
<domain>: [<keys>]
|
|
113
|
+
apps_wired:
|
|
114
|
+
- path: <appPath>
|
|
115
|
+
references: [<domains>]
|
|
116
|
+
script_path: scripts/infisical-sync.sh
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
If `.project-context.md` does not exist, create it with the standard header and the Installed Blueprints section.
|
|
120
|
+
|
|
121
|
+
### Create Maintenance Skill
|
|
122
|
+
|
|
123
|
+
Append to the shared project-level maintenance skill at `<project>/.claude/skills/project-maintenance/SKILL.md`. If the file does not exist, create it with frontmatter:
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
---
|
|
127
|
+
name: project-maintenance
|
|
128
|
+
description: Maintenance rules for all installed blueprints. Invoke when modifying schemas, env vars, auth config, workspace configs, UI components, or Tailwind tokens.
|
|
129
|
+
user-invocable: false
|
|
130
|
+
---
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Then append (skip if `### secrets-infisical maintenance` already exists — do not duplicate):
|
|
134
|
+
|
|
135
|
+
```markdown
|
|
136
|
+
### secrets-infisical maintenance
|
|
137
|
+
- Adding a SHARED env var (used by >1 app, or a domain's canonical value):
|
|
138
|
+
1. Add the key to its `/shared/<domain>` folder in EVERY environment, set to
|
|
139
|
+
`REPLACE_ME` (or a non-secret default).
|
|
140
|
+
2. Add an environment-aware reference `${<env>.shared.<domain>.<KEY>}` in each
|
|
141
|
+
consuming app/package folder, for every environment.
|
|
142
|
+
3. Add the key to scripts/infisical-map.sh (SHARED_MAP + REF_MAP) and re-run
|
|
143
|
+
`APPLY=1 ./scripts/infisical-sync.sh`.
|
|
144
|
+
4. Tell the human which `/shared/<domain>/<KEY>` placeholders to fill.
|
|
145
|
+
- Adding an APP-NATIVE var (PORT, NEXT_PUBLIC_*, single-app config): set it
|
|
146
|
+
directly in that app's Infisical folder as a real value — do NOT create a
|
|
147
|
+
shared key or reference.
|
|
148
|
+
- Reference syntax is ALWAYS `${<env-slug>.shared.<domain>.<KEY>}` where the slug
|
|
149
|
+
matches the environment the reference lives in. The bare `${shared...}` form
|
|
150
|
+
DOES NOT resolve. Verify with:
|
|
151
|
+
`infisical run --path=/apps/<app> --env=dev -- printenv <KEY>`.
|
|
152
|
+
- Rotating a secret: edit the single `/shared/<domain>/<KEY>` value; all
|
|
153
|
+
referencing apps inherit it. Never edit the value in an app folder.
|
|
154
|
+
- Removing a var: delete the shared key AND every app reference to it; remove from
|
|
155
|
+
infisical-map.sh.
|
|
156
|
+
- Never commit real secret values. `.infisical.json` is safe to commit; `.env*`
|
|
157
|
+
stays gitignored.
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Step 9: Output Summary
|
|
161
|
+
|
|
162
|
+
- List files created/modified, with a one-line description each.
|
|
163
|
+
- Print the full domain map and the per-app reference list.
|
|
164
|
+
- Print the app-native vars as "set directly — not referenced."
|
|
165
|
+
- Print the **`REPLACE_ME` checklist**: every `/shared/<domain>/<KEY>` still set to the placeholder, per environment, so the user knows exactly what to paste into the Infisical dashboard.
|
|
166
|
+
- Remind: real values are pasted in Infisical; the agent never sets them.
|
|
167
|
+
|
|
168
|
+
## Important Notes
|
|
169
|
+
|
|
170
|
+
- The reference slug MUST match the environment it lives in. The script iterates per env and substitutes `$ENV` into both `--env` and the reference string.
|
|
171
|
+
- Never write real secret values — only placeholders and plumbing.
|
|
172
|
+
- `.infisical.json` is safe to commit; `.env*` stays gitignored.
|
|
173
|
+
- Idempotent: re-running the sync script must be safe — `stub` only sets when empty, `link` just rewrites the same reference.
|
package/ENGINEERING.md
CHANGED
|
@@ -43,5 +43,5 @@ These preferences apply to ALL blueprints in this repo and to any project scaffo
|
|
|
43
43
|
- **Project context:** Every blueprint appends to `.project-context.md` in the target project root — tool-agnostic location readable by any AI coding tool
|
|
44
44
|
- **Skills live in `.claude/skills/<name>/SKILL.md`** — single source of truth. Installed globally to `~/.claude/skills/`, `~/.config/opencode/skills/`, `~/.cursor/skills/` via the CLI. OpenCode and Cursor also read `~/.claude/skills/` as a cross-tool fallback.
|
|
45
45
|
- **Rules/preferences are in `ENGINEERING.md`** — tool-specific files (`CLAUDE.md`, `AGENTS.md`, `.cursor/rules/engineering.mdc`) point to it
|
|
46
|
-
- **Available skills:** `/new-project`, `/discover`, `/copywrite`, `/copywrite-landing`, `/design-system`, `/scaffold-foundation`, `/scaffold-env`, `/scaffold-tailwind`, `/scaffold-ui`, `/scaffold-auth`, `/scaffold-db`, `/audit`, plus `blueprint-catalog` (background, auto-invoked)
|
|
46
|
+
- **Available skills:** `/new-project`, `/discover`, `/copywrite`, `/copywrite-landing`, `/design-system`, `/scaffold-foundation`, `/scaffold-env`, `/scaffold-tailwind`, `/scaffold-ui`, `/scaffold-auth`, `/scaffold-db`, `/scaffold-infisical`, `/audit`, plus `blueprint-catalog` (background, auto-invoked)
|
|
47
47
|
- **BLUEPRINT.md required** in every blueprint directory. Placeholder blueprints must still have a BLUEPRINT.md documenting intended scope.
|
|
@@ -10,7 +10,9 @@ Complete.
|
|
|
10
10
|
|
|
11
11
|
## Problem
|
|
12
12
|
|
|
13
|
-
Environment variable management is tedious and error-prone across projects. Developers
|
|
13
|
+
Environment variable management is tedious and error-prone across projects. Developers miss the split between client and server vars in Next.js, deploy with missing variables that only crash at runtime, and have no type safety when accessing `process.env`. This blueprint creates a validated, type-safe environment variable system using `@t3-oss/env` + Zod that catches missing or malformed variables at build time.
|
|
14
|
+
|
|
15
|
+
The Zod schema files are the **registry** of what environment variables an app needs — there is no separate `.env.example`. Values are delivered by Infisical (see the `secrets-infisical` blueprint) or the deployment platform; for local work, a developer uses `.env.local` or `infisical run`.
|
|
14
16
|
|
|
15
17
|
This blueprint covers both Next.js apps (client/server split with `@t3-oss/env-nextjs`) and backend apps (server-only with `@t3-oss/env-core`).
|
|
16
18
|
|
|
@@ -56,9 +58,9 @@ Both files are imported in `next.config.ts` to trigger build-time validation.
|
|
|
56
58
|
|
|
57
59
|
- **`z.preprocess` for PORT** (recommended) — Environment variables are always strings. PORT needs to be a number. `z.preprocess` handles the string-to-number conversion cleanly.
|
|
58
60
|
|
|
59
|
-
-
|
|
61
|
+
- **Schema files are the registry** (required) — The Zod schema files (`client.ts`, `server.ts`, `env.ts`) are the single source of truth for what environment variables an app needs. Keep them well-commented (purpose, local/production hints) — they replace the old `.env.example`. Values come from Infisical or the platform.
|
|
60
62
|
|
|
61
|
-
- **Empty `.env.local` created** (required) — An empty `.env.local` file is created during scaffolding so developers have the file ready to fill in. This file is gitignored.
|
|
63
|
+
- **Empty `.env.local` created** (required) — An empty `.env.local` file is created during scaffolding so developers have the file ready to fill in for local work. This file is gitignored.
|
|
62
64
|
|
|
63
65
|
---
|
|
64
66
|
|
|
@@ -70,7 +72,7 @@ Both files are imported in `next.config.ts` to trigger build-time validation.
|
|
|
70
72
|
- Zod for schema validation
|
|
71
73
|
- Client/server split in Next.js (two files)
|
|
72
74
|
- Build-time validation via `next.config.ts` import
|
|
73
|
-
- `.env.example`
|
|
75
|
+
- Well-commented schema files as the variable registry (no `.env.example`)
|
|
74
76
|
|
|
75
77
|
**Recommended defaults — ask during scaffolding:**
|
|
76
78
|
|
|
@@ -123,7 +125,6 @@ apps/web/
|
|
|
123
125
|
│ ├── client.ts # Client env schema + validation
|
|
124
126
|
│ └── server.ts # Server env schema + validation
|
|
125
127
|
├── next.config.ts # Imports both env files for build-time validation
|
|
126
|
-
├── .env.example # Documented env template
|
|
127
128
|
└── .env.local # Local values (gitignored)
|
|
128
129
|
```
|
|
129
130
|
|
|
@@ -152,7 +153,6 @@ apis/server/
|
|
|
152
153
|
├── src/
|
|
153
154
|
│ ├── env.ts # Server env schema + validation
|
|
154
155
|
│ └── index.ts # Imports env.ts at top
|
|
155
|
-
├── .env.example # Documented env template
|
|
156
156
|
└── .env.local # Local values (gitignored)
|
|
157
157
|
```
|
|
158
158
|
|
|
@@ -175,7 +175,6 @@ For standalone applications (Next.js or backend) without a monorepo, the pattern
|
|
|
175
175
|
|--------|----------|-------------|
|
|
176
176
|
| Next.js env location | `apps/web/src/env/` | `src/env/` |
|
|
177
177
|
| Backend env location | `apis/server/src/env.ts` | `src/env.ts` |
|
|
178
|
-
| `.env.example` location | Per-app (e.g., `apps/web/.env.example`) | Project root `.env.example` |
|
|
179
178
|
| `.env.local` location | Per-app (e.g., `apps/web/.env.local`) | Project root `.env.local` |
|
|
180
179
|
| Import path | `~/env/server` or `~/env/client` | `~/env/server` or `@/env/server` |
|
|
181
180
|
|
|
@@ -186,7 +185,7 @@ All core patterns are project-type-agnostic:
|
|
|
186
185
|
- Zod schemas for validation
|
|
187
186
|
- Client/server split (two files) for Next.js
|
|
188
187
|
- Build-time validation via `next.config.ts` import
|
|
189
|
-
-
|
|
188
|
+
- Well-commented schema files as the variable registry
|
|
190
189
|
- `emptyStringAsUndefined: true`
|
|
191
190
|
|
|
192
191
|
---
|
|
@@ -197,10 +196,8 @@ All core patterns are project-type-agnostic:
|
|
|
197
196
|
|---|---|---|---|
|
|
198
197
|
| `files/nextjs/env/client.ts` | Next.js client env schema | `apps/web/src/env/client.ts` | `src/env/client.ts` |
|
|
199
198
|
| `files/nextjs/env/server.ts` | Next.js server env schema | `apps/web/src/env/server.ts` | `src/env/server.ts` |
|
|
200
|
-
| `files/nextjs/.env.example` | Documented env template (Next.js) | `apps/web/.env.example` | `.env.example` |
|
|
201
199
|
| `files/nextjs/.env.local` | Empty local env file (Next.js) | `apps/web/.env.local` | `.env.local` |
|
|
202
200
|
| `files/server/env.ts` | Backend env schema (`@t3-oss/env-core`) | `apis/server/src/env.ts` | `src/env.ts` |
|
|
203
|
-
| `files/server/.env.example` | Documented env template (backend) | `apis/server/.env.example` | `.env.example` |
|
|
204
201
|
| `files/server/.env.local` | Empty local env file (backend) | `apis/server/.env.local` | `.env.local` |
|
|
205
202
|
|
|
206
203
|
---
|
|
@@ -222,29 +219,27 @@ All core patterns are project-type-agnostic:
|
|
|
222
219
|
import "./src/env/client";
|
|
223
220
|
```
|
|
224
221
|
If existing `import "./env"` is present, replace it with the two new imports.
|
|
225
|
-
6.
|
|
226
|
-
7.
|
|
227
|
-
8. If other blueprints are already installed, pre-populate their env vars into the appropriate schema files
|
|
222
|
+
6. Create `.env.local` (empty)
|
|
223
|
+
7. If other blueprints are already installed, pre-populate their env vars into the appropriate schema files
|
|
228
224
|
|
|
229
225
|
### Phase 2: Backend App Setup (if present)
|
|
230
226
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
12. Create `.env.local` (empty)
|
|
227
|
+
8. Copy `env.ts` to `src/env.ts` in the backend app — replace `{{APP_NAME}}`
|
|
228
|
+
9. Update the app's entry point (e.g., `src/index.ts`) to import `env.ts`:
|
|
229
|
+
```typescript
|
|
230
|
+
import { env } from "./env.js";
|
|
231
|
+
```
|
|
232
|
+
10. Create `.env.local` (empty)
|
|
238
233
|
|
|
239
234
|
### Phase 3: Dependency Installation
|
|
240
235
|
|
|
241
|
-
|
|
242
|
-
|
|
236
|
+
11. For each Next.js app: ensure `@t3-oss/env-nextjs` and `zod` are in `dependencies`
|
|
237
|
+
12. For each backend app: ensure `@t3-oss/env-core`, `zod`, and `dotenv` are in `dependencies`
|
|
243
238
|
|
|
244
239
|
### Phase 4: Verification
|
|
245
240
|
|
|
246
|
-
|
|
247
|
-
|
|
241
|
+
13. Run `bun dev` (or equivalent) — env validation should run without errors
|
|
242
|
+
14. Try removing a required variable from `.env.local` — build should fail with a clear error message
|
|
248
243
|
|
|
249
244
|
---
|
|
250
245
|
|
|
@@ -254,22 +249,22 @@ All core patterns are project-type-agnostic:
|
|
|
254
249
|
|
|
255
250
|
| When you... | Then do... | Why |
|
|
256
251
|
|---|---|---|
|
|
257
|
-
| Add a new env var in code | Add to `env/client.ts` or `env/server.ts` schema
|
|
252
|
+
| Add a new env var in code | Add to the `env/client.ts` or `env/server.ts` schema (the registry) and, if used locally, `.env.local` | Missing from schema = build-time crash; the schema documents what's needed |
|
|
258
253
|
| Add a `NEXT_PUBLIC_*` var | Put it in `env/client.ts` with both `client` schema and `runtimeEnv` entries | Client vars must be explicitly mapped in Next.js |
|
|
259
254
|
| Add a server-only var to Next.js | Put it in `env/server.ts` (uses `experimental__runtimeEnv: process.env`) | Avoids listing every server var twice |
|
|
260
|
-
| Remove an env var | Remove from schema file
|
|
255
|
+
| Remove an env var | Remove from the schema file and `.env.local` | Stale vars cause confusion |
|
|
261
256
|
| Add a new app to the monorepo | Create its own `env/` files — don't share env schemas between apps | Each app has different variable requirements |
|
|
262
|
-
| Change a var from optional to required | Update the Zod schema (remove `.optional()`) and ensure
|
|
257
|
+
| Change a var from optional to required | Update the Zod schema (remove `.optional()`) and ensure the value is provided (Infisical/platform/`.env.local`) | Will break builds where the value is empty |
|
|
263
258
|
|
|
264
259
|
### Condensed Rules for project rules file
|
|
265
260
|
|
|
266
261
|
```markdown
|
|
267
262
|
### env-t3 maintenance
|
|
268
|
-
-
|
|
263
|
+
- The Zod schema files (client.ts/server.ts/env.ts) are the registry of env vars — keep them well-commented. There is no .env.example; values come from Infisical or the platform.
|
|
264
|
+
- After adding a new env var: add it to the env schema file (client.ts or server.ts) and, if used locally, .env.local
|
|
269
265
|
- NEXT_PUBLIC_* vars go in env/client.ts with explicit runtimeEnv mapping; server vars go in env/server.ts
|
|
270
|
-
- After removing an env var: remove from schema
|
|
266
|
+
- After removing an env var: remove from the schema and .env.local
|
|
271
267
|
- Never access process.env directly — always use the typed env objects (clientEnv, serverEnv, or env)
|
|
272
|
-
- Keep .env.example documented with section headers and local/production hints
|
|
273
268
|
```
|
|
274
269
|
|
|
275
270
|
---
|
|
@@ -288,7 +283,7 @@ All core patterns are project-type-agnostic:
|
|
|
288
283
|
- **Client/server split** — Next.js apps always get two files, not one combined file
|
|
289
284
|
- **Build-time validation** — env files are always imported in `next.config.ts`
|
|
290
285
|
- **`emptyStringAsUndefined`** — always enabled (prevents empty string foot-guns)
|
|
291
|
-
-
|
|
286
|
+
- **Schema as registry** — the Zod schema files are the documented source of truth for env vars; no `.env.example` (values come from Infisical or the platform)
|
|
292
287
|
|
|
293
288
|
## Project Context Output
|
|
294
289
|
|
|
@@ -307,13 +302,11 @@ apps_configured:
|
|
|
307
302
|
files:
|
|
308
303
|
- src/env/client.ts
|
|
309
304
|
- src/env/server.ts
|
|
310
|
-
- .env.example
|
|
311
305
|
- .env.local
|
|
312
306
|
- name: server
|
|
313
307
|
type: backend
|
|
314
308
|
files:
|
|
315
309
|
- src/env.ts
|
|
316
|
-
- .env.example
|
|
317
310
|
- .env.local
|
|
318
311
|
packages_added:
|
|
319
312
|
- "@t3-oss/env-nextjs"
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# Secrets Management Blueprint — Infisical /shared + Env-Aware References
|
|
2
|
+
|
|
3
|
+
## Tier
|
|
4
|
+
|
|
5
|
+
Feature
|
|
6
|
+
|
|
7
|
+
## Status
|
|
8
|
+
|
|
9
|
+
Complete.
|
|
10
|
+
|
|
11
|
+
## Problem
|
|
12
|
+
|
|
13
|
+
Multi-app monorepos duplicate the same secret (DB URLs, API keys) across every app's env, so a rotation means editing N places and inevitably drifting. Local `.env` files also have to be copied into every fresh clone / workspace, and there's no single place that says "this is the canonical value." This blueprint centralizes every shared secret **once** in Infisical under a domain-grouped `/shared` folder, and has each app reference those values, so a single change propagates everywhere and a fresh checkout needs only `infisical login` + the committed `.infisical.json`.
|
|
14
|
+
|
|
15
|
+
The agent does all the plumbing (folders, placeholders, references, `infisical run` wiring); the human's only remaining job is to paste real secret values over the placeholders in the Infisical dashboard. The agent never invents or guesses secret values.
|
|
16
|
+
|
|
17
|
+
## Blueprint Dependencies
|
|
18
|
+
|
|
19
|
+
- **Required:** `env-t3` — the T3 Zod schemas (`env/client.ts`, `env/server.ts`, backend `env.ts`) are the source of truth for which variables exist; this blueprint reads them to build the Infisical structure. If `env-t3` is not installed, run `/scaffold-env` first.
|
|
20
|
+
- **Recommended:** `db-drizzle-postgres`, `auth-better-auth` — they define the natural domains (databases, auth) and their variables.
|
|
21
|
+
|
|
22
|
+
### Interaction with Other Blueprints
|
|
23
|
+
|
|
24
|
+
- `env-t3` defines *what* vars exist + validation. This blueprint defines *where the values live and how they're delivered*. They are complementary layers — `env-t3` stays usable without Infisical (some projects deliver secrets via Vercel/Railway directly), and Infisical cleanly **consumes** the T3 schemas instead of bloating them.
|
|
25
|
+
- When a feature blueprint adds an env var to a T3 schema, the maintenance rules here sync it into Infisical as a placeholder. The T3 schema change and the Infisical change are two halves of the same edit.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Architecture
|
|
30
|
+
|
|
31
|
+
### Core Pattern
|
|
32
|
+
|
|
33
|
+
- Canonical **values** live exactly once in `/shared/<domain>/<KEY>` (placeholders until a human fills them).
|
|
34
|
+
- Apps/packages hold **environment-aware references**: `${<ENV_SLUG>.shared.<domain>.<KEY>}` — never raw values for shared vars.
|
|
35
|
+
- **App-native** vars (`PORT`, `NEXT_PUBLIC_*`, langfuse keys, etc.) stay as real values in the app folder, never referenced.
|
|
36
|
+
- The CLI delivers secrets at launch via `infisical run --path=<app> --env=<env>`.
|
|
37
|
+
|
|
38
|
+
Change a shared value once → every app that references it sees the new value. No copy-paste drift across `apps/server`, `apps/web`, `packages/db`.
|
|
39
|
+
|
|
40
|
+
### Reference Syntax (CRITICAL)
|
|
41
|
+
|
|
42
|
+
References MUST be environment-prefixed, and the slug MUST match the environment the reference lives in:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
${dev.shared.pipeline.PIPELINE_VERSION} # in the dev environment
|
|
46
|
+
${staging.shared.pipeline.PIPELINE_VERSION} # in the staging environment
|
|
47
|
+
${prod.shared.pipeline.PIPELINE_VERSION} # in the prod environment
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The **same key** therefore has a **different literal value in each environment** — each points at its own environment's shared subtree.
|
|
51
|
+
|
|
52
|
+
The bare form `${shared.<domain>.<KEY>}` **DOES NOT RESOLVE** (this is the #1 past failure). Other wrong forms:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
${shared.pipeline.PIPELINE_VERSION} ← MISSING env slug — does not resolve
|
|
56
|
+
${env.shared.pipeline.PIPELINE_VERSION} ← literal "env" — wrong
|
|
57
|
+
${/shared/pipeline/PIPELINE_VERSION} ← slash path form — wrong for this case
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Verify with the §Verification check below — the reference must print its value, not the literal `${...}`.
|
|
61
|
+
|
|
62
|
+
### Domains
|
|
63
|
+
|
|
64
|
+
`/shared` is subdivided by **concern** (not by app), derived from installed blueprints + the T3 schemas: e.g. `core`, `auth`, `pipeline`, `knowledge-base`, `image-storage`, `ai`, `search`, `infra`. Domains are a soft taxonomy; the user confirms the map before anything is applied.
|
|
65
|
+
|
|
66
|
+
| Source signal | Domain(s) created |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `db-drizzle-postgres` installed, schema groups | one domain per DB group (`core`, `auth`, `pipeline`, …), each with a `*_DATABASE_URL` |
|
|
69
|
+
| `auth-better-auth` installed | `auth` (`BETTER_AUTH_*`, `GOOGLE_*`, `RESEND_EMAIL_API_KEY`) |
|
|
70
|
+
| Object storage / S3-style vars present | `image-storage`, `knowledge-base` (bucket keys) |
|
|
71
|
+
| LLM / model API keys present | `ai` |
|
|
72
|
+
| Web search / scraping / media API keys present | `search` |
|
|
73
|
+
| Redis / queue / cache | `infra` |
|
|
74
|
+
| Anything not classifiable | `core` (catch-all) or ask |
|
|
75
|
+
|
|
76
|
+
### Placeholders
|
|
77
|
+
|
|
78
|
+
Every shared secret is created with the `REPLACE_ME` sentinel (caps-with-underscore, trivially greppable in the UI). Sensible **non-secret** defaults are allowed for clearly non-sensitive config (`KB_QUEUE_NAMESPACE=pgboss_<env>`, `KB_ENABLE_RERANK=false`, `*_PUBLIC_BASE_URL=https://replace-me.example.com`). Anything that is an actual secret (keys, passwords, connection strings) → `REPLACE_ME`. The sync script only stubs a value if it is currently empty — it never overwrites a filled value.
|
|
79
|
+
|
|
80
|
+
### Verbose, Meaningful Names
|
|
81
|
+
|
|
82
|
+
The blueprint encourages self-describing variable names with the domain/purpose embedded: `STORAGE_ENDPOINT` → `IMAGE_BUCKET_ENDPOINT`, `RESEND_API_KEY` → `RESEND_EMAIL_API_KEY`, `SYNTHETIC_API_KEY` → `SYNTHETIC_AI_API_KEY`, `PARALLEL_API_KEY` → `PARALLEL_RESEARCH_API_KEY`. The skill offers an optional rename/cleanup step.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Hard Requirements vs Recommended Defaults
|
|
87
|
+
|
|
88
|
+
**Hard requirements:**
|
|
89
|
+
- Infisical CLI + a linked project (`.infisical.json` committed per app/package — holds no secrets).
|
|
90
|
+
- `/shared/<domain>` canonical values; app folders hold env-aware references.
|
|
91
|
+
- Environment-prefixed reference syntax (the slug matches the environment the reference lives in).
|
|
92
|
+
- An idempotent, dry-run-by-default sync script committed to the repo.
|
|
93
|
+
- `infisical run --path --env` wrappers in dev/run scripts.
|
|
94
|
+
|
|
95
|
+
**Recommended defaults — ask during scaffolding:**
|
|
96
|
+
|
|
97
|
+
| Choice | Recommended | Alternatives |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| Environments | `dev`, `staging`, `prod` | project-specific set (use the real Infisical slugs) |
|
|
100
|
+
| Placeholder sentinel | `REPLACE_ME` | per-key sentinel `REPLACE_ME_<KEY>`; key-name-as-value |
|
|
101
|
+
| Sync script language | bash (uses Infisical CLI, zero deps) | TypeScript |
|
|
102
|
+
| Domain map | derived from blueprints/schemas, user confirms | fully manual |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Dependencies
|
|
107
|
+
|
|
108
|
+
### Tooling
|
|
109
|
+
|
|
110
|
+
| Tool | Install | Purpose |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| Infisical CLI | `brew install infisical/get-cli/infisical` | Create folders/secrets, deliver them via `infisical run` |
|
|
113
|
+
|
|
114
|
+
### npm packages
|
|
115
|
+
|
|
116
|
+
None required at runtime — delivery is via the CLI wrapper. `env-t3` already provides the validation layer.
|
|
117
|
+
|
|
118
|
+
### Environment Variables
|
|
119
|
+
|
|
120
|
+
This blueprint creates no new variables of its own. It mirrors the variables already declared in the project's T3 env schemas into Infisical.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Monorepo Wiring
|
|
125
|
+
|
|
126
|
+
- `.infisical.json` committed in each of `apps/*`, `packages/*` that need secrets (workspaceId only — no secrets).
|
|
127
|
+
- `package.json` dev scripts wrapped in `infisical run --path=/apps/<app> --env=dev`:
|
|
128
|
+
```jsonc
|
|
129
|
+
// apps/server
|
|
130
|
+
"dev": "infisical run --path=/apps/server --env=dev -- bun --watch src/index.ts"
|
|
131
|
+
// apps/web
|
|
132
|
+
"dev": "infisical run --path=/apps/web --env=dev -- bun run --bun next dev"
|
|
133
|
+
// packages/db — a :dev variant per script that needs DB envs
|
|
134
|
+
"core:migrate:dev": "infisical run --env=dev --path=/packages/db -- bun run core:migrate"
|
|
135
|
+
```
|
|
136
|
+
- Production scripts stay clean — Railway/the host injects Infisical at the platform level.
|
|
137
|
+
|
|
138
|
+
## Single-Repo Adaptation
|
|
139
|
+
|
|
140
|
+
One app folder (e.g. `/app`) plus `/shared`. Same env-aware references; same placeholder + sync-script approach. `infisical run --path=/app --env=dev`. The `APP_PATHS` and `REF_MAP` in the data map collapse to the single app.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## File Manifest
|
|
145
|
+
|
|
146
|
+
| File | Purpose | Target |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| `files/scripts/infisical-sync.sh` | Idempotent, dry-run-by-default structure sync; loops all environments | `scripts/infisical-sync.sh` |
|
|
149
|
+
| `files/scripts/infisical-map.sh` | Data map: `ENVIRONMENTS`, `DOMAINS`, `APP_PATHS`, `SHARED_MAP`, `REF_MAP` (generated per project) | `scripts/infisical-map.sh` |
|
|
150
|
+
| `files/docs/INFISICAL.md` | Runbook: setup, reference syntax, verification, maintenance | `docs/INFISICAL.md` |
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Integration Steps
|
|
155
|
+
|
|
156
|
+
### Phase 0: Preconditions
|
|
157
|
+
|
|
158
|
+
1. Confirm Infisical CLI installed (`infisical --version`); else `brew install infisical/get-cli/infisical`.
|
|
159
|
+
2. Confirm `infisical login` done.
|
|
160
|
+
3. Confirm an Infisical project exists and `infisical init` produced a committed `.infisical.json` in each app/package dir.
|
|
161
|
+
4. Confirm `env-t3` is installed (read `.project-context.md`). If not, run `/scaffold-env` first.
|
|
162
|
+
5. Capture the real environment **slugs** (default `dev`, `staging`, `prod`) — these go into the reference prefix and MUST match Infisical exactly.
|
|
163
|
+
|
|
164
|
+
### Phase 1: Read Sources of Truth
|
|
165
|
+
|
|
166
|
+
6. Read the T3 env schemas in every app/package → full variable list per app.
|
|
167
|
+
7. Read `.project-context.md` → installed blueprints, app layout, npm scope.
|
|
168
|
+
|
|
169
|
+
### Phase 2: Derive and Confirm the Plan (do not apply yet)
|
|
170
|
+
|
|
171
|
+
8. Classify each var: shared-reference vs app-native.
|
|
172
|
+
9. Group shared vars into domains (table above).
|
|
173
|
+
10. Produce a dry-run plan: folders, shared placeholders, app references, app-native vars left in place.
|
|
174
|
+
11. **Present the domain map + plan and ask the user to confirm/edit.**
|
|
175
|
+
|
|
176
|
+
### Phase 3: Generate the Sync Script + Data Map
|
|
177
|
+
|
|
178
|
+
12. Write `scripts/infisical-sync.sh` and `scripts/infisical-map.sh` from the templates, filling the data map from the confirmed plan.
|
|
179
|
+
|
|
180
|
+
### Phase 4: Apply
|
|
181
|
+
|
|
182
|
+
13. Run dry-run, show output, then (on user OK) re-run with `APPLY=1`. The script loops every environment.
|
|
183
|
+
|
|
184
|
+
### Phase 5: Wire package.json
|
|
185
|
+
|
|
186
|
+
14. Add/adjust `infisical run --path=... --env=...` wrappers in dev/run scripts; add `:dev` variants for `packages/db` scripts. Leave production scripts clean.
|
|
187
|
+
|
|
188
|
+
### Phase 6: Verify (do not skip)
|
|
189
|
+
|
|
190
|
+
15. Resolve a reference end to end for at least `dev`:
|
|
191
|
+
```bash
|
|
192
|
+
infisical run --path=/apps/server --env=dev -- printenv CORE_DATABASE_URL
|
|
193
|
+
```
|
|
194
|
+
Prints a value → good. Prints the literal `${dev.shared...}` → the reference is malformed or the shared key/folder is missing. Fix before continuing.
|
|
195
|
+
|
|
196
|
+
### Phase 7: Record + Summarize
|
|
197
|
+
|
|
198
|
+
16. Update `.project-context.md` and the project-maintenance skill.
|
|
199
|
+
17. Print files created, the full domain map, and the **`REPLACE_ME` checklist** the user must fill in Infisical.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Maintenance Hooks
|
|
204
|
+
|
|
205
|
+
### Secrets Hooks
|
|
206
|
+
|
|
207
|
+
| When you... | Then do... | Why |
|
|
208
|
+
|---|---|---|
|
|
209
|
+
| Add a **shared** env var to a T3 schema | Add the key to `/shared/<domain>` (placeholder) in every env, add an env-aware reference in each consuming app folder, update `infisical-map.sh`, re-run the sync script | Keeps the single source of truth and every consumer in sync |
|
|
210
|
+
| Add an **app-native** var | Set it directly in that app's Infisical folder as a real value — no shared key, no reference | App-native vars don't belong in `/shared` |
|
|
211
|
+
| Add a new environment | Add its slug to `ENVIRONMENTS` and re-run the sync script | Folders + references are per-environment |
|
|
212
|
+
| Rotate a secret | Edit the single `/shared/<domain>/<KEY>` value; all apps inherit it | Never edit a value in an app folder — it should be a reference |
|
|
213
|
+
| Remove a var | Delete the shared key AND every app reference; remove from `infisical-map.sh` | Stale references print the literal `${...}` |
|
|
214
|
+
|
|
215
|
+
### Condensed Rules for project rules file
|
|
216
|
+
|
|
217
|
+
```markdown
|
|
218
|
+
### secrets-infisical maintenance
|
|
219
|
+
- Adding a SHARED env var (used by >1 app, or a domain's canonical value):
|
|
220
|
+
1. Add the key to its `/shared/<domain>` folder in EVERY environment, set to
|
|
221
|
+
`REPLACE_ME` (or a non-secret default).
|
|
222
|
+
2. Add an environment-aware reference `${<env>.shared.<domain>.<KEY>}` in each
|
|
223
|
+
consuming app/package folder, for every environment.
|
|
224
|
+
3. Add the key to scripts/infisical-map.sh (SHARED_MAP + REF_MAP) and re-run
|
|
225
|
+
`APPLY=1 ./scripts/infisical-sync.sh`.
|
|
226
|
+
4. Tell the human which `/shared/<domain>/<KEY>` placeholders to fill.
|
|
227
|
+
- Adding an APP-NATIVE var (PORT, NEXT_PUBLIC_*, single-app config): set it
|
|
228
|
+
directly in that app's Infisical folder as a real value — do NOT create a
|
|
229
|
+
shared key or reference.
|
|
230
|
+
- Reference syntax is ALWAYS `${<env-slug>.shared.<domain>.<KEY>}` where the slug
|
|
231
|
+
matches the environment the reference lives in. The bare `${shared...}` form
|
|
232
|
+
DOES NOT resolve. Verify with:
|
|
233
|
+
`infisical run --path=/apps/<app> --env=dev -- printenv <KEY>`.
|
|
234
|
+
- Rotating a secret: edit the single `/shared/<domain>/<KEY>` value; all
|
|
235
|
+
referencing apps inherit it. Never edit the value in an app folder.
|
|
236
|
+
- Removing a var: delete the shared key AND every app reference to it; remove from
|
|
237
|
+
infisical-map.sh.
|
|
238
|
+
- Never commit real secret values. `.infisical.json` is safe to commit; `.env*`
|
|
239
|
+
stays gitignored.
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## What's Configurable
|
|
245
|
+
|
|
246
|
+
- Environments list (slugs)
|
|
247
|
+
- Domain map (the soft taxonomy)
|
|
248
|
+
- Placeholder sentinel
|
|
249
|
+
- Which apps/packages are wired
|
|
250
|
+
- Sync script language (bash vs TS)
|
|
251
|
+
|
|
252
|
+
## What's Opinionated
|
|
253
|
+
|
|
254
|
+
- **Infisical** as the secret store.
|
|
255
|
+
- **`/shared` + env-aware references** as the single-source-of-truth mechanism.
|
|
256
|
+
- **Environment-prefixed reference syntax** — non-negotiable; it's the only form that resolves.
|
|
257
|
+
- **Placeholder-first, human-fills-values** workflow — the agent never writes real secret values.
|
|
258
|
+
|
|
259
|
+
## Project Context Output
|
|
260
|
+
|
|
261
|
+
Appends to `.project-context.md` under `## Installed Blueprints`:
|
|
262
|
+
|
|
263
|
+
```yaml
|
|
264
|
+
### secrets-infisical
|
|
265
|
+
blueprint: secrets-infisical
|
|
266
|
+
installed_at: <date>
|
|
267
|
+
choices:
|
|
268
|
+
placeholder_sentinel: REPLACE_ME
|
|
269
|
+
script_language: bash
|
|
270
|
+
environments:
|
|
271
|
+
- dev
|
|
272
|
+
- staging
|
|
273
|
+
- prod
|
|
274
|
+
domain_map:
|
|
275
|
+
core: [CORE_DATABASE_URL]
|
|
276
|
+
auth: [AUTH_DATABASE_URL, BETTER_AUTH_URL, BETTER_AUTH_SECRET, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, RESEND_EMAIL_API_KEY]
|
|
277
|
+
# ... one entry per domain
|
|
278
|
+
apps_wired:
|
|
279
|
+
- path: /apps/server
|
|
280
|
+
references: [core, auth, ...]
|
|
281
|
+
- path: /apps/web
|
|
282
|
+
references: [ai]
|
|
283
|
+
- path: /packages/db
|
|
284
|
+
references: [core, auth, pipeline, knowledge-base]
|
|
285
|
+
script_path: scripts/infisical-sync.sh
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## References
|
|
291
|
+
|
|
292
|
+
- **Infisical secret references:** https://infisical.com/docs/documentation/platform/secret-reference
|
|
293
|
+
- **Infisical CLI:** https://infisical.com/docs/cli/overview
|
|
294
|
+
- **`infisical run`:** https://infisical.com/docs/cli/commands/run
|
|
295
|
+
- **`infisical secrets`:** https://infisical.com/docs/cli/commands/secrets
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Infisical Secrets Runbook
|
|
2
|
+
|
|
3
|
+
How secrets work in this project, and how to keep them in sync.
|
|
4
|
+
|
|
5
|
+
## The model
|
|
6
|
+
|
|
7
|
+
- **Canonical values live once** in Infisical under `/shared/<domain>/<KEY>`.
|
|
8
|
+
- **Apps and packages hold references**, never raw values, for anything shared.
|
|
9
|
+
- **App-native values** (`PORT`, `NEXT_PUBLIC_*`, etc.) live directly in the app's
|
|
10
|
+
Infisical folder.
|
|
11
|
+
- Secrets are delivered at launch by the Infisical CLI:
|
|
12
|
+
`infisical run --path=<app> --env=<env> -- <command>`.
|
|
13
|
+
|
|
14
|
+
Change a shared value once and every referencing app inherits it — no drift.
|
|
15
|
+
|
|
16
|
+
## Reference syntax (read this before editing anything)
|
|
17
|
+
|
|
18
|
+
References are **environment-prefixed**. The slug MUST match the environment the
|
|
19
|
+
reference lives in:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
${dev.shared.pipeline.PIPELINE_VERSION} # stored in the dev environment
|
|
23
|
+
${staging.shared.pipeline.PIPELINE_VERSION} # stored in the staging environment
|
|
24
|
+
${prod.shared.pipeline.PIPELINE_VERSION} # stored in the prod environment
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The same key has a different literal value per environment — each points at its
|
|
28
|
+
own environment's `/shared` subtree.
|
|
29
|
+
|
|
30
|
+
**Wrong forms (do not use — they silently fail to resolve):**
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
${shared.pipeline.PIPELINE_VERSION} ← missing env slug (the classic bug)
|
|
34
|
+
${env.shared.pipeline.PIPELINE_VERSION} ← literal "env"
|
|
35
|
+
${/shared/pipeline/PIPELINE_VERSION} ← slash path form
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## First-time setup
|
|
39
|
+
|
|
40
|
+
1. Install the CLI: `brew install infisical/get-cli/infisical`
|
|
41
|
+
2. `infisical login`
|
|
42
|
+
3. Ensure each app/package has a committed `.infisical.json` (from `infisical init`).
|
|
43
|
+
It holds the workspace id only — no secrets — and is safe to commit.
|
|
44
|
+
4. Run the sync script to create the structure (see below).
|
|
45
|
+
5. Open the Infisical dashboard and paste real values over every `REPLACE_ME`
|
|
46
|
+
placeholder in `/shared/<domain>`.
|
|
47
|
+
|
|
48
|
+
## The sync script
|
|
49
|
+
|
|
50
|
+
`scripts/infisical-sync.sh` creates and maintains the `/shared` structure and all
|
|
51
|
+
app references. It is **dry-run by default** and **idempotent** (never overwrites a
|
|
52
|
+
filled value):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# from a dir with .infisical.json (e.g. apps/server)
|
|
56
|
+
APPLY=0 ./scripts/infisical-sync.sh # dry run — prints the plan, changes nothing
|
|
57
|
+
APPLY=1 ./scripts/infisical-sync.sh # apply to every environment in ENVIRONMENTS
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The plan lives in `scripts/infisical-map.sh` (`ENVIRONMENTS`, `DOMAINS`,
|
|
61
|
+
`APP_PATHS`, `SHARED_MAP`, `REF_MAP`). Edit that file to add/remove vars.
|
|
62
|
+
|
|
63
|
+
## Verification
|
|
64
|
+
|
|
65
|
+
After applying, resolve a reference end to end:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
infisical run --path=/apps/server --env=dev -- printenv CORE_DATABASE_URL
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- ✅ prints the value (or `REPLACE_ME`) → the reference resolved correctly.
|
|
72
|
+
- ❌ prints the literal `${dev.shared.core.CORE_DATABASE_URL}` → the reference is
|
|
73
|
+
malformed or the shared key/folder is missing. Fix before proceeding (almost
|
|
74
|
+
always a missing shared key or a bare `${shared...}` reference).
|
|
75
|
+
|
|
76
|
+
## Running apps with secrets
|
|
77
|
+
|
|
78
|
+
```jsonc
|
|
79
|
+
// apps/server
|
|
80
|
+
"dev": "infisical run --path=/apps/server --env=dev -- bun --watch src/index.ts"
|
|
81
|
+
// apps/web
|
|
82
|
+
"dev": "infisical run --path=/apps/web --env=dev -- bun run --bun next dev"
|
|
83
|
+
// packages/db — a :dev variant per script that needs DB envs
|
|
84
|
+
"core:migrate:dev": "infisical run --env=dev --path=/packages/db -- bun run core:migrate"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Production scripts stay clean — the host (e.g. Railway) injects Infisical at the
|
|
88
|
+
platform level.
|
|
89
|
+
|
|
90
|
+
## Maintenance
|
|
91
|
+
|
|
92
|
+
**Add a shared var** (used by >1 app, or a domain's canonical value):
|
|
93
|
+
1. Add `"domain KEY"` to `SHARED_MAP` and `"appPath domain KEY"` to `REF_MAP` in
|
|
94
|
+
`scripts/infisical-map.sh`.
|
|
95
|
+
2. Run `APPLY=1 ./scripts/infisical-sync.sh`.
|
|
96
|
+
3. Paste the real value over the new `/shared/<domain>/<KEY>` placeholder.
|
|
97
|
+
|
|
98
|
+
**Add an app-native var:** set it directly in that app's Infisical folder as a
|
|
99
|
+
real value. Do not add it to the map.
|
|
100
|
+
|
|
101
|
+
**Add an environment:** add its slug to `ENVIRONMENTS` and re-run the sync.
|
|
102
|
+
|
|
103
|
+
**Rotate a secret:** edit the single `/shared/<domain>/<KEY>` value. Every
|
|
104
|
+
referencing app inherits it. Never edit the value in an app folder.
|
|
105
|
+
|
|
106
|
+
**Remove a var:** delete the shared key and every app reference, and remove it
|
|
107
|
+
from `infisical-map.sh`.
|
|
108
|
+
|
|
109
|
+
## Gotchas
|
|
110
|
+
|
|
111
|
+
- **Env slugs must match Infisical exactly.** If your envs are
|
|
112
|
+
Development/Staging/Production with slugs `dev`/`staging`/`prod`, the references
|
|
113
|
+
use `dev`/`staging`/`prod`.
|
|
114
|
+
- **Folders are per-environment.** The tree exists in every environment; the sync
|
|
115
|
+
loops them all.
|
|
116
|
+
- **Local override foot-gun:** a local `.env.local` applied with `override: true`
|
|
117
|
+
beats Infisical. Watch for stale local values masking the real ones.
|
|
118
|
+
- **Never commit real secret values.** `.infisical.json` is safe to commit;
|
|
119
|
+
`.env*` stays gitignored.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Data map for infisical-sync.sh — generated by /scaffold-infisical, edit freely.
|
|
3
|
+
#
|
|
4
|
+
# This file is the per-project plan. The example below is the worked reference
|
|
5
|
+
# shape (a multi-app monorepo); /scaffold-infisical regenerates it from YOUR
|
|
6
|
+
# project's T3 env schemas + installed blueprints. Treat it as a template, not a
|
|
7
|
+
# hardcode.
|
|
8
|
+
#
|
|
9
|
+
# Four arrays drive the sync:
|
|
10
|
+
# ENVIRONMENTS — the env slugs (MUST match Infisical exactly)
|
|
11
|
+
# DOMAINS — the /shared subfolders
|
|
12
|
+
# APP_PATHS — app/package folders that hold references
|
|
13
|
+
# SHARED_MAP — "domain KEY [non-secret default]" (omit default -> REPLACE_ME)
|
|
14
|
+
# REF_MAP — "appPath domain KEY" (the env-aware reference is generated)
|
|
15
|
+
|
|
16
|
+
ENVIRONMENTS=(dev staging prod)
|
|
17
|
+
|
|
18
|
+
DOMAINS=(core auth pipeline knowledge-base image-storage ai search infra)
|
|
19
|
+
|
|
20
|
+
APP_PATHS=(/apps/server /apps/web /packages/db)
|
|
21
|
+
|
|
22
|
+
# "domain KEY [non-secret default]" — omit default for real secrets (-> REPLACE_ME)
|
|
23
|
+
SHARED_MAP=(
|
|
24
|
+
"core CORE_DATABASE_URL"
|
|
25
|
+
"auth AUTH_DATABASE_URL"
|
|
26
|
+
"auth BETTER_AUTH_URL"
|
|
27
|
+
"auth BETTER_AUTH_SECRET"
|
|
28
|
+
"auth GOOGLE_CLIENT_ID"
|
|
29
|
+
"auth GOOGLE_CLIENT_SECRET"
|
|
30
|
+
"auth RESEND_EMAIL_API_KEY"
|
|
31
|
+
"pipeline PIPELINE_DATABASE_URL"
|
|
32
|
+
"pipeline PIPELINE_VERSION"
|
|
33
|
+
"knowledge-base KB_DATABASE_URL"
|
|
34
|
+
"knowledge-base KB_BUCKET_ENDPOINT"
|
|
35
|
+
"knowledge-base KB_BUCKET_NAME"
|
|
36
|
+
"knowledge-base KB_BUCKET_ACCESS_KEY_ID"
|
|
37
|
+
"knowledge-base KB_BUCKET_SECRET_ACCESS_KEY"
|
|
38
|
+
"knowledge-base KB_BUCKET_REGION"
|
|
39
|
+
"knowledge-base KB_QUEUE_NAMESPACE pgboss_dev"
|
|
40
|
+
"knowledge-base KB_ENABLE_RERANK false"
|
|
41
|
+
"image-storage IMAGE_BUCKET_ENDPOINT"
|
|
42
|
+
"image-storage IMAGE_BUCKET_NAME"
|
|
43
|
+
"image-storage IMAGE_BUCKET_ACCESS_KEY_ID"
|
|
44
|
+
"image-storage IMAGE_BUCKET_SECRET_ACCESS_KEY"
|
|
45
|
+
"image-storage IMAGE_BUCKET_PUBLIC_BASE_URL https://replace-me.example.com"
|
|
46
|
+
"ai OPENROUTER_API_KEY"
|
|
47
|
+
"ai OPENROUTER_DEFAULT_MODEL"
|
|
48
|
+
"ai SYNTHETIC_AI_API_KEY"
|
|
49
|
+
"ai MISTRAL_OCR_API_KEY"
|
|
50
|
+
"ai FIREWORKS_AI_API_KEY"
|
|
51
|
+
"ai JINA_PAGE_EXTRACTOR_API_KEY"
|
|
52
|
+
"search PARALLEL_RESEARCH_API_KEY"
|
|
53
|
+
"search TAVILY_API_KEY"
|
|
54
|
+
"search EXA_API_KEY"
|
|
55
|
+
"search PEXELS_API_KEY"
|
|
56
|
+
"search UNSPLASH_API_KEY"
|
|
57
|
+
"search BRAVE_SEARCH_API_KEY"
|
|
58
|
+
"search SERPER_API_KEY"
|
|
59
|
+
"infra REDIS_URL"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# "appPath domain KEY" — the env-aware reference is generated automatically
|
|
63
|
+
REF_MAP=(
|
|
64
|
+
"/apps/server core CORE_DATABASE_URL"
|
|
65
|
+
"/apps/server auth AUTH_DATABASE_URL"
|
|
66
|
+
"/apps/server auth BETTER_AUTH_URL"
|
|
67
|
+
"/apps/server auth BETTER_AUTH_SECRET"
|
|
68
|
+
"/apps/server auth GOOGLE_CLIENT_ID"
|
|
69
|
+
"/apps/server auth GOOGLE_CLIENT_SECRET"
|
|
70
|
+
"/apps/server auth RESEND_EMAIL_API_KEY"
|
|
71
|
+
"/apps/server pipeline PIPELINE_DATABASE_URL"
|
|
72
|
+
"/apps/server pipeline PIPELINE_VERSION"
|
|
73
|
+
"/apps/server knowledge-base KB_DATABASE_URL"
|
|
74
|
+
"/apps/server knowledge-base KB_BUCKET_ENDPOINT"
|
|
75
|
+
"/apps/server knowledge-base KB_BUCKET_NAME"
|
|
76
|
+
"/apps/server knowledge-base KB_BUCKET_ACCESS_KEY_ID"
|
|
77
|
+
"/apps/server knowledge-base KB_BUCKET_SECRET_ACCESS_KEY"
|
|
78
|
+
"/apps/server knowledge-base KB_BUCKET_REGION"
|
|
79
|
+
"/apps/server knowledge-base KB_QUEUE_NAMESPACE"
|
|
80
|
+
"/apps/server knowledge-base KB_ENABLE_RERANK"
|
|
81
|
+
"/apps/server image-storage IMAGE_BUCKET_ENDPOINT"
|
|
82
|
+
"/apps/server image-storage IMAGE_BUCKET_NAME"
|
|
83
|
+
"/apps/server image-storage IMAGE_BUCKET_ACCESS_KEY_ID"
|
|
84
|
+
"/apps/server image-storage IMAGE_BUCKET_SECRET_ACCESS_KEY"
|
|
85
|
+
"/apps/server image-storage IMAGE_BUCKET_PUBLIC_BASE_URL"
|
|
86
|
+
"/apps/server ai OPENROUTER_API_KEY"
|
|
87
|
+
"/apps/server ai OPENROUTER_DEFAULT_MODEL"
|
|
88
|
+
"/apps/server ai SYNTHETIC_AI_API_KEY"
|
|
89
|
+
"/apps/server ai MISTRAL_OCR_API_KEY"
|
|
90
|
+
"/apps/server ai FIREWORKS_AI_API_KEY"
|
|
91
|
+
"/apps/server ai JINA_PAGE_EXTRACTOR_API_KEY"
|
|
92
|
+
"/apps/server search PARALLEL_RESEARCH_API_KEY"
|
|
93
|
+
"/apps/server search TAVILY_API_KEY"
|
|
94
|
+
"/apps/server search EXA_API_KEY"
|
|
95
|
+
"/apps/server search PEXELS_API_KEY"
|
|
96
|
+
"/apps/server search UNSPLASH_API_KEY"
|
|
97
|
+
"/apps/server search BRAVE_SEARCH_API_KEY"
|
|
98
|
+
"/apps/server search SERPER_API_KEY"
|
|
99
|
+
"/apps/server infra REDIS_URL"
|
|
100
|
+
"/apps/web ai SYNTHETIC_AI_API_KEY"
|
|
101
|
+
"/packages/db core CORE_DATABASE_URL"
|
|
102
|
+
"/packages/db auth AUTH_DATABASE_URL"
|
|
103
|
+
"/packages/db pipeline PIPELINE_DATABASE_URL"
|
|
104
|
+
"/packages/db knowledge-base KB_DATABASE_URL"
|
|
105
|
+
"/packages/db knowledge-base KB_BUCKET_ENDPOINT"
|
|
106
|
+
"/packages/db knowledge-base KB_BUCKET_NAME"
|
|
107
|
+
"/packages/db knowledge-base KB_BUCKET_ACCESS_KEY_ID"
|
|
108
|
+
"/packages/db knowledge-base KB_BUCKET_SECRET_ACCESS_KEY"
|
|
109
|
+
"/packages/db knowledge-base KB_BUCKET_REGION"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# NOTE on app-native vars: deliberately NOT listed here. PORT, FRONTEND_DOMAIN,
|
|
113
|
+
# NEXT_PUBLIC_*, langfuse keys, etc. are set directly as real values in their app
|
|
114
|
+
# folder (by the user), never referenced. The skill prints them as
|
|
115
|
+
# "app-native — set directly."
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# infisical-sync.sh — create/maintain a domain-grouped /shared secret structure
|
|
4
|
+
# in Infisical, with each app/package folder holding ENVIRONMENT-AWARE references.
|
|
5
|
+
#
|
|
6
|
+
# SAFE BY DEFAULT: prints the plan, changes nothing unless APPLY=1.
|
|
7
|
+
# Idempotent. Never prints secret values. Never overwrites existing values.
|
|
8
|
+
# Runs across every environment in ENVIRONMENTS.
|
|
9
|
+
#
|
|
10
|
+
# APPLY=0 ./scripts/infisical-sync.sh # dry run (default)
|
|
11
|
+
# APPLY=1 ./scripts/infisical-sync.sh # apply to all environments
|
|
12
|
+
#
|
|
13
|
+
# Requires: `infisical login`, a committed .infisical.json in the cwd (run from a
|
|
14
|
+
# dir that has one, e.g. apps/server), and scripts/infisical-map.sh alongside.
|
|
15
|
+
#
|
|
16
|
+
# REFERENCE SYNTAX (the only form that resolves):
|
|
17
|
+
# ${<env-slug>.shared.<domain>.<KEY>} e.g. ${dev.shared.core.CORE_DATABASE_URL}
|
|
18
|
+
# The slug MUST match the environment the reference lives in.
|
|
19
|
+
|
|
20
|
+
set -uo pipefail
|
|
21
|
+
APPLY="${APPLY:-0}"
|
|
22
|
+
PLACEHOLDER="${PLACEHOLDER:-REPLACE_ME}"
|
|
23
|
+
|
|
24
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
25
|
+
# shellcheck source=/dev/null
|
|
26
|
+
source "$HERE/infisical-map.sh" # defines: ENVIRONMENTS, DOMAINS, APP_PATHS,
|
|
27
|
+
# SHARED_MAP, REF_MAP. See infisical-map.sh.
|
|
28
|
+
|
|
29
|
+
plan() { printf ' %s\n' "$*"; }
|
|
30
|
+
banner() { printf '\n=== %s ===\n' "$*"; }
|
|
31
|
+
|
|
32
|
+
# REF <domain> <KEY> -> ${<ENV>.shared.<domain>.<KEY>} (ENV is the current env slug)
|
|
33
|
+
REF() { printf '${%s.shared.%s.%s}' "$ENV" "$1" "$2"; }
|
|
34
|
+
|
|
35
|
+
mkfolder() { # parentPath name
|
|
36
|
+
plan "folder $1/$2"
|
|
37
|
+
[ "$APPLY" = 1 ] && infisical secrets folders create \
|
|
38
|
+
--env "$ENV" --path "$1" --name "$2" --silent >/dev/null 2>&1 || true
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# stub <domain> <KEY> [defaultValue]
|
|
42
|
+
# Sets /shared/<domain>/<KEY> to a placeholder ONLY if it has no value yet.
|
|
43
|
+
stub() {
|
|
44
|
+
local d="$1" k="$2" v="${3:-$PLACEHOLDER}" cur
|
|
45
|
+
cur="$(infisical secrets get "$k" --env "$ENV" --path "/shared/$d" --plain --silent 2>/dev/null)" || cur=""
|
|
46
|
+
if [ -n "$cur" ]; then plan "keep /shared/$d/$k (already set)"; return; fi
|
|
47
|
+
plan "stub /shared/$d/$k = $v"
|
|
48
|
+
[ "$APPLY" = 1 ] && infisical secrets set "$k=$v" --env "$ENV" --path "/shared/$d" --silent >/dev/null 2>&1 || true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# link <appPath> <domain> <KEY> -> sets KEY=${ENV.shared.domain.KEY} in the app folder
|
|
52
|
+
link() {
|
|
53
|
+
local app="$1" d="$2" k="$3"
|
|
54
|
+
plan "ref ${app}/${k} = $(REF "$d" "$k")"
|
|
55
|
+
[ "$APPLY" = 1 ] && infisical secrets set "${k}=$(REF "$d" "$k")" \
|
|
56
|
+
--env "$ENV" --path "$app" --silent >/dev/null 2>&1 || true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
printf 'Infisical sync — APPLY=%s%s\n' "$APPLY" \
|
|
60
|
+
"$([ "$APPLY" = 1 ] && echo ' (LIVE)' || echo ' (dry run — no changes)')"
|
|
61
|
+
|
|
62
|
+
for ENV in "${ENVIRONMENTS[@]}"; do
|
|
63
|
+
banner "ENVIRONMENT: $ENV"
|
|
64
|
+
|
|
65
|
+
banner "1. Folders ($ENV)"
|
|
66
|
+
mkfolder / shared
|
|
67
|
+
for d in "${DOMAINS[@]}"; do mkfolder /shared "$d"; done
|
|
68
|
+
for app in "${APP_PATHS[@]}"; do
|
|
69
|
+
# ensure app/package folders exist (apps/server, apps/web, packages/db, ...)
|
|
70
|
+
parent="$(dirname "$app")"; name="$(basename "$app")"
|
|
71
|
+
[ "$parent" = "/" ] || mkfolder "$parent" "$name"
|
|
72
|
+
done
|
|
73
|
+
|
|
74
|
+
banner "2. Shared placeholders ($ENV)"
|
|
75
|
+
# SHARED_MAP entries look like "domain KEY [optionalDefault]"
|
|
76
|
+
for entry in "${SHARED_MAP[@]}"; do
|
|
77
|
+
# shellcheck disable=SC2086
|
|
78
|
+
stub $entry
|
|
79
|
+
done
|
|
80
|
+
|
|
81
|
+
banner "3. App references ($ENV)"
|
|
82
|
+
# REF_MAP entries look like "appPath domain KEY"
|
|
83
|
+
for entry in "${REF_MAP[@]}"; do
|
|
84
|
+
# shellcheck disable=SC2086
|
|
85
|
+
link $entry
|
|
86
|
+
done
|
|
87
|
+
done
|
|
88
|
+
|
|
89
|
+
printf '\nDone (%s).\n' "$([ "$APPLY" = 1 ] && echo applied || echo 'dry run — re-run with APPLY=1')"
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import pc4 from "picocolors";
|
|
|
6
6
|
|
|
7
7
|
// src/constants.ts
|
|
8
8
|
var API_URL = process.env["WITHMATA_API_URL"] || "https://blueprints.withmata.dev";
|
|
9
|
-
var VERSION = "0.
|
|
9
|
+
var VERSION = "3.0.0";
|
|
10
10
|
|
|
11
11
|
// src/lib/install-state.ts
|
|
12
12
|
import { existsSync, readdirSync, lstatSync, readlinkSync } from "fs";
|
|
@@ -269,7 +269,7 @@ function isBlueprintsRepo(dir) {
|
|
|
269
269
|
function discoverBlueprints(blueprintsDir) {
|
|
270
270
|
const ids = [];
|
|
271
271
|
if (!existsSync3(blueprintsDir)) return ids;
|
|
272
|
-
const tiers = ["discovery", "foundation", "features"];
|
|
272
|
+
const tiers = ["discovery", "foundation", "features", "thinking"];
|
|
273
273
|
for (const tier of tiers) {
|
|
274
274
|
const tierDir = join3(blueprintsDir, tier);
|
|
275
275
|
if (!existsSync3(tierDir)) continue;
|
|
@@ -483,6 +483,15 @@ var SKILL_GROUPS = [
|
|
|
483
483
|
description: "Check project health and find available upgrades"
|
|
484
484
|
}
|
|
485
485
|
]
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
title: "Think",
|
|
489
|
+
skills: [
|
|
490
|
+
{
|
|
491
|
+
command: "/reflect",
|
|
492
|
+
description: "Run a structured thinking-partner session on any personal topic"
|
|
493
|
+
}
|
|
494
|
+
]
|
|
486
495
|
}
|
|
487
496
|
];
|
|
488
497
|
function showSummary({
|
package/package.json
CHANGED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# =============================================================================
|
|
2
|
-
# {{APP_NAME}} — Environment Variables
|
|
3
|
-
# =============================================================================
|
|
4
|
-
# Copy this file to .env.local and fill in the values.
|
|
5
|
-
# Feature blueprints will add their own variables — check their BLUEPRINT.md.
|
|
6
|
-
|
|
7
|
-
# -----------------------------------------------------------------------------
|
|
8
|
-
# App
|
|
9
|
-
# -----------------------------------------------------------------------------
|
|
10
|
-
|
|
11
|
-
# Frontend URL (used for redirects, CORS, and metadata)
|
|
12
|
-
# Local: http://localhost:3000 | Production: https://yourdomain.com
|
|
13
|
-
NEXT_PUBLIC_FRONTEND_URL=http://localhost:3000
|
|
14
|
-
|
|
15
|
-
# CONFIGURE: Feature blueprints add variables here:
|
|
16
|
-
# - /scaffold-auth adds: BETTER_AUTH_URL, BETTER_AUTH_SECRET, GOOGLE_CLIENT_ID, etc.
|
|
17
|
-
# - /scaffold-db adds: DATABASE_URL (in packages/db/.env for monorepos)
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# =============================================================================
|
|
2
|
-
# {{APP_NAME}} Server — Environment Variables
|
|
3
|
-
# =============================================================================
|
|
4
|
-
# Copy this file to .env.local and fill in the values.
|
|
5
|
-
|
|
6
|
-
# -----------------------------------------------------------------------------
|
|
7
|
-
# Server Configuration
|
|
8
|
-
# -----------------------------------------------------------------------------
|
|
9
|
-
|
|
10
|
-
# Port the server listens on
|
|
11
|
-
# Local: 4000 | Production: Set by hosting platform
|
|
12
|
-
PORT=4000
|
|
13
|
-
|
|
14
|
-
# CONFIGURE: Add server-specific variables below
|