Social media caption and reply helper for local businesses
TypeScript AI agent drafts weekly per-platform captions, posting schedules, and simple comment replies for owner approval on one screen.
typescriptnextjszoddrizzlesqlitevitest
Built 1 time, downloaded 0 times.
Install from your terminal
Run this in your project folder. It signs you in, shows what changes and asks before it overwrites a file.
npx claude-lab add social-media-caption-and-reply-helper-for-local-businesses-ba7b19CLAUDE.md# the app
A TypeScript agent for local businesses. The owner uploads 5 photos a week, and the agent drafts captions per platform, proposes a posting schedule, and drafts replies to simple comments. The owner reviews everything on one approval screen. Flow: weekly photo upload, then captions per platform, then schedule, then draft comment replies, then a single approval screen. Nothing goes out until the owner approves it.
## Stack
- TypeScript in strict mode.
- Next.js (App Router) for the approval UI and API routes.
- Zod for validating all model output.
- Vitest for tests.
- SQLite through Drizzle for storage.
- If SPEC.md names a different database or hosting choice, follow SPEC.md and update this section.
## Commands
- `npm run dev`: start the local app.
- `npm test`: run Vitest.
- `npm run typecheck`: TypeScript check, no emit.
- `npm run lint`: ESLint.
- `npm run build`: production build.
- `npm run eval`: caption fixture checks against the sample businesses.
- `npx tsx scripts/smoke.ts`: run the agent on fixtures and print drafts.
## Folder map
- `src/agent/`: prompts, tools, and orchestration. Comment logic lives in `src/agent/comments/`.
- `src/platforms/`: one adapter per platform behind a common interface. Adapters are stubbed first. Each platform's length limits and tone rules sit in `src/platforms/<name>.ts` as constants.
- `src/app/`: approval UI and API routes.
- `src/db/`: Drizzle schema and SQLite file.
- `tests/fixtures/`: sample photos and fictional business profiles. No real data.
- `scripts/`: smoke run and helper scripts.
## Hard rules
- Nothing is posted or replied to automatically. Every caption, schedule slot, and reply is a draft until the owner approves it. Any code path that publishes must check that the status is `approved`.
- Comment replies are drafted only for simple factual questions (hours, address, phone, menu basics), and only from the business profile. Complaints, refunds, prices not in the profile, health, and legal topics are flagged `needs_owner`. They are never drafted.
- All model output is parsed with a Zod schema. On failure, retry once, then show an error. Never pass raw model text onward.
- Secrets come from environment variables (`ANTHROPIC_API_KEY`, platform tokens) loaded from `.env.local`. Never commit secrets. Docs use `YOUR_API_KEY` as the placeholder.
- Platform limits and tone rules belong in `src/platforms/<name>.ts`, not in prompts.
## Gotchas
- Strip EXIF location data from every uploaded photo before storing or sending it to the model.
- Cap uploads at 5 photos per week per business.
- Store all times in UTC. Save the business timezone separately and convert for display and scheduling.
- Schedule suggestions must not collide with existing slots. Check the business's likely open hours.
- The platform adapters are stubs in MVP. The stub logs to the console and does not call any network API.
## Working style
- Explore, then plan, then code, then verify. Use plan mode before Phase 2 and Phase 5.
- Run the `verify` skill before saying a change is done. If any step was skipped, say which one and why.
- Use `/clear` between unrelated tasks and `/compact` at natural breaks.
- Keep changes inside this project folder. Do not add dependencies without asking.
- For long output, delegate to the `test-runner` subagent.
- Phase work is tracked in PLAN.md. Each phase ends with a `/goal` check.
## Review
@REVIEW.md
.claude/agents/test-runner.md---
name: test-runner
description: Runs typecheck, lint and tests and reports only failures concisely
model: haiku
tools: Bash, Read, Grep, Glob
maxTurns: 10
---
You run the project's checks and report results. You never edit files.
Run these commands from the project root, in this order:
1. `npm run typecheck`
2. `npm run lint`
3. `npm test`
4. `npm run build`
5. `npm run eval`, only if `package.json` defines an `eval` script
Run each command once. Do not retry unless the failure is clearly a network or install error, and say so if you do.
Report in this format:
- One line per command: `PASS` or `FAIL`, followed by the command name.
- For each FAIL, the first 20 lines of its output, copied as-is inside a code block.
- Nothing else. No summaries of passing output, no suggested fixes, no code changes.
If a command is missing from `package.json`, report it as `SKIPPED (no script)`.
Never edit, create or delete files. Never read `.env` or `.env.*` files.
.claude/settings.json{
"permissions": {
"allow": [
"Bash(npm run dev)",
"Bash(npm run test)",
"Bash(npm run typecheck)",
"Bash(npm run lint)",
"Bash(npm run build)",
"Bash(npm run eval)",
"Bash(npx vitest:*)",
"Bash(git status:*)",
"Bash(git diff:*)",
"Bash(git log:*)"
],
"ask": [
"Bash(npm install:*)",
"Bash(git commit:*)",
"Bash(git push:*)",
"Bash(curl:*)",
"Bash(wget:*)"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Bash(rm -rf:*)",
"Bash(git push --force:*)",
"Bash(git reset --hard:*)"
],
"blockWritesOutsideWorkingDirectories": true
},
"env": {
"CLAUDE_CODE_SUBAGENT_MODEL": "haiku"
}
}
.claude/skills/caption-style/SKILL.md---
name: caption-style
description: Rules for writing and testing captions and caption prompts for each social platform.
paths:
- "src/agent/**"
- "src/platforms/**"
---
# Caption style
Use this skill when changing caption prompts, platform adapters or caption tests.
Length limits and tone constants live in `src/platforms/<name>.ts`, not in prompts. Read the limit from there. Never hard-code a limit in a prompt.
## Per platform
- **Instagram:** hook line first, short body, 3 to 8 hashtags, emoji sparingly.
- **Facebook:** conversational, 1 to 3 short paragraphs, minimal hashtags.
- **Google Business Profile:** TODO. Add rules when the adapter is built.
- **TikTok:** TODO. Add rules when the adapter is built.
## Voice
- Match the profile `tone` field. If it is missing, ask the owner. Do not guess.
- Use only facts in the business profile: name, type, hours, address, platforms and listed services.
- No invented prices, offers, dates or events.
- Mention the local area only if the profile's address or area field names it.
- No discounts, guarantees, or health or medical claims.
- If a photo or request needs one of those, do not write it. Return `needsOwner` with a short reason.
## Prompt structure
Build every caption request in this order:
1. System prompt: role, target platform, and the rules in this skill.
2. Profile JSON: the validated BusinessProfile.
3. Photo: image input. EXIF must already be stripped.
4. Required output: JSON that matches the Zod caption schema in `src/agent`.
Parse the reply with the Zod schema. On failure, retry once, then surface a plain-language error. Never pass raw model text onward.
## Add a fixture and eval case
1. Add a fictional profile to `tests/fixtures/businesses/<name>.json`. No real data.
2. Add a photo to `tests/fixtures/photos/`. Strip EXIF before committing.
3. Add an expected entry to the eval cases in `tests/fixtures/`: platform, length limit, required facts, and banned phrases.
4. Run `npm run eval`.
5. Fix the prompt or the platform constants. Change the expectation only if it is wrong.
## Before calling a caption done
- Length is within the platform limit, and a test covers it.
- Every fact appears in the profile.
- No discount, guarantee or claim wording.
.claude/skills/comment-triage/SKILL.md---
name: comment-triage
description: Rules for classifying comments by intent and drafting replies only when the business profile has the answer.
paths:
- "src/agent/comments/**"
---
# Comment triage
Replies are drafts. Nothing is posted until the owner approves it.
## Intent categories
- **hours:** opening times, closing times, holiday hours.
- **address:** location, directions, parking.
- **contact:** phone, email, website, booking link.
- **menu_or_services:** what the business sells or offers, if listed in the profile.
- **thanks:** praise or thanks with no question.
- **other:** safe but not covered above.
- **sensitive:** any trigger below.
## Sensitive triggers (always win)
Sensitive overrides every other category, even when the comment also asks an easy question.
- Complaints, bad experiences, wrong orders.
- Refunds, money back, chargebacks.
- Allergies, health, injury, food safety.
- Legal matters: lawyer, lawsuit, police.
- Harassment, threats, slurs.
- Pricing that is not in the profile.
## Reply rules
- Draft a reply only if the answer is in the business profile. Otherwise set status to `needs_owner` and leave the draft empty.
- Validate classifier output with the Zod schema. On failure, retry once, then surface an error. Never send raw model text.
- Style: short, friendly, one or two sentences, no promises.
- No draft for sensitive comments, not even a holding line.
## Test table
Keep cases in `tests/fixtures/` as a table. Each row is one Vitest case. Expected results assume the cafe fixture: hours Monday to Saturday, an address, a phone number, no vegan items, no pricing, and no holiday hours.
| Comment | Intent | Draft |
|---|---|---|
| What time do you open on Saturday? | hours | yes |
| Are you open on Sundays? | hours | yes |
| Where are you located? | address | yes |
| Do you have parking? | address | no (needs_owner) |
| What's your phone number? | contact | yes |
| Can I book a table by phone? | contact | yes |
| Do you do vegan options? | menu_or_services | no (needs_owner) |
| Love the latte art! | thanks | yes |
| Nice post! 🙂 | thanks | yes |
| Are you open late on Sunday because my order was wrong | sensitive | no |
| I want my money back for the broken chair | sensitive | no |
| My son had a reaction to your almond croissant | sensitive | no |
| Is the cake safe for nut allergies? | sensitive | no |
| Do you charge $50 for a cut? | sensitive | no |
| This place is a rip-off, shame on you | sensitive | no |
| I'm going to call my lawyer about this | sensitive | no |
| What are your hours on Christmas Eve? | hours | no (needs_owner) |
| Your staff was rude to me yesterday | sensitive | no |
| Do you ship across the country? | other | no (needs_owner) |
| Can you do a discount for students? | sensitive | no |
.claude/skills/verify/SKILL.md---
name: verify
description: Run typecheck, lint, tests and build, then a smoke run of the agent on fixtures
when_to_use: After any code change, before reporting a task as done.
---
# Verify
Proves a change works for this stack. Run the steps in order and stop at the first failure.
## Steps
1. `npm run typecheck`
2. `npm run lint`
3. `npm test`
4. `npm run build`
5. `npm run eval` (caption fixture checks; exists from Phase 2 onward)
6. `npx tsx scripts/smoke.ts`
## Smoke run
- Feeds `tests/fixtures/photos` and a sample profile from `tests/fixtures/businesses` through the agent.
- Prints the drafts.
- Asserts that none of them is published. Exit non-zero if any item reaches the platform adapter without `approved` status.
## Report format
```
typecheck: pass
lint: pass
test: fail
first error: <first 20 lines only>
```
- Give pass or fail for each step.
- Show the first failing error only.
- After a fix, rerun the whole sequence from step 1 so earlier steps stay green.
## Rules
- Never say done if any step was skipped. Name each skipped step and say why.
- The eval step is only skipped before Phase 2. Say so in the report.
- If output is long, delegate to the test-runner subagent and use its summary.
INSTALL.md# Install and first run
This guide sets up the the app project step by step.
## What you need
- Node.js 20 or newer.
- Git.
- Claude Code, version 2.1.296 or newer.
- An Anthropic API key for the app itself. This is separate from your Claude Code login.
## Words used in this guide
- **Agent:** the part of the app that asks Claude to write captions and drafts.
- **Adapter:** a small piece of code for one platform, such as Instagram or Facebook.
- **Fixture:** a sample input, such as a fake business or a sample photo, used for tests.
- **Eval:** a check that runs the agent on fixtures and confirms the output is good.
## Step 1: Create the project folder
Open a terminal and run:
```
mkdir the-app
cd the-app
git init
```
## Step 2: Copy the setup files
Copy these into the folder:
- CLAUDE.md
- SPEC.md
- PLAN.md
- REVIEW.md
- INSTALL.md
- The `.claude` folder
## Step 3: Check the Claude Code version
Run:
```
claude --version
```
If the number is lower than 2.1.296, update Claude Code first.
## Step 4: Start Claude Code
From the project folder, run:
```
claude
```
Only run `/init` if CLAUDE.md is missing. This project already includes one.
## Step 5: Install the plugins
Type these commands inside Claude Code, one at a time:
- `/plugin install feature-dev@claude-plugins-official` adds a guided explore, plan and build flow.
- `/plugin install code-review@claude-plugins-official` reviews your changes against REVIEW.md.
- `/plugin install commit-commands@claude-plugins-official` makes clean commits quickly.
- `/plugin install security-guidance@claude-plugins-official` flags risky patterns around tokens, uploads and comment text.
## Step 6: Create the app's API key file
Create a file named `.env.local` in the project folder with this line:
```
ANTHROPIC_API_KEY=YOUR_API_KEY
```
Replace `YOUR_API_KEY` with your real key. Never commit this file. Check that `.gitignore` lists `.env.local` before your first commit.
Never paste a real key into CLAUDE.md, SPEC.md, PLAN.md, a fixture or a chat with Claude.
## Step 7: Fill in the sample business
Open SPEC.md and fill in the TODO items for your first sample business. Use fictional details if you are testing. Save the file.
## Step 8: Start Phase 1
Paste the kickoff prompt from PLAN.md into Claude Code. It begins with `/goal`. Claude will work until the check passes or the turn limit is reached.
## What `/goal` does
`/goal` gives Claude a target and keeps working until a small model judges that the target is met. The judge only reads what Claude has shown in the chat, so the check command output must appear there.
To stop a goal early, type:
```
/goal clear
```
## Step 9: Finish each phase
When a phase ends:
1. Run `/code-review`.
2. Fix anything it flags as a blocker.
3. Commit with the commit command from the plugin.
4. Type `/clear`.
5. Paste the next phase's `/goal` prompt from PLAN.md.
Use plan mode before Phase 2 and Phase 5.
## Token-saving habits
- Type `/clear` between unrelated tasks and between phases.
- Type `/compact` at natural breaks, such as after a finished feature.
- Type `/usage` to see where your tokens went.
## Troubleshooting
**Tests are failing.** Ask Claude to run the verify skill. It shows the first failing error. Ask for a fix to that error only, then rerun.
**The goal stops early.** Read the last message. Usually a check failed or a turn limit was reached. Type `/goal clear`, then start again with a smaller goal.
**Permission prompts keep appearing.** Read each prompt before you approve it. Commands listed as allowed in `.claude/settings.json` run without asking. Installs, commits, pushes and network calls always ask first.
**The app cannot reach the model.** Check that `.env.local` exists, that the key is correct and that you restarted `npm run dev` after editing it.
## Glossary
- **Agent:** code that asks Claude to do a job and checks the answer.
- **Adapter:** code that talks to one platform.
- **Fixture:** a fake sample used for tests.
- **Eval:** a repeatable check of the agent's output on fixtures.
- **Draft:** any caption, time slot or reply that has not been approved yet.
PLAN.md# the app: build plan
Run /clear between phases. Use plan mode before Phase 2 and Phase 5. Run /code-review and commit at the end of each phase.
Each phase ends with a /goal prompt. Paste it after /clear. To stop a goal early, run `/goal clear`.
---
## Phase 1: Scaffold
Goal: Create a typed, tested project skeleton with a validated business profile and no feature code yet.
```
/goal Scaffold the the app per SPEC.md and CLAUDE.md. End state: a Next.js App Router project in TypeScript strict mode with Vitest, Zod, ESLint, Drizzle and SQLite installed; src/agent, src/platforms, src/app and src/db folders exist; a Zod BusinessProfile schema (name, type, tone, timezone, hours, address, platforms) with unit tests for valid and invalid profiles; tests/fixtures holds 3 fictional sample businesses (cafe, salon, bookshop) as JSON with no real data; npm scripts dev, test, typecheck, lint, build and eval exist (eval may print a placeholder and exit 0); scripts/smoke.ts loads a fixture profile and prints it; .gitignore covers node_modules, .next, .env* and the SQLite file. Check: run the verify skill. npm run typecheck, npm run lint, npm test and npm run build must each exit 0, and paste each command's output into the conversation. Constraints: no external API calls; no caption, scheduling or comment code yet; no dependencies beyond those named here without asking first; no real keys (use YOUR_API_KEY); keep all changes inside this project folder; no secrets in code or fixtures. Before coding, read SPEC.md and propose a short file plan, then proceed. Stop after 35 turns and report what remains and which check is failing.
```
Check: `npm run typecheck && npm run lint && npm test && npm run build`
Constraints: no new dependencies without asking, no real posting, no secrets in files.
Turn bound: stop after 35 turns and report what remains.
---
## Phase 2: Caption agent
Goal: Turn a photo and a business profile into Zod-validated captions for each platform, with fixture-based checks.
```
/goal Build the caption agent per SPEC.md and .claude/skills/caption-style/SKILL.md. End state: src/platforms/instagram.ts and src/platforms/facebook.ts export length limits, tone rules and hashtag limits as constants behind a shared adapter interface; stub adapters exist for Google Business Profile and TikTok; src/agent builds a prompt from the profile JSON plus the photo as image input and calls the Claude API with ANTHROPIC_API_KEY from .env.local; model output is parsed with a Zod schema, retried once on failure, then returns an error (never raw text); npm run eval runs fixture checks for the 3 sample businesses, prints a pass/fail table and fails if any caption exceeds its platform limit. Unit tests mock the model, so npm test makes no network calls. Check: npm run eval exits 0 with all 3 businesses passing; then run the verify skill with typecheck, lint, test and build all exiting 0. Constraints: no real posting; secrets only in .env.local; ask before adding the Anthropic SDK or any package not already installed; no hard-coded limits in prompts; keep changes inside this project folder. Stop after 40 turns and report what remains and which check is failing.
```
Check: `npm run eval` (live calls, run once at phase end and report approximate token use), then `npm run typecheck && npm run lint && npm test && npm run build`
Constraints: no new dependencies without asking, no real posting, no secrets in files.
Turn bound: stop after 40 turns and report what remains.
---
## Phase 3: Scheduler
Goal: Suggest posting times with plain rules so timezones, collisions and open hours are tested code, not model guesses.
```
/goal Build the scheduler per SPEC.md. End state: src/agent/schedule.ts holds pure functions that take the business profile hours, the business timezone and existing posts, then return slot times stored in UTC with the timezone saved separately; slots never duplicate an existing slot for the same platform; slots fall inside the likely open hours from the profile; the agent writes only a short reasoning string for each slot and cannot change a time, and that string is validated with Zod. Unit tests cover: a timezone conversion across a daylight saving change, a collision case, an open-hours case, and a case where a business has no hours set (the function must return a clear error, not guess). Check: npm test exits 0 with these cases passing; npm run typecheck, npm run lint and npm run build exit 0. Constraints: no real posting; no secrets in code or fixtures; no new dependencies without asking first (use built-in Intl or an approved date library); keep changes inside this project folder. Stop after 35 turns and report what remains and which check is failing.
```
Check: `npm test && npm run typecheck && npm run lint && npm run build`
Constraints: no new dependencies without asking, no real posting, no secrets in files.
Turn bound: stop after 35 turns and report what remains.
---
## Phase 4: Comment replies
Goal: Classify incoming comments safely, draft replies only from the profile, and flag anything sensitive for the owner.
```
/goal Build comment handling per SPEC.md and .claude/skills/comment-triage/SKILL.md. End state: src/agent/comments/ classifies each comment into hours, address, contact, menu_or_services, thanks, other or sensitive; a rule-based sensitive check runs before the model label is accepted, so any sensitive trigger (complaint, refund, allergy or health, legal, harassment, pricing not in profile) forces status needs_owner; draftReply is produced only when the answer exists in the business profile; a Zod schema validates model output. tests/fixtures/comments.json holds at least 20 fictional comments, including tricky ones such as "are you open late on Sunday because my order was wrong", and a Vitest table asserts each expected intent and status. Check: npm test exits 0; the test asserts zero sensitive comments have a draftReply; npm run typecheck, npm run lint and npm run build exit 0. Constraints: fictional comments only, no real customer data; no real replies posted; no new dependencies without asking first; no secrets in files; keep changes inside this project folder. Stop after 40 turns and report what remains and which check is failing.
```
Check: `npm test && npm run typecheck && npm run lint && npm run build`
Constraints: no new dependencies without asking, no real posting, no secrets in files.
Turn bound: stop after 40 turns and report what remains.
---
## Phase 5: Approval screen and publish gate
Goal: Give the owner one review screen and make publishing impossible for anything not approved.
```
/goal Build the approval screen and publish gate per SPEC.md and REVIEW.md. End state: one page under src/app lists posts and replies for a business with edit, approve and reject actions, and shows errors in plain language; src/platforms/publish.ts exposes a single publish function that throws if status is not approved; the stub adapters log to the console only; scripts/smoke.ts runs the agent on fixtures, prints drafts and asserts that none are published. A Vitest route-level test (or Playwright test, only if approved first) proves an unapproved item cannot be published through the API route. Check: npm run typecheck, npm run lint, npm test and npm run build exit 0, and npx tsx scripts/smoke.ts exits 0 with no published items. Constraints: no real posting, ever; no secrets in code, fixtures or logs; ask before adding Playwright or any other dependency; keep changes inside this project folder. Stop after 40 turns and report what remains and which check is failing.
```
Check: `npm run typecheck && npm run lint && npm test && npm run build && npx tsx scripts/smoke.ts`
Constraints: no new dependencies without asking (Playwright needs approval first), no real posting, no secrets in files.
Turn bound: stop after 40 turns and report what remains.
---
## After Phase 5
- Run /code-review on the full diff and fix blockers.
- Run the verify skill once more.
- Commit with /commit.
- Use /usage to see how many tokens the build took.
REVIEW.md# Review rules
Apply these rules to every change in this project. Report blockers first.
## Blockers
- Any code path that publishes or replies without `status === 'approved'`. Check the status in the function that calls the platform adapter, not only in the UI.
- Model output used without Zod parsing. Raw model text must never reach the database, the UI or an adapter.
- Secrets, tokens or real customer data in code, fixtures, logs or docs. Fixtures must be fictional. Keys must be placeholders such as `YOUR_API_KEY`.
## Checks
- Caption length limits per platform are enforced in code and covered by a test.
- Sensitive comments (complaints, refunds, medical, legal) never get a drafted reply. They are flagged `needs_owner`.
- EXIF location data is stripped before a photo is stored or sent to the model.
- New behavior has a test.
- Error states are shown to the owner in plain language, not as raw stack traces.
## Ignore
- Formatting and style nits. Lint handles them.
## Report format
- List blockers first, then suggestions.
- Maximum 10 items in total.
- Name the file and line for each item.
SPEC.md# the app: starter spec
Fill in the TODOs. Claude will refine this file before coding and will ask when a TODO blocks a decision.
## Problem and user
- Who: busy local business owner (cafe, salon, shop).
- Skill level: non-technical.
- Time budget: about 10 minutes a week.
- Pain: regular posting, writing captions and replying to comments takes time the owner does not have.
## MVP flow
1. Create a business profile: name, type, tone, hours, address, timezone, platforms.
2. Upload 5 photos for the week. EXIF location data is removed on upload.
3. The agent drafts one caption per photo for each selected platform, with hashtags.
4. The agent proposes a posting schedule inside the business's likely open hours.
5. The owner reviews everything on one screen and can edit, approve or reject each item.
6. Approved items go to the platform adapter. In MVP the stub adapter logs to the console.
7. Incoming comments are fetched. Simple questions get draft replies. Everything else is flagged for the owner.
Nothing is posted or replied to automatically. Every item stays a draft until the owner approves it.
## Out of scope for MVP
- Real platform OAuth and publishing
- Image generation
- Analytics
- Multi-user teams
- Payments
## Data model (sketch)
- Business: id, name, type, tone, timezone, hours, address, platforms
- Photo: id, businessId, storedPath, weekStart (EXIF location removed)
- Post: id, photoId, platform, caption, scheduledAt (stored in UTC), status (draft | approved | rejected | published)
- Comment: id, platform, text, intent (hours | address | contact | menu_or_services | thanks | other | sensitive), draftReply, status (draft | needs_owner | approved | rejected | published)
## Acceptance criteria
- [ ] 5 uploaded photos produce 5 posts for each selected platform.
- [ ] Every caption is within its platform's length limit, defined in src/platforms/<name>.ts.
- [ ] The schedule has no duplicate slots and falls inside the business's likely open hours, derived from the profile hours.
- [ ] An opening-hours question gets a reply that matches the profile.
- [ ] A complaint is marked needs_owner and gets no draft reply.
- [ ] Nothing reaches the platform adapter without approved status.
- [ ] A seventh upload in one week for one business is refused.
- [ ] Stored photos have EXIF location data removed.
## Open questions for the owner
- Which platforms first? Suggestion: Instagram and Facebook. Answer: TODO
- How will real comments be ingested: pasted by hand, a platform API later, or an export file? Answer: TODO
- Hosting: local only, or a small cloud host? Answer: TODO
- Timezone and opening hours for the first sample business? Answer: TODO
- Should the owner approve captions and replies in one batch or one item at a time? Answer: TODO
## Rules for Claude
- Ask instead of guessing when a TODO blocks a decision.
- Propose any new dependency and wait for approval before adding it.
- Keep all sample businesses fictional.