@oaklandzoo/ostup 0.1.2 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oaklandzoo/ostup",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "description": "Scaffolds a new repo with the Ostup Agent Kit pre-installed: slash commands, doc templates, and a clean working state.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/mvp-flow.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  // mvp-flow.mjs: orchestrate the full MVP scaffold: preflight, prompts, creds, scaffold, GitHub, Vercel, inject, summary.
2
2
  import { existsSync } from 'node:fs';
3
- import { readdir, mkdir, writeFile, readFile, rm } from 'node:fs/promises';
3
+ import { readdir, mkdir, writeFile, readFile, rm, chmod } from 'node:fs/promises';
4
+ import { homedir } from 'node:os';
4
5
  import { resolve, join, dirname } from 'node:path';
5
6
  import { fileURLToPath } from 'node:url';
6
7
  import { preflightOrExit } from './preflight.mjs';
@@ -56,6 +57,7 @@ export async function runMvp({ flags = {}, cwd = process.cwd() } = {}) {
56
57
  const targetDir = resolve(cwd, answers.projectName);
57
58
  await ensureFreshTarget(targetDir, flags.force);
58
59
  await mkdir(targetDir, { recursive: true });
60
+ await ensureMemoryDir(targetDir);
59
61
 
60
62
  await maybeScaffoldStack({ stack: answers.stack, projectName: answers.projectName, targetDir });
61
63
 
@@ -179,6 +181,16 @@ async function writeOne({ entry, targetDir, tokens, defer }) {
179
181
  const out = substitute(raw, tokens, { defer });
180
182
  await mkdir(dirname(dest), { recursive: true });
181
183
  await writeFile(dest, out, 'utf8');
184
+ if (entry.executable) {
185
+ await chmod(dest, 0o755);
186
+ }
187
+ }
188
+
189
+ export async function ensureMemoryDir(targetDir) {
190
+ const sanitized = targetDir.replace(/[/ ]/g, '-');
191
+ const memoryDir = join(homedir(), '.claude', 'projects', sanitized, 'memory');
192
+ await mkdir(memoryDir, { recursive: true });
193
+ return memoryDir;
182
194
  }
183
195
 
184
196
  async function writeProjectEnvFiles({ targetDir, collected }) {
package/src/scaffold.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import { existsSync } from 'node:fs';
2
- import { readFile, writeFile, mkdir, readdir } from 'node:fs/promises';
2
+ import { readFile, writeFile, mkdir, readdir, chmod } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
3
4
  import { dirname, resolve, join } from 'node:path';
4
5
  import { fileURLToPath } from 'node:url';
5
6
  import { execSync } from 'node:child_process';
@@ -38,6 +39,7 @@ export async function scaffold({ targetDir, flags, stdinIsTTY = process.stdin.is
38
39
  const tokens = buildTokenMap(values);
39
40
 
40
41
  await mkdir(absTarget, { recursive: true });
42
+ await ensureMemoryDir(absTarget);
41
43
 
42
44
  for (const entry of REGISTRY) {
43
45
  await writeOne({ entry, absTarget, tokens });
@@ -60,6 +62,15 @@ async function writeOne({ entry, absTarget, tokens }) {
60
62
  const out = substitute(raw, tokens);
61
63
  await mkdir(dirname(dest), { recursive: true });
62
64
  await writeFile(dest, out, 'utf8');
65
+ if (entry.executable) {
66
+ await chmod(dest, 0o755);
67
+ }
68
+ }
69
+
70
+ async function ensureMemoryDir(absTarget) {
71
+ const sanitized = absTarget.replace(/[/ ]/g, '-');
72
+ const memoryDir = join(homedir(), '.claude', 'projects', sanitized, 'memory');
73
+ await mkdir(memoryDir, { recursive: true });
63
74
  }
64
75
 
65
76
  async function ensureTargetReady(absTarget, force) {
package/src/templates.mjs CHANGED
@@ -21,6 +21,13 @@ export const REGISTRY = [
21
21
  { src: '.claude/commands/prompt-end.md', dest: '.claude/commands/prompt-end.md' },
22
22
  { src: '.claude/commands/create-prd.md', dest: '.claude/commands/create-prd.md' },
23
23
  { src: '.claude/commands/generate-tasks.md', dest: '.claude/commands/generate-tasks.md' },
24
+ { src: '.claude/commands/preflight.md', dest: '.claude/commands/preflight.md' },
25
+ { src: '.claude/commands/update-image.md', dest: '.claude/commands/update-image.md' },
26
+ { src: '.claude/commands/update-gui.md', dest: '.claude/commands/update-gui.md' },
27
+ { src: '.claude/commands/update-backend.md', dest: '.claude/commands/update-backend.md' },
28
+ { src: '.claude/commands/add-storage.md', dest: '.claude/commands/add-storage.md' },
29
+ { src: '.claude/commands/generate-image-prompt.md', dest: '.claude/commands/generate-image-prompt.md' },
30
+ { src: '.claude/commands/generate-image.md', dest: '.claude/commands/generate-image.md' },
24
31
  { src: 'CLAUDE.md', dest: 'CLAUDE.md' },
25
32
  { src: 'AGENTS.md', dest: 'AGENTS.md' },
26
33
  { src: 'START_HERE.md', dest: 'START_HERE.md' },
@@ -31,6 +38,7 @@ export const REGISTRY = [
31
38
  { src: 'docs/ARCHITECTURE.md', dest: 'docs/ARCHITECTURE.md' },
32
39
  { src: 'tasks/.gitkeep', dest: 'tasks/.gitkeep' },
33
40
  { src: 'inputs/README.md', dest: 'inputs/README.md' },
41
+ { src: 'scripts/screenshot.sh', dest: 'scripts/screenshot.sh', executable: true },
34
42
  ];
35
43
 
36
44
  export const OPTIONAL_REGISTRY = [
@@ -0,0 +1,112 @@
1
+ ---
2
+ description: Provision a Vercel storage product (Blob, KV, Postgres, Edge Config), pull its env vars, and scaffold a typed lib helper.
3
+ ---
4
+
5
+ # Add storage
6
+
7
+ ## Step 1: which type
8
+
9
+ If the operator named a type (e.g. `/add-storage blob`), use it. Otherwise ask:
10
+
11
+ | Type | What it is | When to pick |
12
+ |---|---|---|
13
+ | `blob` | File storage with public URLs | Image uploads, downloads, generated PDFs |
14
+ | `kv` | Redis-style key/value | Sessions, caches, counters, rate limits |
15
+ | `postgres` | Relational SQL | Users, transactions, anything with joins |
16
+ | `edge-config` | Read-heavy small config (<8 KB) | Feature flags, A/B variants, allowlists |
17
+
18
+ ## Step 2: check what already exists
19
+
20
+ ```bash
21
+ vercel <type> list-stores --all 2>&1 | head -10
22
+ ```
23
+
24
+ If a suitable store already exists, ask the operator if they want to reuse it instead of creating a new one. Skip to Step 4 if reusing.
25
+
26
+ ## Step 3: provision a new store
27
+
28
+ Ask for a store name. Default to `<project-name>-<type>`. Then run:
29
+
30
+ ```bash
31
+ vercel <type> create-store <name> \
32
+ --environment production --environment preview --environment development --yes
33
+ ```
34
+
35
+ For `blob`, also include `--access public` if the stored objects should be served via public URLs:
36
+
37
+ ```bash
38
+ vercel blob create-store <name> --access public --yes \
39
+ --environment production --environment preview --environment development
40
+ ```
41
+
42
+ Capture the output (store ID, etc.) and surface it.
43
+
44
+ ## Step 4: pull env vars
45
+
46
+ ```bash
47
+ vercel env pull .env.local
48
+ ```
49
+
50
+ Confirm `.env.local` got the new keys. Common ones:
51
+
52
+ | Type | Keys |
53
+ |---|---|
54
+ | blob | `BLOB_READ_WRITE_TOKEN` |
55
+ | kv | `KV_URL`, `KV_REST_API_URL`, `KV_REST_API_TOKEN`, `KV_REST_API_READ_ONLY_TOKEN` |
56
+ | postgres | `POSTGRES_URL`, `POSTGRES_PRISMA_URL`, `POSTGRES_URL_NON_POOLING`, `POSTGRES_USER`, etc. |
57
+ | edge-config | `EDGE_CONFIG` |
58
+
59
+ ## Step 5: install the SDK and scaffold the lib helper
60
+
61
+ ```bash
62
+ npm install @vercel/<type>
63
+ ```
64
+
65
+ Create `src/lib/<type>.ts` with typed helpers. Example for blob:
66
+
67
+ ```typescript
68
+ // src/lib/blob.ts
69
+ import { put, list, del } from '@vercel/blob';
70
+
71
+ export async function uploadFile(path: string, body: Blob | ArrayBuffer | string) {
72
+ return put(path, body, { access: 'public' });
73
+ }
74
+
75
+ export async function listFiles(prefix?: string) {
76
+ return list({ prefix });
77
+ }
78
+
79
+ export async function deleteFile(url: string) {
80
+ return del(url);
81
+ }
82
+ ```
83
+
84
+ Match the project's existing TypeScript conventions (named exports, no defaults, etc.).
85
+
86
+ ## Step 6: update AGENTS.md
87
+
88
+ Append under a `## Storage` section:
89
+
90
+ ```markdown
91
+ ## Storage
92
+
93
+ - `<type>`: `src/lib/<type>.ts`. Store: `<name>`. Env vars in `.env.local`.
94
+ ```
95
+
96
+ ## Step 7: commit and push
97
+
98
+ ```bash
99
+ git add src/lib/<type>.ts AGENTS.md package.json package-lock.json
100
+ git commit -m "feat(storage): add <type> via src/lib/<type>.ts"
101
+ git push
102
+ ```
103
+
104
+ ## Step 8: report
105
+
106
+ ```
107
+ Added <type> storage:
108
+ - Store: <name>
109
+ - Lib: src/lib/<type>.ts
110
+ - Env: <list of keys>
111
+ - Deploy: <url>
112
+ ```
@@ -22,6 +22,9 @@ find . -maxdepth 2 -type d ! -path '*/node_modules*' ! -path '*/.git*' ! -path '
22
22
  [ -f inputs/INGEST_MANIFEST.md ] && cat inputs/INGEST_MANIFEST.md
23
23
  [ -f inputs/README.md ] && cat inputs/README.md
24
24
  [ -d inputs ] && ls -la inputs/
25
+ # Helpers available in this kit
26
+ [ -x scripts/screenshot.sh ] && echo "scripts/screenshot.sh ready (required for visual verification per CLAUDE.md Part 19)"
27
+ ls .claude/commands/ 2>/dev/null
25
28
  ```
26
29
 
27
30
  ## Step 2: Determine what you know
@@ -0,0 +1,118 @@
1
+ ---
2
+ description: Compose an image-generation prompt from the project brief plus 2-3 clarifying questions. Outputs a copy-pasteable prompt the operator pastes into DALL-E, Midjourney, Imagen, or any image tool. No API call, no key required.
3
+ ---
4
+
5
+ # Generate image prompt (composer only)
6
+
7
+ Use this when you need an image asset the project does not have yet. Composer only: produces a prompt for the operator to paste elsewhere. When the resulting image is dropped into `inputs/images/`, run `/update-image` to promote it per CLAUDE.md Part 19.
8
+
9
+ ## Step 1: identify the asset type
10
+
11
+ If the operator named a type (e.g. `/generate-image-prompt hero`), use it. Otherwise ask.
12
+
13
+ Asset types:
14
+
15
+ | Type | Dimensions | Notes |
16
+ |---|---|---|
17
+ | `hero` | 1920x1080 | Focal point off-center to leave room for overlaid headline |
18
+ | `background` | 2400x3000 or 1920x2400 | Full-bleed, subtle or busy is operator's call |
19
+ | `og-image` | 1200x630 | Bold, brand-forward, high contrast for social previews |
20
+ | `favicon` | 512x512 source | Mark only, no scene, must read at 16x16 |
21
+ | `infographic` | operator-defined | Clean type, flat or isometric, tight palette |
22
+ | `brand-scene` | 1920x1080 | Lifestyle or product photography style |
23
+ | `card` | 1200x900 or 1080x1080 | Focal image with headline headroom |
24
+ | `logo` | 1024x1024 | Mark only, transparent background preferred |
25
+
26
+ ## Step 2: read the project context
27
+
28
+ ```bash
29
+ [ -f docs/branding/ostup-brand-brief.md ] && cat docs/branding/ostup-brand-brief.md 2>/dev/null
30
+ [ -f inputs/INGEST_MANIFEST.md ] && cat inputs/INGEST_MANIFEST.md 2>/dev/null
31
+ [ -f inputs/README.md ] && cat inputs/README.md 2>/dev/null
32
+ ls inputs/images/ 2>/dev/null
33
+ grep -A 5 "Brand\|palette\|Visual identity\|tone" CLAUDE.md AGENTS.md 2>/dev/null | head -40
34
+ [ -f docs/brief.md ] && cat docs/brief.md 2>/dev/null
35
+ ```
36
+
37
+ Absorb: brand voice, color palette, mood, any existing visual style references, anything the brief specifies.
38
+
39
+ ## Step 3: ask at most 3 clarifying questions
40
+
41
+ Tailor per asset type. Defaults below.
42
+
43
+ For `hero`:
44
+ - What is the subject in one phrase?
45
+ - Mood: bold / quiet / playful / serious?
46
+ - Any image already in `inputs/images/` to riff on?
47
+
48
+ For `og-image`:
49
+ - What headline word or short phrase should dominate?
50
+ - Same palette as the rest of the site or a contrast variant?
51
+
52
+ For `favicon`:
53
+ - Use the existing logo mark, or a simplified abstraction of it?
54
+
55
+ For `background`:
56
+ - Specific subject or abstract texture?
57
+ - Light or dark dominant?
58
+
59
+ For others: subject, palette, mood unless the brief makes it obvious.
60
+
61
+ ## Step 4: compose the prompt
62
+
63
+ Use this exact output shape. Fill every field. Honor the brand palette from the brief.
64
+
65
+ ```
66
+ PROMPT
67
+ ------
68
+ <one paragraph, vivid, specific. Cover: subject, composition, lighting, color
69
+ palette (echo the brief), style references like "editorial photography" or
70
+ "isometric flat illustration" or "product hero shot". End with concrete
71
+ detail anchors.>
72
+
73
+ MODEL RECOMMENDATION
74
+ --------------------
75
+ <pick one with a one-line reason:
76
+ - DALL-E 3: photorealism, reliable text-following
77
+ - Midjourney v6: strongest art direction, painterly
78
+ - Imagen: best typography
79
+ - Stable Diffusion via Replicate: fastest iteration, lowest cost>
80
+
81
+ SIZE / ASPECT RATIO
82
+ -------------------
83
+ <exact dims for the asset type from Step 1>
84
+
85
+ NEGATIVE PROMPT (if applicable)
86
+ -------------------------------
87
+ <one line: things to exclude. e.g. "no text, no people, no logos in frame, no watermarks">
88
+
89
+ STYLE NOTES
90
+ -----------
91
+ <one line: e.g. "seed=2024 for reproducibility, subject off-center to leave
92
+ right third clear for overlaid headline">
93
+ ```
94
+
95
+ ## Step 5: report
96
+
97
+ ```
98
+ Composed prompt for <asset type>.
99
+
100
+ Paste the PROMPT block above into your chosen image tool. When the image is
101
+ back, save it to inputs/images/ with a clear filename (e.g.
102
+ inputs/images/hero-v1.png).
103
+
104
+ Then run /update-image <slot> to promote it into the project and verify
105
+ visually per CLAUDE.md Part 19.
106
+
107
+ To skip the paste-into-another-tool step, use /generate-image instead. That
108
+ calls Vercel AI Gateway directly and saves the result to inputs/images/
109
+ (requires VERCEL_AI_GATEWAY_KEY in env).
110
+ ```
111
+
112
+ ## Hard rules
113
+
114
+ - Always honor the brand palette from the brief. Do not invent colors.
115
+ - Always specify exact dimensions.
116
+ - Never claim done. This command only composes a prompt.
117
+ - If the brief and operator answers conflict, surface the conflict and ask. Do not silently choose one.
118
+ - The PROMPT block must be copy-pasteable as-is; do not wrap it in commentary.
@@ -0,0 +1,168 @@
1
+ ---
2
+ description: Compose an image-generation prompt AND call Vercel AI Gateway to actually generate the image. Saves to inputs/images/ and appends to MANIFEST.md. Requires VERCEL_AI_GATEWAY_KEY. Per CLAUDE.md Part 19, use /update-image to promote into the project.
3
+ ---
4
+
5
+ # Generate image (composer plus API call)
6
+
7
+ Same flow as `/generate-image-prompt`, but calls Vercel AI Gateway and saves the result. Operator skips the paste-into-another-tool step.
8
+
9
+ ## Step 1: preflight
10
+
11
+ Check the key exists:
12
+
13
+ ```bash
14
+ if [ -n "$VERCEL_AI_GATEWAY_KEY" ]; then
15
+ echo "VERCEL_AI_GATEWAY_KEY: present"
16
+ else
17
+ echo "VERCEL_AI_GATEWAY_KEY: MISSING"
18
+ echo "Get a key: https://vercel.com/dashboard/ai-gateway"
19
+ echo "Set it in .env.local (export VERCEL_AI_GATEWAY_KEY=...) and rerun."
20
+ echo "Aborting."
21
+ fi
22
+ ```
23
+
24
+ If the key is missing, stop and ask the operator to set it. Do not call the API without a key.
25
+
26
+ ## Step 2: identify the asset type, read context, ask clarifying questions
27
+
28
+ Run Steps 1-3 of `/generate-image-prompt`. Compose the prompt in memory (or print it first for operator review if scope is significant).
29
+
30
+ ## Step 3: surface cost estimate before calling
31
+
32
+ Print exactly:
33
+
34
+ ```
35
+ About to call Vercel AI Gateway:
36
+ Model: openai/dall-e-3
37
+ Size: 1024x1024
38
+ Quality: standard
39
+ Estimated cost: ~$0.04 (DALL-E 3 standard 1024x1024)
40
+
41
+ Proceed? (y/n)
42
+ ```
43
+
44
+ Wait for operator confirmation. If they answer no, stop. If they answer yes (or the agent was invoked with `--yes` semantics from /update-image), proceed.
45
+
46
+ Cost reference (verify against current Vercel AI Gateway pricing before claiming numbers):
47
+
48
+ | Model | Size | Cost per image |
49
+ |---|---|---:|
50
+ | openai/dall-e-3 | 1024x1024 standard | ~$0.04 |
51
+ | openai/dall-e-3 | 1024x1024 hd | ~$0.08 |
52
+ | openai/dall-e-3 | 1792x1024 standard | ~$0.08 |
53
+ | openai/dall-e-3 | 1792x1024 hd | ~$0.12 |
54
+ | stability-ai/stable-diffusion-xl | 1024x1024 | ~$0.003 |
55
+
56
+ If size or model differs from defaults, recompute and surface.
57
+
58
+ ## Step 4: call the API
59
+
60
+ Use bash and curl. OpenAI-compatible endpoint via Vercel AI Gateway.
61
+
62
+ ```bash
63
+ TIMESTAMP=$(date +%s)
64
+ TYPE="<asset-type>"
65
+ OUT="inputs/images/${TYPE}-${TIMESTAMP}.png"
66
+ mkdir -p inputs/images
67
+
68
+ # Use a heredoc to safely encode the prompt
69
+ PROMPT_JSON=$(cat <<'JSON_EOF'
70
+ {
71
+ "model": "openai/dall-e-3",
72
+ "prompt": "<COMPOSED_PROMPT_GOES_HERE>",
73
+ "size": "1024x1024",
74
+ "quality": "standard",
75
+ "n": 1,
76
+ "response_format": "b64_json"
77
+ }
78
+ JSON_EOF
79
+ )
80
+
81
+ curl -s https://ai-gateway.vercel.sh/v1/images/generations \
82
+ -H "Authorization: Bearer $VERCEL_AI_GATEWAY_KEY" \
83
+ -H "Content-Type: application/json" \
84
+ -d "$PROMPT_JSON" > /tmp/ostup-img-response.json
85
+
86
+ # Verify success
87
+ if ! grep -q "b64_json" /tmp/ostup-img-response.json 2>/dev/null; then
88
+ echo "API call failed. Response:"
89
+ cat /tmp/ostup-img-response.json
90
+ exit 1
91
+ fi
92
+
93
+ # Extract base64 and write the PNG
94
+ python3 -c "
95
+ import json, base64, sys
96
+ with open('/tmp/ostup-img-response.json') as f:
97
+ d = json.load(f)
98
+ b64 = d['data'][0]['b64_json']
99
+ with open('$OUT', 'wb') as f:
100
+ f.write(base64.b64decode(b64))
101
+ print('Saved:', '$OUT')
102
+ "
103
+
104
+ rm -f /tmp/ostup-img-response.json
105
+ ```
106
+
107
+ If the model returns a URL instead of base64 (some configurations), fall back to:
108
+
109
+ ```bash
110
+ URL=$(python3 -c "import json; print(json.load(open('/tmp/ostup-img-response.json'))['data'][0]['url'])")
111
+ curl -sL -o "$OUT" "$URL"
112
+ ```
113
+
114
+ ## Step 5: append to manifest
115
+
116
+ ```bash
117
+ MANIFEST="inputs/images/MANIFEST.md"
118
+
119
+ # Initialize if missing
120
+ if [ ! -f "$MANIFEST" ]; then
121
+ echo "# Image manifest" > "$MANIFEST"
122
+ echo "" >> "$MANIFEST"
123
+ echo "Generated images, prompts used, models, and timestamps." >> "$MANIFEST"
124
+ echo "" >> "$MANIFEST"
125
+ fi
126
+
127
+ cat >> "$MANIFEST" <<EOF
128
+
129
+ ## ${TIMESTAMP} — <asset-type>
130
+
131
+ - **File:** \`${OUT}\`
132
+ - **Model:** openai/dall-e-3
133
+ - **Size:** 1024x1024
134
+ - **Quality:** standard
135
+ - **Cost:** ~\$0.04
136
+ - **Prompt:**
137
+ > <COMPOSED_PROMPT_SUMMARY_ONE_LINE>
138
+
139
+ EOF
140
+ ```
141
+
142
+ ## Step 6: report and offer to promote
143
+
144
+ ```
145
+ Generated <asset type>: inputs/images/<asset-type>-<timestamp>.png
146
+ Manifest updated: inputs/images/MANIFEST.md
147
+
148
+ Want to promote it into the project now? Running /update-image <slot> will:
149
+ 1. Copy this image into public/<slot>/
150
+ 2. Update any code references
151
+ 3. Commit + push
152
+ 4. Wait for Vercel deploy
153
+ 5. Run scripts/screenshot.sh and read the PNG to verify visually
154
+
155
+ Proceed with /update-image? (y/n)
156
+ ```
157
+
158
+ If operator agrees, run the full `/update-image` routine. Otherwise stop.
159
+
160
+ ## Hard rules
161
+
162
+ - Generated images land in `inputs/images/` ONLY. Never directly in `public/`. The operator (or `/update-image`) promotes.
163
+ - Every generation appends a manifest line with prompt + model + size + timestamp + cost.
164
+ - Cost is always surfaced before the API call. No surprise charges.
165
+ - Missing `VERCEL_AI_GATEWAY_KEY` fails fast with a clear pointer to the dashboard URL.
166
+ - Brand palette from the brief must be in the prompt. No "free interpretation."
167
+ - This command does not promote the image. `/update-image` handles promotion + Part 19 visual verification.
168
+ - If the API call fails (rate limit, content policy, network), report the full error and do NOT retry silently.
@@ -0,0 +1,65 @@
1
+ ---
2
+ description: Surface what is already provisioned for this project (Vercel auth, blob stores, gh auth, env vars, Chrome). Prevents the agent from assigning CLI work it could do itself.
3
+ ---
4
+
5
+ # Preflight
6
+
7
+ Run at the start of any session where you might touch Vercel, gh, the GUI, or storage. The output tells you what already exists so you do not duplicate setup or assign the operator CLI work that is already done.
8
+
9
+ ## Step 1: probe everything in one block
10
+
11
+ ```bash
12
+ echo "=== Vercel ==="
13
+ vercel whoami 2>&1 | head -3
14
+ if [ -f .vercel/project.json ]; then
15
+ echo "Linked:"
16
+ cat .vercel/project.json
17
+ else
18
+ echo "Not linked (no .vercel/project.json)"
19
+ fi
20
+ echo ""
21
+ echo "Blob stores:"
22
+ vercel blob list-stores --all 2>&1 | head -10
23
+ echo ""
24
+ echo "Env vars:"
25
+ vercel env list 2>&1 | head -15
26
+
27
+ echo ""
28
+ echo "=== GitHub ==="
29
+ gh auth status 2>&1 | head -5
30
+ git remote -v 2>&1 | head -2
31
+
32
+ echo ""
33
+ echo "=== Local tools ==="
34
+ node --version
35
+ [ -d "/Applications/Google Chrome.app" ] && echo "Chrome: present" || echo "Chrome: MISSING (operator must install for visual verification per CLAUDE.md Part 19)"
36
+ [ -x "scripts/screenshot.sh" ] && echo "scripts/screenshot.sh: ready" || echo "scripts/screenshot.sh: MISSING or not executable"
37
+
38
+ echo ""
39
+ echo "=== AI Gateway (for /generate-image) ==="
40
+ if [ -n "$VERCEL_AI_GATEWAY_KEY" ]; then
41
+ echo "VERCEL_AI_GATEWAY_KEY: present (image generation enabled)"
42
+ else
43
+ echo "VERCEL_AI_GATEWAY_KEY: not set (/generate-image will fail until set; get one at https://vercel.com/dashboard/ai-gateway; /generate-image-prompt still works as composer-only)"
44
+ fi
45
+ ```
46
+
47
+ ## Step 2: print the SESSION READY summary
48
+
49
+ After reading the output, print exactly:
50
+
51
+ ```
52
+ SESSION READY
53
+
54
+ Vercel: <authed as X | linked to project Y | N blob stores | M env vars>
55
+ GitHub: <authed | remote URL>
56
+ Chrome: <present | MISSING>
57
+ Screenshot: <ready | missing>
58
+ Node: <version>
59
+
60
+ Anything missing above must be flagged before claiming any work that depends on it.
61
+ ```
62
+
63
+ ## Step 3: hard rule
64
+
65
+ Do NOT propose CLI steps the operator should run for anything Vercel or gh already covers per the SESSION READY line above. CLAUDE.md Part 3 Rule 9 is the constraint: agent does what the agent can do.
@@ -0,0 +1,74 @@
1
+ ---
2
+ description: Make a backend logic change (API route, lib function, server action), deploy, and verify the new behavior via API call before claiming done.
3
+ ---
4
+
5
+ # Update backend
6
+
7
+ Non-visual analog to `/update-gui`. Verify behavior, not just compile.
8
+
9
+ ## Step 1: confirm intent
10
+
11
+ Restate the change in one sentence. Get a thumbs-up before editing if scope is non-trivial.
12
+
13
+ ## Step 2: edit
14
+
15
+ Make the change. Minimal diff.
16
+
17
+ ## Step 3: write or run a test
18
+
19
+ If a test framework is configured, run the test suite:
20
+
21
+ ```bash
22
+ npm test 2>&1 | tail -10
23
+ ```
24
+
25
+ If no tests exist for the touched code, write one that exercises the new behavior (preferred), or capture an expected response shape for Step 6.
26
+
27
+ If no test framework exists at all, skip to Step 4 and rely on the curl probe in Step 6.
28
+
29
+ ## Step 4: build
30
+
31
+ ```bash
32
+ npm run build 2>&1 | tail -10
33
+ ```
34
+
35
+ If the build fails, fix before deploying.
36
+
37
+ ## Step 5: deploy
38
+
39
+ ```bash
40
+ git add <changed-files>
41
+ git commit -m "<short message>"
42
+ git push
43
+ ```
44
+
45
+ Wait ~60 seconds for Vercel auto-deploy.
46
+
47
+ ## Step 6: probe the live endpoint
48
+
49
+ For an API route change:
50
+
51
+ ```bash
52
+ curl -s -i "<deploy-url>/api/<route>" | head -20
53
+ ```
54
+
55
+ For a route that takes a body:
56
+
57
+ ```bash
58
+ curl -s -X POST "<deploy-url>/api/<route>" \
59
+ -H "Content-Type: application/json" \
60
+ -d '{"key":"value"}' | head -20
61
+ ```
62
+
63
+ Compare the response to the expected shape from Step 3.
64
+
65
+ ## Step 7: report
66
+
67
+ ```
68
+ Backend change shipped: <one-line description>
69
+ Deploy: <url>
70
+ Verified via: <test name or curl command>
71
+ Response: <status, key field, whatever proves it works>
72
+ ```
73
+
74
+ If verification fails, do NOT claim done. Diagnose, fix, re-deploy, re-probe.
@@ -0,0 +1,55 @@
1
+ ---
2
+ description: Make a UI change, deploy it, and verify visually before claiming done. Enforces CLAUDE.md Part 19.
3
+ ---
4
+
5
+ # Update GUI
6
+
7
+ This command exists so that CLAUDE.md Part 19 (visual verification) is the default for any UI change, not an afterthought.
8
+
9
+ ## Step 1: confirm intent
10
+
11
+ The operator described the change. Restate it in one sentence and get a thumbs-up before editing if the scope is non-trivial. Skip the thumbs-up for tiny changes (typo, single color, single class).
12
+
13
+ ## Step 2: edit
14
+
15
+ Make the change. Keep the diff minimal. No unrelated cleanup in the same commit.
16
+
17
+ ## Step 3: build locally
18
+
19
+ ```bash
20
+ npm run build 2>&1 | tail -10
21
+ ```
22
+
23
+ If the build fails, fix before deploying.
24
+
25
+ ## Step 4: deploy
26
+
27
+ ```bash
28
+ git add <changed-files>
29
+ git commit -m "<short message>"
30
+ git push
31
+ ```
32
+
33
+ Wait ~60 seconds for Vercel auto-deploy.
34
+
35
+ ## Step 5: screenshot
36
+
37
+ ```bash
38
+ scripts/screenshot.sh <deploy-url> /tmp/gui-check.png 420,900
39
+ ```
40
+
41
+ ## Step 6: read the PNG
42
+
43
+ Use the Read tool on `/tmp/gui-check.png`. Confirm the visual change matches what the operator asked for. If it does not, diagnose (CSS regression, z-index, cache, stale file path) and re-deploy. Do NOT claim done.
44
+
45
+ ## Step 7: report
46
+
47
+ ```
48
+ GUI change shipped: <one-line description>
49
+ Deploy: <url>
50
+ Screenshot: /tmp/gui-check.png (read, change confirmed)
51
+ ```
52
+
53
+ ## Hard rule
54
+
55
+ HTTP 200, build success, lint pass, byte counts: none of these count as visual verification. Only a read PNG counts.
@@ -0,0 +1,57 @@
1
+ ---
2
+ description: Swap an image referenced in the codebase (background, hero, logo) using a candidate from inputs/images/. Always ends with screenshot verification per CLAUDE.md Part 19.
3
+ ---
4
+
5
+ # Update an image
6
+
7
+ ## Step 1: figure out the slot
8
+
9
+ If the operator named a slot (e.g. `/update-image bg-week`), use that.
10
+
11
+ If not, list every image reference in the codebase:
12
+
13
+ ```bash
14
+ grep -rln -E "/(bg|hero|logo|images?)/[a-zA-Z0-9_-]+\.(png|jpg|jpeg|svg|webp|avif)" src/ public/ 2>/dev/null
15
+ ```
16
+
17
+ Show the operator the slots and ask which to update.
18
+
19
+ ## Step 2: pick the source
20
+
21
+ List candidates in `inputs/images/`:
22
+
23
+ ```bash
24
+ ls -la inputs/images/ 2>/dev/null
25
+ ```
26
+
27
+ If there is an `inputs/images/MANIFEST.md`, read it for filenames and captions.
28
+
29
+ If multiple candidates and the choice is not obvious, ask the operator. Do NOT Read more than 3 image files at a time — each Read returns a vision payload that is heavy in context.
30
+
31
+ ## Step 3: swap it in
32
+
33
+ 1. Copy the chosen file from `inputs/images/` to the right path under `public/`.
34
+ 2. Update any code references if the filename changed.
35
+ 3. `git add <changed files>` (the new image plus any code).
36
+ 4. `git commit -m "feat(images): swap <slot>"`
37
+ 5. `git push` (Vercel auto-deploys).
38
+
39
+ ## Step 4: verify visually (REQUIRED per CLAUDE.md Part 19)
40
+
41
+ Wait ~60 seconds for Vercel to deploy. Then:
42
+
43
+ ```bash
44
+ scripts/screenshot.sh <deploy-url> /tmp/after-swap.png 420,900
45
+ ```
46
+
47
+ Read the resulting PNG with the Read tool. Confirm the new image is showing in the slot.
48
+
49
+ ## Step 5: report
50
+
51
+ ```
52
+ Swapped <slot>: <old-filename> -> <new-filename>
53
+ Deploy: <url>
54
+ Screenshot: /tmp/after-swap.png (read and verified)
55
+ ```
56
+
57
+ If the screenshot does not show the change, do NOT claim done. Diagnose (CSS z-index, wrong path, wrong file) and re-deploy before reporting.
@@ -27,7 +27,12 @@ Operator materials live in `{{INPUTS_PATH}}`. Read `{{INPUTS_PATH}}README.md` fo
27
27
 
28
28
  ## Slash commands available
29
29
 
30
- - `/bootstrap`, `/prompt-start`, `/prompt-mid`, `/prompt-end`
31
- - `/create-prd`, `/generate-tasks`
30
+ Session lifecycle: `/bootstrap`, `/prompt-start`, `/prompt-mid`, `/prompt-end`, `/preflight`
32
31
 
33
- See each file under `.claude/commands/` for details.
32
+ Building: `/create-prd`, `/generate-tasks`, `/update-image`, `/update-gui`, `/update-backend`, `/add-storage`, `/generate-image-prompt`, `/generate-image`
33
+
34
+ See each file under `.claude/commands/` for the full routine.
35
+
36
+ ## Helpers
37
+
38
+ - `scripts/screenshot.sh <url> [out] [WxH]` — headless Chrome screenshot. Required for visual verification per `CLAUDE.md` Part 19.
@@ -254,3 +254,16 @@ Detailed file map: `docs/ARCHITECTURE.md`.
254
254
  | What happened recently | `docs/SESSION_NOTES.md` |
255
255
  | Stack and file map | `docs/ARCHITECTURE.md` |
256
256
  | How to behave | `CLAUDE.md` (this file) |
257
+
258
+ ---
259
+
260
+ # PART 19: VISUAL VERIFICATION
261
+
262
+ Any change that affects what the operator sees in a browser (HTML, CSS, Tailwind classes, image swaps, layout, copy in JSX, fonts, colors, hero sections, anything rendered) is NOT done until BOTH of the following are true:
263
+
264
+ 1. `scripts/screenshot.sh <deployed-url>` has been run after the change is deployed.
265
+ 2. The resulting PNG has been read with the Read tool and the visual change is confirmed to match the intent.
266
+
267
+ HTTP 200, build success, lint passing, byte counts of the deployed HTML: none of these count as visual verification.
268
+
269
+ If `scripts/screenshot.sh` is missing or Chrome is not installed, surface the gap before claiming the change is done. Do not assume.
@@ -47,11 +47,18 @@ Type these in your CLI agent. Each runs a structured routine.
47
47
  | Command | What it does | When to run it |
48
48
  |---|---|---|
49
49
  | `/bootstrap` | Scans the repo and asks up to 5 clarifiers, then fills in `CLAUDE.md` Parts 13 to 17 and `docs/ARCHITECTURE.md` with your project's real facts. | Once, on the first session. |
50
+ | `/preflight` | Surfaces what is already provisioned (Vercel auth, blob stores, gh auth, env vars, Chrome). Stops the agent from assigning CLI work it could do itself. | Start of any session where you might touch Vercel, gh, the GUI, or storage. |
50
51
  | `/prompt-start` | Reads HANDOFF, MANUAL_TASKS, git status. Briefs the agent on the current state. Asks "Proceed or pivot?" | Start of every session after the first. |
51
52
  | `/prompt-mid` | Checks context budget, restates progress, verifies any files the agent claimed to write actually exist. | Mid-session, if you feel lost or the agent seems off-track. |
52
53
  | `/prompt-end` | Rewrites HANDOFF, appends to SESSION_NOTES, surfaces any new blockers to MANUAL_TASKS. | End of every session. Always. Without this the next session has no memory of what just happened. |
53
54
  | `/create-prd` | Asks 3 to 5 clarifying questions, then writes a PRD to `tasks/prd-<feature>.md`. | Before building a significant new feature. Optional for small changes. |
54
55
  | `/generate-tasks` | Reads a PRD, breaks it into a phased task list at `tasks/tasks-<feature>.md` (parent tasks first, then sub-tasks after you say "Go"). | After a PRD is approved, before the agent starts coding. |
56
+ | `/update-image` | Swap an image (background, hero, logo) using a candidate from `inputs/images/`. Ends with screenshot verification. | Any time you want to change a visual asset. |
57
+ | `/update-gui` | Make a UI change, deploy, screenshot, confirm the visual change matches intent. Enforces visual verification. | Any UI tweak. |
58
+ | `/update-backend` | Make a backend/API change, deploy, probe the live endpoint, confirm behavior. | Any server/API change. |
59
+ | `/add-storage` | Provision a Vercel Blob, KV, Postgres, or Edge Config store + scaffold a typed `src/lib/<type>.ts` helper + pull env vars. | When you need persistent storage. |
60
+ | `/generate-image-prompt` | Compose an image-generation prompt from the project brief + 2-3 questions. No API call. Paste the prompt into your preferred image tool. | When you need a visual asset and want to use Midjourney, DALL-E, Imagen, etc. directly. |
61
+ | `/generate-image` | Compose the prompt AND call Vercel AI Gateway, saving the image to `inputs/images/`. Requires `VERCEL_AI_GATEWAY_KEY`. | When you want the image generated in-line without leaving your agent. |
55
62
 
56
63
  ## Three workflows
57
64
 
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env bash
2
+ # Headless Chrome screenshot helper. Use to visually verify a UI change.
3
+ # Required by CLAUDE.md Part 19 (Visual Verification).
4
+ #
5
+ # Usage: scripts/screenshot.sh <url> [output-path] [width,height]
6
+ # Example: scripts/screenshot.sh https://your-app.vercel.app /tmp/check.png 420,900
7
+
8
+ set -e
9
+
10
+ if [ -z "$1" ]; then
11
+ echo "Usage: $0 <url> [output-path] [width,height]" >&2
12
+ echo "Example: $0 https://your-app.vercel.app /tmp/check.png 420,900" >&2
13
+ exit 1
14
+ fi
15
+
16
+ URL="$1"
17
+ OUT="${2:-/tmp/screenshot-$(date +%s).png}"
18
+ SIZE="${3:-420,900}"
19
+
20
+ CHROME="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
21
+ if [ ! -x "$CHROME" ]; then
22
+ CHROME="$(command -v chromium 2>/dev/null || command -v google-chrome 2>/dev/null || echo '')"
23
+ fi
24
+
25
+ if [ -z "$CHROME" ] || [ ! -x "$CHROME" ]; then
26
+ echo "ERROR: Chrome not found. Install Google Chrome from https://www.google.com/chrome/" >&2
27
+ exit 1
28
+ fi
29
+
30
+ "$CHROME" \
31
+ --headless=new --disable-gpu --hide-scrollbars \
32
+ --window-size="$SIZE" --virtual-time-budget=4000 \
33
+ --screenshot="$OUT" "$URL" 2>/dev/null
34
+
35
+ echo "$OUT"