1691 lines
94 KiB
Markdown
1691 lines
94 KiB
Markdown
# Build an AI Content Creation Machine (Cadence)
|
||
|
||
All prompts from this tutorial, in order. Copy and paste any prompt directly into your AI agent.
|
||
|
||
Total prompts: 45
|
||
|
||
---
|
||
|
||
## Usage & License
|
||
|
||
For your own personal and commercial projects.
|
||
**Not** allowed: reselling, repackaging, redistributing, or republishing these prompts
|
||
— on YouTube, Gumroad, paid courses, newsletters, or anywhere else.
|
||
Send people to komputermechanic.com instead.
|
||
Violations will be reported and taken down.
|
||
|
||
— Komputer Mechanic · https://komputermechanic.com
|
||
|
||
---
|
||
|
||
## Prompt 1
|
||
|
||
### Prompt 1 — Introduce Yourself & Meet the Owner
|
||
|
||
```
|
||
Your name is Orchestrator. You are the top-level coordinator of my multi-agent Hermes setup.
|
||
I am the owner and hold the highest authority — I may instruct you directly at any time.
|
||
|
||
Here's who I am and what you're helping me build:
|
||
- My name: [YOUR NAME]
|
||
- My brand / account name: [e.g. KomputerMechanic]
|
||
- My social handle (no @): [e.g. komputermechanic]
|
||
- What my brand teaches or does, in ONE sentence: [e.g. "teaches technical builders to
|
||
build with AI, agents, and automation"]
|
||
- My audience: [who follows you / who you make content for]
|
||
- My voice: [e.g. "plain, direct, no hype, occasionally cheeky"]
|
||
- My time zone and working hours: [e.g. CET, 9am–6pm]
|
||
|
||
We are building CADENCE: a premium content-automation studio. A crew of four specialist
|
||
agents will find content ideas, write Instagram/TikTok carousels that TEACH, fit them into
|
||
designed templates, render them as finished 1080×1350 images, and publish them through
|
||
Buffer — all runnable from a beautiful dashboard.
|
||
|
||
Your job is to coordinate four specialists on my behalf — ATLAS (research and content
|
||
ideas), VERA (carousel copywriting), KITE (design, rendering, engineering), and ORIN
|
||
(publishing, growth, analytics). You take my instructions, delegate to the right
|
||
specialist, check the work, and bring results back to me clearly. You own outcomes: when I
|
||
hand you something that takes several steps across agents, you coordinate it end to end and
|
||
come back with a finished result — not a half-done handoff.
|
||
|
||
Save all of this to your long-term memory — especially the brand name, handle, niche
|
||
sentence, and voice, because every piece of content the crew ever writes must be written
|
||
FOR THIS BRAND. Confirm you've got it, and name yourself, me, and my brand back in one line.
|
||
```
|
||
|
||
## Prompt 2
|
||
|
||
### Prompt 2 — Let the Orchestrator Interview You
|
||
|
||
```
|
||
You now know the basics. Before we build the crew, fill in whatever gaps you still have.
|
||
|
||
Ask me your own follow-up questions — one at a time, waiting for my answer each time —
|
||
about whatever would genuinely help a CONTENT crew do great work for my brand: the topics I
|
||
can credibly teach, content styles I love and hate, accounts I admire, what I'm promoting
|
||
or selling, how often I want to post, and any hard rules for my voice (words I'd never
|
||
use, claims I'd never make). Keep going until you could brief a copywriter properly, then
|
||
stop.
|
||
|
||
Summarise what you've learned, save all of it to your long-term memory, and confirm. Later,
|
||
when we create the specialists, you'll pass each one the parts they need.
|
||
```
|
||
|
||
## Prompt 3
|
||
|
||
### Prompt 3 — Install Permanent Operating Rules
|
||
|
||
```
|
||
These are your permanent operating rules. Follow them in every interaction.
|
||
|
||
PROGRESS
|
||
On any task with more than one step, send a short status line before starting each step.
|
||
Format: [Agent]: Step X of Y — [what you're doing now]
|
||
Never go silent for more than 60 seconds on an active task.
|
||
|
||
APPROVAL
|
||
Always show me your plan before you act on it.
|
||
|
||
COMMUNICATION
|
||
Keep responses short and clear — no padding, no filler.
|
||
When giving options, always label them: 1, 2, 3.
|
||
Lead with the decision I need to make, not background context.
|
||
Never open with "Great question," "Certainly," or "Absolutely."
|
||
|
||
DELEGATION
|
||
In one line, tell me which specialist you're routing to and why.
|
||
Never fabricate a result. If something failed, say so plainly.
|
||
|
||
CONTENT QUALITY (Cadence-specific)
|
||
Never publish or schedule anything without my explicit approval.
|
||
Never invent facts, statistics, product names, or sources in content.
|
||
Plain language always: short words, short sentences, no hype.
|
||
|
||
Confirm all rules are saved to your long-term memory.
|
||
```
|
||
|
||
## Prompt 4
|
||
|
||
### Prompt 4 — Plan the Content Crew
|
||
|
||
```
|
||
Our crew is five agents, and you — the Orchestrator — are already one of them: you're the
|
||
main agent I'm talking to right now, so you do NOT get a new profile. The other four are
|
||
specialists you'll create as their own persistent Hermes profiles (not temporary
|
||
sub-agents), each with a stable identity, dedicated memory, and isolated workspace.
|
||
|
||
Each has a PROFILE NAME (the folder on disk) and a CADENCE NAME (their identity):
|
||
|
||
profile `scout` → ATLAS — the content scout: finds carousel ideas, researches facts.
|
||
profile `scribe` → VERA — the writer: turns ideas into carousel copy that teaches.
|
||
profile `dev` → KITE — the designer/engineer: fits copy to templates, renders images,
|
||
builds and maintains the dashboard and pipeline.
|
||
profile `reach` → ORIN — the publisher: captions, hashtags, Buffer scheduling, analytics.
|
||
|
||
The profile names (scout/scribe/dev/reach) matter: the pipeline we build later calls the
|
||
agents by profile folder, e.g. ~/.hermes/profiles/scribe. Do not rename them.
|
||
|
||
Each specialist gets its own SOUL.md identity file at ~/.hermes/profiles/<profile>/SOUL.md;
|
||
you keep your own identity in your long-term memory. I remain the owner with final
|
||
authority. Confirm you understand the plan, the five roles, and the profile↔name mapping.
|
||
```
|
||
|
||
## Prompt 5
|
||
|
||
### Prompt 5 — Create the Four Specialists
|
||
|
||
```
|
||
Create the four SPECIALISTS as persistent Hermes profiles — profile names scout, scribe,
|
||
dev, reach. For each one, do three things in order: (1) create the profile with
|
||
`hermes profile create <profile> --clone`, which makes ~/.hermes/profiles/<profile>/;
|
||
(2) write its EXACT identity into that profile's SOUL.md; (3) verify the agent responds
|
||
with the right identity before moving to the next. Do NOT create them as transient
|
||
helpers. (Each identity below says "the owner" — write my real name in its place, and
|
||
write my real handle where it says "@[HANDLE]".)
|
||
|
||
IMPORTANT — do NOT create a profile for the Orchestrator. You ARE the Orchestrator. Only
|
||
the four specialists get profiles.
|
||
|
||
IMPORTANT — a cloned profile can carry a copy of a Telegram bot token in its .env. After
|
||
creating each profile, if ~/.hermes/profiles/<profile>/.env contains TELEGRAM_BOT_TOKEN,
|
||
remove that line (back the file up first). Two profiles sharing one token break the
|
||
gateway, and Cadence's specialists are called headlessly — they need no messaging platform.
|
||
|
||
— ATLAS (profile: scout) —
|
||
Your name is Atlas. You are the content scout for the owner's Cadence studio. Your job is
|
||
to find fresh, scroll-stopping Instagram/TikTok carousel ideas and to research the facts
|
||
behind them. You propose specific, teachable angles — never vague themes. You only
|
||
reference real, well-known tools and facts; you NEVER invent product names, numbers, or
|
||
sources — if you can't verify something, you say so. You gather and structure the raw
|
||
truth and pass it to Vera to write. You do not write finished slides (Vera), design them
|
||
(Kite), or publish them (Orin).
|
||
|
||
— VERA (profile: scribe) —
|
||
Your name is Vera. You are the writer of the owner's Cadence studio — the best carousel
|
||
writer on the internet. You turn an idea into ONE carousel that TEACHES: when the reader
|
||
finishes, they have learned something concrete they can go DO. Slide 1 is the COVER —
|
||
keywords + a promise + curiosity, never the first tip. You write in plain, simple
|
||
language: short words, short sentences, no hype, no AI-tell filler words. You end with a
|
||
clear CTA (ask for a save or a send, and follow @[HANDLE]). You return clean structured
|
||
JSON when the pipeline asks for it. You do not research (Atlas), design (Kite), or
|
||
publish (Orin).
|
||
|
||
— KITE (profile: dev) —
|
||
Your name is Kite. You are the designer/engineer of the owner's Cadence studio. You take
|
||
Vera's copy and fit it to a chosen template — respecting each template's character budgets
|
||
so nothing overflows the frame; when text runs long you shrink or trim it to fit and say
|
||
so plainly. You also build and maintain the studio's code: the render engine, the server,
|
||
the dashboard. You write clean, well-commented, production-quality code in Python stdlib
|
||
and HTML/CSS/JS, you test what you ship, you back up a working file before you change it,
|
||
and you never leave the system broken. You do not research (Atlas), write copy (Vera), or
|
||
publish (Orin).
|
||
|
||
— ORIN (profile: reach) —
|
||
Your name is Orin. You are the publishing strategist of the owner's Cadence studio. You
|
||
handle captions, hashtags, scheduling and publishing through Buffer (Instagram + TikTok),
|
||
and you read real performance numbers back so the studio learns what works. You are
|
||
practical and honest about what the numbers say. You never publish without the owner's
|
||
approval. You do not research (Atlas), write slide copy (Vera), or design (Kite).
|
||
|
||
After creating all four profiles (scout, scribe, dev, reach), ask each one "Who are you?",
|
||
confirm it replies with the right Cadence identity, and report each profile's path +
|
||
SOUL.md confirmation + the verified reply. That's five agents total — you plus the four.
|
||
```
|
||
|
||
## Prompt 6
|
||
|
||
### Prompt 6 — Memory, Boundaries & Team Awareness
|
||
|
||
```
|
||
For each of the five agents, set up:
|
||
|
||
DEDICATED MEMORY — each agent's memory stores only what's relevant to its role.
|
||
UNIQUE IDENTITY — name, role, personality never change across sessions.
|
||
ISOLATED WORKSPACE — separate files, outputs, and session history per agent.
|
||
ROLE BOUNDARIES — each agent politely declines out-of-scope work in ONE line and names the
|
||
right teammate. Example: ask Vera for code and she replies "That's Kite's department."
|
||
|
||
Then give every agent this shared team awareness and make sure each one saves it:
|
||
|
||
The owner — may directly instruct any agent at any time. Final say on everything published.
|
||
Orchestrator — top-level coordinator.
|
||
Atlas (profile scout) — ideas and research.
|
||
Vera (profile scribe) — carousel copywriting.
|
||
Kite (profile dev) — design, rendering, engineering.
|
||
Orin (profile reach) — captions, publishing, analytics.
|
||
|
||
Also pass each specialist the parts of my brand brief they need (from your memory): all of
|
||
them get the brand name, handle, niche sentence, and voice; Vera additionally gets the
|
||
audience, the topics, and my content likes/dislikes.
|
||
|
||
Confirm once all five agents are updated, then run a "Who are you?" test on each and paste
|
||
their one-line answers.
|
||
```
|
||
|
||
## Prompt 7
|
||
|
||
### Prompt 7 — The Project Folder & the Activity Log
|
||
|
||
```
|
||
Create the Cadence project folder and its database. Everything we build lives in
|
||
~/cadence-dashboard/ (never inside ~/.hermes). Python stdlib only — no pip packages.
|
||
|
||
Build ~/cadence-dashboard/store.py — the data layer, importable as a module, SQLite at
|
||
~/cadence-dashboard/cadence.db (WAL mode, busy_timeout 5000, a module-level threading.Lock
|
||
around writes). Tables:
|
||
|
||
ideas(id INTEGER PK AUTOINCREMENT, title, angle, format, rationale,
|
||
source DEFAULT 'Atlas', status DEFAULT 'proposed', created_at)
|
||
-- idea status flow: proposed → promoted (or dismissed)
|
||
drafts(id INTEGER PK AUTOINCREMENT, idea_id, title, caption, hashtags,
|
||
template DEFAULT 'editorial', status DEFAULT 'writing', error,
|
||
buffer_post_id, created_at, published_at, note)
|
||
-- draft status flow: writing → review → fitting → fitted → designing → ready
|
||
-- → publishing → published/scheduled/drafted (or error)
|
||
slides(id INTEGER PK AUTOINCREMENT, draft_id, idx, kicker, title, body, template)
|
||
runs(id INTEGER PK AUTOINCREMENT, agent_name, task_description, model_used,
|
||
status, created_at)
|
||
settings(k TEXT PRIMARY KEY, v TEXT)
|
||
|
||
Helper functions (all with the lock, all returning plain dicts):
|
||
init() — creates tables, safe to re-run (use IF NOT EXISTS + column-add migrations)
|
||
log_run(agent, task, model="", status="completed") — inserts into runs, task capped
|
||
at 200 chars, created_at = ISO-8601 UTC
|
||
add_idea / list_ideas(limit=50) / get_idea / set_idea_status / update_idea / delete_idea
|
||
(delete cascades: removes the idea's drafts and their slides)
|
||
add_draft / get_draft / set_draft(draft_id, **fields) / list_drafts(limit=50) /
|
||
delete_draft (also removes its slides)
|
||
replace_slides(draft_id, slides, template) / get_slides(draft_id) /
|
||
update_slide(draft_id, idx, **fields)
|
||
get_settings() / set_settings(dict) — settings defaults:
|
||
brand_name, handle, niche (seed these three from MY brand — you saved it in Prompt 1;
|
||
if it's not in your memory, ASK me for the three values now instead of guessing),
|
||
voice="", default_slides="7", default_research="0", default_steroid="0"
|
||
|
||
Run `python3 -c "import store; store.init()"` in the project folder, then insert one test
|
||
run via log_run("dev", "built the Cadence store", "your-model") and show me the row.
|
||
Also prove the connection rule holds: call list_ideas() 200 times in a loop and show that
|
||
the process's open file count stays flat (ls /proc/self/fd | wc -l before and after).
|
||
If the count grows with the calls, connections are leaking — fix it before moving on.
|
||
```
|
||
|
||
## Prompt 8
|
||
|
||
### Prompt 8 — Agents Log Everything They Do
|
||
|
||
```
|
||
Orchestrator, save the following as a durable operating rule in YOUR OWN long-term memory
|
||
first, then distribute it to Atlas, Vera, Kite, and Orin — making sure each one also saves
|
||
it to their long-term memory:
|
||
|
||
---
|
||
Store this in your long-term memory as a durable operating rule:
|
||
|
||
Before sending any response, log what you did into the Cadence runs table by running:
|
||
python3 -c "import sys; sys.path.insert(0,'$HOME/cadence-dashboard'); import store; \
|
||
store.log_run('<agent-profile>', '<brief description>', '<model>', '<status>')"
|
||
|
||
Rules:
|
||
- <agent-profile> is your lowercase PROFILE name: orchestrator, scout, scribe, dev, or reach.
|
||
- <status> is completed or failed. <model> is the exact model you run on.
|
||
- Keep the description under 140 characters. Log every response. Never mention logging
|
||
unless the owner asks.
|
||
---
|
||
|
||
Have every agent run a smoke test log right after saving. Then show me the last five rows
|
||
of the runs table (agent_name, status, model_used, created_at).
|
||
```
|
||
|
||
## Prompt 9
|
||
|
||
### Prompt 9 — Brief the Orchestrator on the Kite Plan
|
||
|
||
```
|
||
Before we change anything, here's the whole plan for what we're about to build together.
|
||
Read it, then tell it back to me in your own words — do NOT change anything yet.
|
||
|
||
THE GOAL
|
||
Kite (profile dev) is about to build our studio's dashboard, and I want to talk to the
|
||
engineer DIRECTLY — in his own topic of this group, answered by his own bot with his
|
||
own identity. You keep this topic; Kite gets his.
|
||
|
||
WHY A SECOND BOT
|
||
Telegram gives one bot a single identity, and one bot token allows only ONE listener.
|
||
Instead of routing everything through you (a bottleneck) or a routing plugin (complex,
|
||
fragile), each agent I talk to simply gets its OWN bot: you have yours, Kite gets his.
|
||
The only wrinkle: in a group, every bot hears every topic — so each bot also gets a
|
||
tiny ten-line "stay in your lane" filter that ignores messages outside its own topic.
|
||
That's not a router; it's a mute button for other people's lanes.
|
||
|
||
THE ORDER (each step is its own prompt; do nothing until each arrives)
|
||
1. Kite's bot comes to life: I store its token MYSELF in the terminal, then you start
|
||
his own gateway service. He answers me in a direct message first. NOT in the group.
|
||
2. We capture the addresses: the group's chat id and each topic's thread id.
|
||
3. You wire the lanes for BOTH bots and authorize the group for Kite's home —
|
||
while Kite's bot is still OUTSIDE the group, so there is never a messy moment.
|
||
4. Only then do I add Kite's bot to the group — he walks in already knowing his lane —
|
||
and we prove it: one question per topic, exactly one answer from the right agent.
|
||
|
||
ONE STANDING RULE, from now to forever: tokens and API keys are NEVER pasted into this
|
||
chat, and you never echo commands that contain them. Secrets go in via hidden-input
|
||
commands I run myself in the terminal; I just tell you when it's done. Save this rule
|
||
to your long-term memory.
|
||
|
||
Tell me the plan back in your own words, confirm the standing rule is saved, and wait
|
||
for the next prompt.
|
||
```
|
||
|
||
## Prompt 10
|
||
|
||
### Prompt 10 — Store Kite's Token
|
||
|
||
```
|
||
mkdir -p ~/.hermes/profiles/dev; echo "Now paste your Kite bot token and press Enter - you will see the first 4 and last 4 characters, the middle stays masked:"; T=""; while IFS= read -r -s -n1 c; do [ -z "$c" ] && break; T="$T$c"; if [ ${#T} -le 4 ]; then printf '%s' "$c"; else printf 'x'; fi; done; if [ -z "$T" ]; then echo; echo "nothing received - run this again"; else if [ ${#T} -gt 8 ]; then M=$(printf '%*s' $((${#T}-8)) '' | tr ' ' 'x'); printf '\r%s%s%s\n' "${T:0:4}" "$M" "${T: -4}"; else echo; fi; printf '%s=%s\n' TELEGRAM_BOT_TOKEN "$T" > ~/.hermes/profiles/dev/.env; chmod 600 ~/.hermes/profiles/dev/.env; echo "received ${#T} characters - saved"; fi; unset T c M
|
||
```
|
||
|
||
## Prompt 11
|
||
|
||
### Prompt 11 — Start Kite's Gateway
|
||
|
||
```
|
||
Step 1 of the plan: Kite's bot comes to life. I have already created his bot and stored
|
||
its token myself at ~/.hermes/profiles/dev/.env — per our standing rule, no secrets in
|
||
this chat.
|
||
|
||
1. Verify that file exists and is non-empty WITHOUT ever printing its contents
|
||
(the dev profile is a complete Hermes home).
|
||
2. SANITY CHECK before starting anything: call Telegram's getMe with each token (never
|
||
printing either) and compare the two bot ids. If Kite's token resolves to the SAME
|
||
bot as yours, STOP — tell me I stored my own bot's token by mistake and that I need
|
||
to create a NEW bot in @BotFather and redo the previous card. Two gateways on one
|
||
token fight over messages; never start the service in that state.
|
||
3. Create a systemd --user service (hermes-kite-gateway.service) running the same
|
||
gateway command as your own service but with HERMES_HOME=~/.hermes/profiles/dev in
|
||
its Environment. Enable + start it, confirm it is active and STAYS up, and tell me
|
||
the bot's @username. Docs if unsure: https://hermes-agent.nousresearch.com/docs
|
||
If anything fails, stop and disable the service before reporting — never leave a
|
||
broken service retrying in the background.
|
||
|
||
Kite's bot stays OUT of the group for now. I'll /start it in a direct message and say
|
||
hello — Kite must answer AS KITE (his SOUL.md identity).
|
||
```
|
||
|
||
## Prompt 12
|
||
|
||
### Prompt 12 — Capture Each Topic's Address
|
||
|
||
```
|
||
What is this topic's thread ID? Reply with just that one number so I can note it down.
|
||
```
|
||
|
||
## Prompt 13
|
||
|
||
### Prompt 13 — Wire the Lanes (Before Kite Enters)
|
||
|
||
```
|
||
Time to wire the lanes — BEFORE Kite's bot enters the group. Our numbers:
|
||
group chat id = [YOUR GROUP ID — the -100… number]
|
||
Orchestrator topic thread id = [ORCHESTRATOR TOPIC ID]
|
||
Kite topic thread id = [KITE TOPIC ID]
|
||
Studio topic thread id = [STUDIO TOPIC ID]
|
||
Save all four to your long-term memory.
|
||
|
||
STEP 1 — THE LANE FILTER, twice. For BOTH Hermes homes (~/.hermes for you,
|
||
~/.hermes/profiles/dev for Kite), create an out-of-tree plugin directory
|
||
<home>/plugins/topic_lane/ containing plugin.yaml (name: topic_lane,
|
||
kind: standalone — kind matters) and __init__.py that registers a
|
||
pre_gateway_dispatch hook: on Telegram events, if the chat id equals OUR group AND
|
||
the event's thread id is not in THIS home's list of allowed topics, return
|
||
{"action": "skip"}; otherwise return None. Allowed topics: YOUR home gets TWO — the
|
||
Orchestrator topic and the Studio topic; the dev home gets ONE — the Kite topic.
|
||
Hard-code each home's group id + its topic list in its own copy. DMs and other chats
|
||
are unaffected (return None).
|
||
|
||
STEP 2 — AUTHORIZE THE GROUP FOR KITE'S HOME. Merge this into
|
||
~/.hermes/profiles/dev/config.yaml (create the file if missing; if a platforms or
|
||
plugins block exists, MERGE — never duplicate keys):
|
||
platforms:
|
||
telegram:
|
||
require_mention: false
|
||
group_allowed_chats:
|
||
- "<the group id above>"
|
||
plugins:
|
||
enabled:
|
||
- topic_lane
|
||
If Hermes blocks you from writing that file, do NOT work around it — reply with ONE
|
||
terminal command I can run instead, then wait for my "done".
|
||
|
||
STEP 3 — ENABLE YOUR OWN copy the allowed way (`hermes plugins enable topic_lane`),
|
||
then restart BOTH gateways (run what you are allowed to run yourself; give me the
|
||
command for anything you cannot). Confirm both are active again.
|
||
|
||
When all three steps are done, tell me — the next prompt lets Kite into the group.
|
||
```
|
||
|
||
## Prompt 14
|
||
|
||
### Prompt 14 — Kite Enters & the Proof
|
||
|
||
```
|
||
Who are you, and which topic is this? One short line.
|
||
```
|
||
|
||
## Prompt 15
|
||
|
||
### Prompt 15 — Brief Kite on the Studio He's About to Build
|
||
|
||
```
|
||
Kite, before you write a single line of code, here's the whole picture of what we're
|
||
building together. Read it, then tell it back to me in your own words — build nothing yet.
|
||
|
||
THE DESTINATION
|
||
CADENCE: a premium content-automation studio with a beautiful web dashboard. The full
|
||
loop: Atlas proposes carousel ideas → I promote one → Vera writes the copy → you fit it
|
||
to a designed template and render finished 1080×1350 images → Orin publishes through
|
||
Buffer. All of it visible and steerable from the dashboard you are about to build.
|
||
|
||
WHAT YOU'LL BUILD, IN ORDER (each step arrives as its own prompt — never build ahead)
|
||
1. A small server so the dashboard has a home, plus a web upload page.
|
||
2. I hand you a FINISHED, hard-coded dashboard design — you lock it in as the design
|
||
source of truth. You will WIRE it to live data, tab by tab; you never redesign it.
|
||
3. A backup protocol, then the first two tabs come alive.
|
||
4. The render engine: headless-Chromium screenshots of designed HTML slides, template
|
||
packs, auto-fit so text never overflows a frame.
|
||
5. The pipeline: code that calls Atlas, Vera and Orin headlessly and turns their work
|
||
into drafts, rendered decks, and published posts.
|
||
6. The content API and every remaining tab, wired live until zero demo data remains.
|
||
|
||
PRINCIPLES FOR THE WHOLE BUILD (save these to your long-term memory)
|
||
- Python stdlib only — no pip packages, ever.
|
||
- The uploaded design is the ONE design authority. Wire it; never restyle it.
|
||
- SECURITY: the dashboard binds to 127.0.0.1 only and is reached through my SSH tunnel.
|
||
Never 0.0.0.0, never open ports, never reverse proxies — on any prompt, ever.
|
||
- Back up before you edit (a backup script arrives early — use it every time).
|
||
- Verify with real commands and show me evidence; never claim untested work.
|
||
- After EVERY prompt that touches the dashboard: compare your result side by side with
|
||
/template (the untouched original) and fix any visual drift until they are identical.
|
||
The design never changes — that is the whole point of starting from a finished design.
|
||
- Copy is WRITTEN to fit designs (character budgets per template) — that philosophy
|
||
shows up all through the pipeline.
|
||
|
||
Tell me the plan back in your own words, confirm the principles are saved, and wait
|
||
for the next prompt.
|
||
```
|
||
|
||
## Prompt 16
|
||
|
||
### Prompt 16 — See the Destination: the Dashboard Server
|
||
|
||
```
|
||
Build ~/cadence-dashboard/server.py — VERSION 1: just enough to stand the dashboard up
|
||
before we build the content engines. Python stdlib ThreadingHTTPServer, port from the
|
||
LOOP_PORT env var, default 8892.
|
||
|
||
SECURITY, NON-NEGOTIABLE: bind to 127.0.0.1 ONLY — this dashboard must NEVER be
|
||
reachable from the public internet (it has no login, and later prompts add file upload
|
||
and publishing). Never bind 0.0.0.0, never open firewall ports, never set up a reverse
|
||
proxy for it. I reach it through an SSH tunnel from my own computer — that is the only
|
||
door, and my SSH key is the lock.
|
||
|
||
GET / — serve ~/cadence-dashboard/index.html with Cache-Control: no-cache
|
||
(the UI updates often — never a stale shell). It doesn't exist yet:
|
||
404 until Prompt 12 installs it — expected, don't chase it.
|
||
MEDIA DIRS — /drafts/..., /previews/..., /uploads/... with path protection
|
||
(resolved path must stay inside the project); .png/.jpg as images,
|
||
.mp4 with HTTP Range support, and .html/.json served with their REAL
|
||
content types (text/html, application/json — never default
|
||
everything to image/png; a later feature loads a draft's
|
||
carousel.html in an iframe and a wrong type breaks it).
|
||
GET /api/state — the dashboard snapshot (cache ~3s), built ONLY from what already
|
||
exists: the runs activity log from Prompt 8 plus the (still empty)
|
||
ideas/drafts tables. Keys the design's JS will read: kpis, fleet,
|
||
activity, agentsDetail, workload, outputs, pipeline, reachSeries,
|
||
nameMap, and heatmap = { rows: [{name, role, counts: [24 ints,
|
||
hour-of-day, LOCAL time], total}] (one row per specialist), max,
|
||
totalEvents, peakHour }. Zero cells stay zero; empty content tables
|
||
mean clean zeros, never a crash.
|
||
|
||
On startup run store.init(). Start the server and KEEP it running from now on (nohup or
|
||
a systemd --user unit — your choice; print how you started it). Confirm from the server
|
||
itself that http://127.0.0.1:8892/api/state returns 200 with real run counts, and that
|
||
the port is NOT reachable on the public IP.
|
||
|
||
Then teach me the door: print the exact tunnel command for THIS machine, with my real
|
||
username and this server's PUBLIC IP ADDRESS filled in (the actual numbers — find them
|
||
yourself; never a hostname, never a placeholder) — the shape is
|
||
ssh -N -L 8892:127.0.0.1:8892 <username>@<server-ip>
|
||
so what you print looks like ssh -N -L 8892:127.0.0.1:8892 anna@203.0.113.7 — and tell
|
||
me: run that in a terminal on my own computer, keep it open, and the dashboard lives at
|
||
http://localhost:8892 in my browser. (GET / is a 404 for now — the design
|
||
arrives over the next two prompts.)
|
||
```
|
||
|
||
## Prompt 17
|
||
|
||
### Prompt 17 — Build the Web Upload Page & Hand Over the Design Files
|
||
|
||
```
|
||
Add a web upload page so I can hand you four files I'll download from the Tutorial page —
|
||
cadence-dashboard-template.html (the finished design) and three template packs
|
||
(templates-pack.json, playbook-pack.json, reels-pack.json). Do NOT ask me to run curl or
|
||
paste file contents into a prompt, and do NOT try to recreate these files yourself.
|
||
|
||
In server.py add:
|
||
GET /upload — a small self-contained upload page. No external libraries, but make
|
||
it genuinely pleasant, not a bare form:
|
||
· a large DRAG-AND-DROP zone (dragover highlight) that also opens a
|
||
file picker on click — accept .html,.json, MULTIPLE files at once
|
||
(drop all four together and they queue up)
|
||
· each file uploads via FileReader.readAsDataURL → POST JSON
|
||
{name, data: <data-URL>} to /api/upload, sequentially, with a
|
||
per-file row showing name, size, and live status
|
||
(uploading… / ✓ saved / ✗ failed with the reason)
|
||
· a CHECKLIST of the four expected files — the template
|
||
(cadence-dashboard-template.html) and the three packs
|
||
(templates-pack.json, playbook-pack.json, reels-pack.json) —
|
||
each ticking green as it lands, so I always see what is still
|
||
missing; when all four are ticked show a clear "All four in —
|
||
return to Kite's topic and continue" banner
|
||
· files with other names still upload fine (the checklist is
|
||
guidance, not a gate); duplicate uploads simply overwrite
|
||
POST /api/upload — JSON {name, data: <base64 data-URL>} (this exact
|
||
encoding): save into ~/cadence-dashboard/uploads/, images +
|
||
.json/.html/.txt/.md/.pdf allowed, reject any name containing "/"
|
||
or "..". SPECIAL CASE: .html/.json files arriving from the /upload
|
||
page are the tutorial design files — save exactly those into
|
||
~/cadence-dashboard/ itself (project root).
|
||
GET /api/uploads — {files:[{name, url, size, ...}]} listing uploads/;
|
||
POST /api/uploads/delete {name}.
|
||
|
||
Restart the server so the routes are live, then remind me: with my SSH tunnel from the
|
||
previous prompt open, the upload page is http://localhost:8892/upload — never a public
|
||
URL. STOP and wait — I'll drag all four files in and come back when the page shows all
|
||
four ticked. (If the files are already in the project folder, say so and continue.)
|
||
```
|
||
|
||
## Prompt 18
|
||
|
||
### Prompt 18 — The Template Becomes the Dashboard
|
||
|
||
```
|
||
I've uploaded the files — cadence-dashboard-template.html, templates-pack.json,
|
||
playbook-pack.json, and reels-pack.json are in ~/cadence-dashboard/. Lock them in.
|
||
|
||
1. Copy cadence-dashboard-template.html to ~/cadence-dashboard/index.html — the file the
|
||
server serves at GET /. Keep the untouched original reachable at GET /template.
|
||
|
||
2. DESIGN SOURCE OF TRUTH — the template is the ONE design authority. You are going to
|
||
WIRE its data, never redesign it. Do not change its layout, spacing, colours
|
||
(cream paper, ink, the yellow accent), fonts, components, or copy. If a later prompt
|
||
makes you add anything visual, first open /template, match its exact visual language,
|
||
then compare side-by-side until identical. New CSS goes inline; the template uses the
|
||
Tailwind CDN so utility classes work.
|
||
|
||
3. The template paints MOCK values on load so nothing flashes empty — those mock numbers
|
||
and any placeholder cards are demo data. CLEAN AS YOU WIRE: as each tab goes live in
|
||
the prompts that follow, delete that tab's demo data so the finished dashboard shows
|
||
ONLY real content from my crew. A tab still showing a fake number or sample card is
|
||
not done.
|
||
|
||
Confirm: / serves the template, /template serves the untouched original, and every tab
|
||
of the dashboard loads (each still showing its baked demo data — we wire them next).
|
||
```
|
||
|
||
## Prompt 19
|
||
|
||
### Prompt 19 — Backup Protocol & Version Badge
|
||
|
||
```
|
||
Set up the safety net before we wire anything.
|
||
|
||
1. BACKUP PROTOCOL — create ~/cadence-dashboard/cadence-backup.sh "<note>": copies the
|
||
core source files (server.py, store.py, index.html, the pack JSONs — plus pipeline.py
|
||
and render.py once they arrive in Part 2; back up whatever exists) into
|
||
~/cadence-dashboard-backups/<timestamp>/ together with a MANIFEST, the note, and a
|
||
restore.sh that copies everything back. From now on, EVERY prompt that edits code
|
||
starts with a backup — save that as a durable rule in your long-term memory.
|
||
|
||
2. VERSION BADGE — the SIDEBAR's bottom (left nav) carries the template's small version
|
||
label; set it to "v0.1" and
|
||
bump the minor number on every wiring prompt from here on, so I can hard-refresh and
|
||
instantly see the new version landed. The badge must match the sidebar's existing
|
||
style EXACTLY — compare against /template; nothing else in the sidebar changes.
|
||
|
||
Run the first backup now ("pre-wiring baseline") and show me the snapshot folder.
|
||
```
|
||
|
||
## Prompt 20
|
||
|
||
### Prompt 20 — Wire the Dashboard & Agents Tabs
|
||
|
||
```
|
||
Back up first. Wire the two overview tabs from /api/state. Delete their mock values.
|
||
|
||
DASHBOARD TAB: KPI cards (total agent runs, ideas in pipeline, carousels shipped) from
|
||
real counts; the activity chart wired to real run history. Charts must NEVER render
|
||
while their container is hidden (a chart drawn at width 0 stretches garbage when shown —
|
||
skip hidden renders and redraw on tab entry). The page must not visibly "reload" every
|
||
poll: only repaint a section when its data signature actually changed.
|
||
|
||
AGENTS TAB: a hierarchy — the Orchestrator as a wide command deck on top (name, status,
|
||
recent coordination), connector lines flowing down to four specialist cards (Atlas /
|
||
Vera / Kite / Orin): each with glyph, role tagline, runs count, success %, model chips,
|
||
recent activity lines, last-active time. Below the cards, the ACTIVITY HEATMAP —
|
||
hour-of-day (0–23) × the four specialists, built ONLY from real run timestamps in local
|
||
time. This consumes the /api/state heatmap contract from Prompt 10 EXACTLY:
|
||
{ rows: [{name, role, counts: [24 ints], total}], max, totalEvents, peakHour } — one row
|
||
per specialist; a zero cell stays completely dark; intensity scales with count; hover
|
||
shows "<agent> · 14:00–15:00 · N runs"; totals + a peak-hour stat in the header. Style
|
||
it as a dark "instrument screen" inset on a light card so it doesn't fight the page.
|
||
|
||
One tasteful touch of life: animate small yellow dots flowing from the Orchestrator deck
|
||
along the connectors into each specialist card on a staggered loop (respect
|
||
prefers-reduced-motion; keep it subtle).
|
||
|
||
Confirm both tabs show only real data and the heatmap matches the runs table.
|
||
DESIGN CHECK, always the last step before you report: open every page you touched
|
||
side by side with /template — layout, spacing, colours, fonts, components must look
|
||
IDENTICAL. Fix any drift until you cannot tell them apart. Then bump the badge.
|
||
```
|
||
|
||
## Prompt 21
|
||
|
||
### Prompt 21 — The Carousel Render Engine, Part 1: the Exporter
|
||
|
||
```
|
||
Build the screenshot exporter at ~/carousel-templates/export-slides.js.
|
||
|
||
Setup: create ~/carousel-templates/, run `npm init -y`, then
|
||
`npm install puppeteer-core` (puppeteer-CORE — it does NOT download a browser). Node may
|
||
not be on your PATH: Hermes bundles one at ~/.hermes/node/bin — use it if needed.
|
||
|
||
export-slides.js takes two args: <carousel.html> <outputDir>. It must:
|
||
|
||
1. FIND A BROWSER without hardcoding a path — a findBrowser() function that checks, in
|
||
order, and returns the first that exists:
|
||
- the CADENCE_CHROME env var, if set and the file exists
|
||
- the newest Chromium under ~/.cache/ms-playwright/chromium-*/chrome-linux*/chrome
|
||
(this is the one Hermes installs — the usual winner)
|
||
- the newest under ~/.cache/puppeteer/chrome/*/chrome-linux*/chrome
|
||
- system installs: /usr/bin/google-chrome, /usr/bin/chromium, /usr/bin/chromium-browser
|
||
If none found, print a clear error telling the user to run
|
||
`npx playwright install chromium`, and exit code 3.
|
||
|
||
2. Launch it via puppeteer-core (headless, --no-sandbox --disable-setuid-sandbox
|
||
--disable-gpu), viewport 1080×1350, open the HTML file with waitUntil networkidle0.
|
||
|
||
3. For every element with class "slide" in the page (they are 1080×1350 blocks), take a
|
||
clipped screenshot saved as slide-01.png, slide-02.png, … into the output dir. Use
|
||
page-absolute clip coordinates (getBoundingClientRect + window.scrollX/scrollY).
|
||
|
||
4. Before screenshotting, if the page defines window.__fitText, call it and, if it returns
|
||
a non-empty array, print "OVERFLOW-FIX " + JSON of it (the render engine injects this
|
||
auto-shrink helper later — the exporter just runs it when present).
|
||
|
||
Test: write a tiny test HTML with two .slide divs (any solid background + big text),
|
||
run the exporter on it, and confirm two correct 1080×1350 PNGs appear. Report the browser
|
||
path findBrowser() picked.
|
||
```
|
||
|
||
## Prompt 22
|
||
|
||
### Prompt 22 — The Render Engine, Part 2: Templates, Packs & Auto-Fit
|
||
|
||
```
|
||
Build ~/cadence-dashboard/render.py — the render engine. Python stdlib only.
|
||
|
||
CORE FLOW — render_carousel(slides, template, out_dir):
|
||
slides = [{kicker, title, body}, ...]. Build ONE self-contained HTML document with one
|
||
<div class="slide"> per slide (1080×1350 each), inline CSS, write it to
|
||
<out_dir>/carousel.html, then run the exporter:
|
||
subprocess [NODE, ~/carousel-templates/export-slides.js, carousel.html, out_dir].
|
||
NODE = shutil.which("node") or the Hermes-bundled ~/.hermes/node/bin/node.
|
||
Return {ok, count, fixes, stderr} — parse any "OVERFLOW-FIX [...]" line from the
|
||
exporter's stdout into `fixes`.
|
||
|
||
ROLES — slide 0 renders as role "hook" (the cover), the last slide as role "cta", the
|
||
rest as "content". Templates style the three roles differently (cover = biggest type).
|
||
|
||
BUILT-IN TEMPLATES — at least 4, as pure CSS themes + an HTML builder per theme, e.g.:
|
||
editorial — newspaper serif on cream, vermilion accent rule
|
||
noir — black + gold serif, spotlit
|
||
split — bold colour-block half against a cream sheet
|
||
ticker — dark headline bar over cream, news-ticker vibe
|
||
Each slide shows: kicker (small label), title, body, the owner's @handle (READ IT from
|
||
store.get_settings()["handle"] at render time — never hardcode it), and a page counter
|
||
"NN / NN". Expose TEMPLATES (list of ids) and TEMPLATE_META = {id: {label, sub, category,
|
||
blurb, swatches:[hex,...]}} for the picker UI later. Category for these: "cadence".
|
||
|
||
READABILITY FLOOR — authored body text never below 28px. (Precedence rule: the floor
|
||
applies to the CSS you write; the auto-fit's emergency shrink below MAY go under it as a
|
||
last resort — clipped text is worse than small text.)
|
||
|
||
AUTO-FIT — inject a <script> defining window.__fitText into every carousel.html: for each
|
||
element carrying generated text (title/body/kicker), if it genuinely overflows its box
|
||
(scrollWidth > clientWidth+2, or it escapes the 1080×1350 slide frame), shrink its
|
||
font-size in 5% steps (floor 50%) until it fits, and return a list of
|
||
{slide, el, from, to} describing what changed. The exporter already calls it and prints
|
||
OVERFLOW-FIX — that's how render_carousel learns about `fixes`.
|
||
|
||
CHARACTER BUDGETS — fit_spec(template_id) returns {cover|content|cta: {title|body:
|
||
{min,max,target}}} — sensible hand-tuned bands per built-in template (e.g. content title
|
||
16–60 chars). The pipeline uses this so copy is WRITTEN to fit the design.
|
||
|
||
TEMPLATE PACKS — render.py ALSO loads templates-pack.json, playbook-pack.json, and
|
||
reels-pack.json at import (you uploaded them in Prompt 11). THE REAL SCHEMA (read it from the files, don't assume):
|
||
each pack is {version, fonts_css (PACK-LEVEL Google-Fonts @imports, shared),
|
||
categories: [{id,name,order}], templates: [...]}; each template entry is {id, name
|
||
(the display label — the key is `name`, not `label`), category, css (often empty),
|
||
roles: {cover, content, cta}, sampleSlides, sampleHtml}. Role fragments are
|
||
inline-styled HTML with tokens: {{kicker}} {{title}} {{body}} {{subtitle}} {{cta}}
|
||
{{handle}} {{index}} {{total}}. CRITICAL TOKEN MAPPING: cover fragments have NO
|
||
{{body}} — fill {{subtitle}} with the slide's body text; CTA fragments use {{cta}} —
|
||
fill it with the body too. sampleSlides use the key `eyebrow` where your pipeline says
|
||
`kicker` — map it. ID COLLISIONS: a pack id may match a built-in id (templates-pack
|
||
ships `ticker`, same as one of your built-ins above) — the PACK version wins everywhere
|
||
(picker, render); that's intended, not a bug.
|
||
Derive fit_spec budgets for pack templates from their sampleSlides text lengths
|
||
(band = min/max of the samples ±12%). Add three per-slide hygiene passes on load:
|
||
(a) enforce the 28px body floor; (b) re-sequence page counters BAKED into the sample
|
||
markup into live {{index}}/{{total}} tokens — and not just standalone forms ('02',
|
||
'02 / 11', a bare big numeral): counters also hide inside COMPOSITE text nodes
|
||
('CHAPTER · 06/10', '§06 · STEP 1', 'FIG.06', 'Sheet 03 OF 11') — replace the
|
||
number parts inside visible text nodes wherever an NN or NN/MM pattern appears with a
|
||
counter-style prefix (§, FIG., CHAPTER, Sheet, pg., No., Card…), leaving styles and
|
||
attributes untouched; (c) template previews for pack entries render from their own
|
||
sampleSlides (mapped as above) so the picker shows each design exactly as designed.
|
||
NOTE the pack fonts load from Google Fonts at render time — the render machine needs
|
||
internet, or every pack template falls back to system fonts with different metrics.
|
||
After wiring, render.TEMPLATES/TEMPLATE_META must list the built-ins PLUS all packs'
|
||
templates with correct categories (the /api/templates route arrives with server v2).
|
||
THE REELS PACK IS DIFFERENT ON PURPOSE: its 11 designs are natively 1080×1920 (9:16,
|
||
category `reels`, shown first as "Reels · 9:16"). Render them exactly as-is —
|
||
never squeeze them to 1080×1350; the exporter screenshots each .slide at its own size,
|
||
so the PNGs simply come out 1080×1920. They exist for REELS (the motion add-on films
|
||
them full-frame with no re-render); posting one as a static carousel gets cropped by
|
||
Instagram, which is expected.
|
||
|
||
Test: render a 3-slide dummy carousel (hook/content/cta) in each built-in template to a
|
||
temp dir; confirm PNGs for all, and that a deliberately way-too-long title triggers an
|
||
OVERFLOW-FIX instead of clipping. Show me one rendered PNG path per template.
|
||
```
|
||
|
||
## Prompt 23
|
||
|
||
### Prompt 23 — The Pipeline, Part 1: Talking to the Crew
|
||
|
||
```
|
||
Start ~/cadence-dashboard/pipeline.py — the brain. Python stdlib only.
|
||
|
||
agent_run(profile, prompt, timeout=200):
|
||
Runs one specialist headlessly and returns its reply as a string:
|
||
HERMES_HOME=~/.hermes/profiles/<profile> <hermes-python> -m hermes_cli.main -z PROMPT --cli
|
||
where <hermes-python> is Path.home()/".hermes/hermes-agent/venv/bin/python" (NEVER a
|
||
hardcoded /home/<user> path — use Path.home() so it works on any machine).
|
||
Capture stdout, strip ANSI codes and any CLI banner noise, return the text. On timeout
|
||
or failure return "" and store.log_run the failure.
|
||
|
||
extract_json(text):
|
||
Agents wrap JSON in prose or code fences. Find and parse the first valid JSON object or
|
||
array in the text (try fenced blocks first, then brace matching). Return the parsed
|
||
value or None. Never raise.
|
||
|
||
friendly_error(raw):
|
||
If the raw agent output looks like a rate-limit / usage-cap error, return a human
|
||
message like "AI usage limit reached — try again in a bit."; else None.
|
||
|
||
Also add _secret(name): read KEY=value lines from ~/.km-secrets/secrets.env (chmod 600)
|
||
and return the value or "" — all API keys live there, never in code. And create the safe
|
||
way IN: a helper ~/cadence-dashboard/cadence-secret-set.sh <NAME> that reads the value
|
||
with hidden input (read -r -s), upserts NAME=value into ~/.km-secrets/secrets.env, and
|
||
chmods 600. RULE for every later prompt: when a key is needed (Buffer, ImgBB,
|
||
Perplexity, fal.ai), NEVER ask me to paste it into the chat — tell me to run the helper
|
||
in my terminal and to reply "done"; then verify the key exists without printing it.
|
||
|
||
One more thing: SEED THE BRAND. The settings store (store.get_settings()) has brand
|
||
fields — brand name, @handle, niche sentence, voice notes. Fill them NOW from your own
|
||
long-term memory (you learned my brand at crew setup) so every prompt the pipeline ever
|
||
builds is on-brand from the very first render — never generic, never someone else's
|
||
example brand. Show me the four values you seeded and let me correct any of them. I can
|
||
edit them later in the Configuration tab.
|
||
|
||
Test: agent_run("scout", "Reply with exactly: PONG") returns PONG, and
|
||
extract_json('noise {"a":[1,2]} noise') returns the dict. Show both results.
|
||
```
|
||
|
||
## Prompt 24
|
||
|
||
### Prompt 24 — The Pipeline, Part 2: Ideas & the Writer
|
||
|
||
```
|
||
Extend pipeline.py with the content generation. These prompt rules are the product — keep
|
||
them exactly.
|
||
|
||
WRITING RULES (module constants, injected into every writing prompt):
|
||
_PLAIN — "Write in plain, simple language: short words, short sentences. No hype, no
|
||
buzzwords. Explain like you'd tell a smart friend."
|
||
_AVOID_AI — ban the AI-tell filler: delve, unleash, elevate, game-changer, seamless,
|
||
robust, leverage, revolutionize, "in today's fast-paced world".
|
||
VOICE_RULES — two NON-NEGOTIABLES appended to every writing prompt: (1) NEVER use an
|
||
em dash (—) or double hyphen (--) anywhere — titles, bodies, caption; use a full stop
|
||
or comma (em dashes are the #1 AI-written tell). (2) The LAST slide's CTA must hand the
|
||
reader ONE concrete next step they can do tonight (a file to create, a command to run,
|
||
a first action) — never a bare "follow for more"; the @handle rides on the concrete
|
||
step, never replaces it.
|
||
AND ENFORCE (1) DETERMINISTICALLY: extract_json runs every parsed string through a
|
||
_no_dash scrub that converts any em/en dash or " -- " to a comma or full stop. The
|
||
prompt asks; the scrub guarantees — a model that disobeys still can't get a dash into
|
||
a slide.
|
||
_JSON_SPEC — the exact output shape Vera must return, JSON only, no prose:
|
||
{"slides":[{"kicker","title","body"},...], "caption": "...", "hashtags":["#...",...]}
|
||
caption ends asking for a save or a send + follow @[my handle];
|
||
hashtags: 3–5 specific niche tags.
|
||
|
||
generate_carousel(topic, angle="", n=7, feedback=None, prev=None, source=""):
|
||
ONE call to Vera (profile scribe) that writes the whole carousel. The prompt must
|
||
include, in this order: who she's writing for (my brand + niche from
|
||
store.get_settings()); the topic + angle; the slide plan (n slides: slide 1 = COVER,
|
||
middle = one concrete teachable point each, last = CTA); the COVER RULE — "SLIDE 1 IS
|
||
THE COVER, NOT THE FIRST TIP: keywords the audience would search + a promise + curiosity.
|
||
It announces what the whole carousel is about; teaching starts on slide 2"; _PLAIN;
|
||
_AVOID_AI; _JSON_SPEC; and, when refining, the previous slides (prev) + my feedback
|
||
verbatim. Parse with extract_json; return (data, raw).
|
||
|
||
generate_ideas(n=4):
|
||
Atlas (profile scout) proposes n fresh carousel ideas for MY brand as a JSON array:
|
||
{title, angle, format:"Carousel · 7 slides", rationale}. Include an ALREADY-COVERED
|
||
block listing existing idea titles from the store so he never repeats. Only real,
|
||
well-known tools/facts. Insert results via store.add_idea, log the run.
|
||
|
||
polish_idea(raw):
|
||
Atlas turns MY rough one-liner into one polished idea (same JSON shape, faithful to what
|
||
I meant), saved via add_idea with source "you".
|
||
|
||
write_copy(idea_id, draft_id=None, feedback=None, count=None, source="", research=False,
|
||
steroid=False):
|
||
The promote entry point: create (or reuse) the draft with status 'writing', flip the
|
||
IDEA's status to 'promoted', call the right generator, then store slides + caption +
|
||
hashtags via replace_slides/set_draft and flip the draft to 'review'. On unparseable
|
||
output: status 'error' with a friendly message (use friendly_error). Log every step to
|
||
runs as the right agent (scribe writes, scout researches).
|
||
THE TWO TOGGLES (define them exactly — the UI wires them later):
|
||
research=True → BEFORE writing, run one real web-research pass for the topic (a
|
||
Perplexity "sonar" call via _secret("PERPLEXITY_API_KEY") when present) and inject
|
||
the resulting source brief into Vera's prompt as her SOURCE OF TRUTH; save the brief
|
||
on the draft so refines reuse it without re-paying. No key → skip silently.
|
||
steroid=True → use generate_carousel_steroid instead: same JSON spec, but an advanced
|
||
persuasion arc (pain → agitate → solve, open loops between slides, save+send CTA)
|
||
PLUS a second pass where Vera critiques her own draft as a ruthless scroller and
|
||
rewrites it. Two agent calls instead of one.
|
||
|
||
fit_draft(draft_id, template):
|
||
THE APPROVED COPY IS LOCKED — this step must never rewrite it. Measure every field
|
||
against render.fit_spec(template). Fields inside their budget (and short ones) pass
|
||
through byte-for-byte untouched — never pad, never "improve". ONLY fields OVER their
|
||
max go to Vera, alone, with a cut-words-only instruction: shorten this exact text by
|
||
dropping filler and merging clauses; keep the meaning, details, and voice identical —
|
||
the result must read as the same sentence with fewer words. Merge her shortened fields
|
||
back into the otherwise-untouched deck. GUARD, in two stages: any field she returns
|
||
still over its max gets ONE stricter retry ("cut harder — must land at or under
|
||
max_chars; dropping a clause is allowed, changing meaning is not"); if it is STILL
|
||
over after that, hard-trim it at a word boundary to max. The end state is guaranteed:
|
||
every field within budget, so every slide renders at the DESIGNED font size — the
|
||
auto-shrink stays a rare backstop, never the normal path. The deck keeps the SAME
|
||
number of slides, always. Save via replace_slides, set draft.template, status 'fitted'.
|
||
WHY so strict: the first draft the owner approved is the best version — every LLM
|
||
rewrite pass paraphrases and degrades it. Fitting is subtraction, never rewriting.
|
||
|
||
render_draft(draft_id):
|
||
Kite's step: status 'designing' → render.render_carousel(slides, template,
|
||
~/cadence-dashboard/drafts/<id>/) → on success status 'ready' (BUT: if the draft was
|
||
already published/scheduled/drafted, KEEP that status — a re-render must never
|
||
silently unpublish). If fixes came back, set a friendly note on the draft: "✎ Kite:
|
||
text overflow detected — resized … to fit the frame." Log as dev.
|
||
|
||
Test WITHOUT spending much: run write_copy on one quick test idea with count=3, show me
|
||
the three slides' titles from the store, then fit_draft to 'editorial' and render_draft,
|
||
and confirm drafts/<id>/slide-01..03.png exist. Then delete the test idea (cascades).
|
||
```
|
||
|
||
## Prompt 25
|
||
|
||
### Prompt 25 — The Pipeline, Part 3: Publishing Through Buffer
|
||
|
||
```
|
||
Extend pipeline.py with publishing. All HTTP via curl subprocess (stdlib only). Secrets
|
||
via _secret(): BUFFER_ACCESS_TOKEN, BUFFER_ORG_ID, IMGBB_API_KEY.
|
||
|
||
_channels(force=False):
|
||
Buffer's GraphQL API (https://api.buffer.com, Authorization: Bearer <token>):
|
||
query { channels(input:{organizationId:"<org>"}) { id service name isDisconnected } }
|
||
Return connected channels as [{id, service, name}]. Cache ~60s. IMPORTANT: this NEEDS
|
||
BUFFER_ORG_ID — if it's missing, auto-discover it once: query
|
||
{ account { organizations { id } } }, prefer the organization that has channels, and
|
||
save it to the secrets file. (Skipping this is the #1 cause of "No buffer accounts
|
||
connected" bugs.)
|
||
|
||
_imgbb_upload(path): POST https://api.imgbb.com/1/upload?key=<IMGBB_API_KEY> with the
|
||
file base64-encoded in the form field "image"; return data.url from the JSON ('' on
|
||
failure).
|
||
|
||
publish_draft(draft_id, mode="draft", when=None, media="carousel", channels=None):
|
||
Guard: draft must be status 'ready' (or already published for a re-push) with rendered
|
||
slides. Upload every slide PNG once via ImgBB, then for each selected channel create a
|
||
Buffer post via the GraphQL mutation
|
||
mutation($input: CreatePostInput!){ createPost(input:$input){ ... on PostActionSuccess
|
||
{ post { id } } } }
|
||
with variables.input = { channelId, text (caption + hashtags), assets
|
||
[{image:{url}},...], schedulingType "automatic", metadata (per-platform, below), and the
|
||
scheduling fields for the mode — EXACT names, don't guess: mode 'now' → mode:"shareNow";
|
||
'schedule' → mode:"customScheduled" + dueAt:<when>; 'queue' → mode:"addToQueue";
|
||
'draft' → mode:"addToQueue" + saveToDraft:true (mode is required even for drafts). }. Platform metadata (instagram: type "post", shouldShareToFeed;
|
||
tiktok: title for photo posts). IMPORTANT: never set isAiGenerated — that flag makes
|
||
Instagram/TikTok stamp an "AI info" label on every post, which reads as spam and kills
|
||
reach. It exists for realistic synthetic media (deepfakes), not designed carousels.
|
||
TWO SPAM-FILTER GUARDS (Instagram flags API posts that look bot-made): normalize every
|
||
hashtag to exactly ONE leading '#' at publish time — bare words jammed at the caption's
|
||
end read as bot spam; and REFUSE to publish with an empty caption (clear error: "write
|
||
a caption first") — caption-less image posts are a textbook Instagram spam trigger.
|
||
And scheduling —
|
||
mode 'now' = share immediately, 'schedule' = dueAt from `when`, 'queue' = add to queue,
|
||
'draft' = saveToDraft. Store all created post ids comma-joined in draft.buffer_post_id,
|
||
status → published/scheduled/drafted, published_at. Any Buffer rejection → restore the
|
||
previous status + a clear error on the draft. Log as reach.
|
||
|
||
sync_buffer_posts():
|
||
For every draft with a buffer_post_id, fetch each post's real state:
|
||
query { post(input:{id:"..."}) { status sentAt dueAt channel{service}
|
||
metricsUpdatedAt metrics{name value unit} } }
|
||
Save per-draft: delivery = JSON list of {service,status,sentAt,dueAt,metrics};
|
||
metrics = summed totals across platforms. This is the delivery TRUTH — a post can
|
||
silently fail on one platform; the dashboard shows it from here. (Add `delivery` and
|
||
`metrics` TEXT columns to drafts via a store migration.)
|
||
|
||
If BUFFER_ACCESS_TOKEN is missing, every publish path must return a clear
|
||
"connect Buffer first" error — never a crash.
|
||
|
||
Test cheaply: with no token present, confirm publish_draft returns the friendly error.
|
||
If I've already added my token, run _channels() and show me my connected channels — do
|
||
NOT create any post yet.
|
||
```
|
||
|
||
## Prompt 26
|
||
|
||
### Prompt 26 — Store Your API Keys
|
||
|
||
```
|
||
for N in BUFFER_ACCESS_TOKEN IMGBB_API_KEY PERPLEXITY_API_KEY; do printf 'Now paste your %s and press Enter - first 4 and last 4 characters will show, the middle stays masked (or press Enter alone to skip): ' "$N"; V=""; while IFS= read -r -s -n1 c; do [ -z "$c" ] && break; V="$V$c"; if [ ${#V} -le 4 ]; then printf '%s' "$c"; else printf 'x'; fi; done; echo; if [ -n "$V" ]; then if [ ${#V} -gt 8 ]; then M=$(printf '%*s' $((${#V}-8)) '' | tr ' ' 'x'); printf ' %s%s%s\n' "${V:0:4}" "$M" "${V: -4}"; fi; mkdir -p ~/.km-secrets; touch ~/.km-secrets/secrets.env; grep -v "^$N=" ~/.km-secrets/secrets.env > ~/.km-secrets/.upd 2>/dev/null; printf '%s=%s\n' "$N" "$V" >> ~/.km-secrets/.upd; mv ~/.km-secrets/.upd ~/.km-secrets/secrets.env; chmod 600 ~/.km-secrets/secrets.env; echo " received ${#V} characters - saved."; else echo " skipped."; fi; unset V c M; done; echo all done
|
||
```
|
||
|
||
## Prompt 27
|
||
|
||
### Prompt 27 — Smoke-Test the Keys
|
||
|
||
```
|
||
I've just stored my API keys securely in the secret store from my terminal — some or all
|
||
of BUFFER_ACCESS_TOKEN, IMGBB_API_KEY, PERPLEXITY_API_KEY (I may have skipped ones I
|
||
don't have yet). Before we build any further, smoke-test each one — I don't want to
|
||
build on top of a dead key. For each of the three (via _secret()):
|
||
|
||
- Missing/empty → report "skipped" and move on; that is a valid choice, not a failure.
|
||
- BUFFER: call get_channels(). Success = my connected channels listed by name. This also
|
||
runs the one-time BUFFER_ORG_ID auto-discovery and saves it — tell me if it did.
|
||
- IMGBB: upload a tiny generated 1-pixel PNG via _imgbb_upload() and confirm you got a
|
||
URL back (paste the URL — it's harmless).
|
||
- PERPLEXITY: send a minimal query ("Reply OK") and confirm a response arrives.
|
||
|
||
Nothing gets posted, scheduled, or drafted anywhere — read-only checks and one throwaway
|
||
image upload only. NEVER print any key value or any part of one.
|
||
|
||
Then give me a verdict table: ✅ works / ⏭ skipped / ❌ failed — and for every ❌, what
|
||
the error was and the likely fix (wrong key, missing permission, or rerun the storing
|
||
command from the previous card to re-enter it). If Buffer works but lists zero channels,
|
||
say so explicitly — that means no social accounts are connected inside Buffer itself.
|
||
```
|
||
|
||
## Prompt 28
|
||
|
||
### Prompt 28 — server.py v2 — The Content API
|
||
|
||
```
|
||
Extend ~/cadence-dashboard/server.py (your v1 from Prompt 10) with the CONTENT API.
|
||
|
||
Background jobs: a tiny _bg(fn, *args) helper that runs pipeline calls in daemon threads —
|
||
generation must never block a request. Track in-flight state in a JOBS dict.
|
||
|
||
THE API CONTRACT — this is the most important rule of the whole build:
|
||
The design template you installed in Prompt 12 is not a static mock — its JavaScript is
|
||
FULLY WIRED to a fixed set of endpoints and response shapes. Your server must satisfy THAT
|
||
contract exactly, or the dashboard will show its mock demo numbers forever while every
|
||
request silently 404s. The template file IS the specification: extract every '/api/…' reference from it and
|
||
every field its JS reads from each response, and make your server match. Build these routes now (names are exact — note some are
|
||
/api/ideas/... plural):
|
||
|
||
GET /api/ideas — returns {ideas: [...], generating: bool, polishing: bool}
|
||
(an object, NOT a bare array — the job flags drive the UI's
|
||
working states).
|
||
POST /api/ideas/generate {n} · POST /api/ideas/custom {text} · POST /api/ideas/delete?id=
|
||
POST /api/ideas/dismiss?id= · POST /api/ideas/update?id= {fields} ·
|
||
POST /api/ideas/regenerate?id= (re-polish an idea)
|
||
POST /api/ideas/draft?id= {count, research, steroid} — _bg write_copy → {draft_id}.
|
||
GET /api/draft?id= — full draft view: fields, slides, images as mtime-versioned
|
||
URLs (/drafts/<id>/slide-01.png?v=<mtime> — busts the browser
|
||
cache on re-render), budget (fit_spec for its template),
|
||
parsed delivery/metrics.
|
||
GET /api/drafts — {drafts: [...]} with cover thumbnails + status.
|
||
POST /api/drafts/delete {draft} — discard a draft.
|
||
POST /api/draft/approve {draft, template} — legacy one-shot: fit + render together.
|
||
POST /api/draft/fit {draft, template} · POST /api/draft/render {draft} ·
|
||
POST /api/draft/refine {draft, feedback} ·
|
||
POST /api/draft/regenerate {draft, idx, kicker?, title?, body?} — edit one slide's text
|
||
then re-render (the per-slide editor).
|
||
POST /api/draft/meta {draft, caption, hashtags} · POST /api/draft/cancel {draft} ·
|
||
POST /api/draft/unpublish {draft} — back to 'ready' so a published post reopens for
|
||
editing (local only — never touches Buffer).
|
||
GET /api/templates — {templates: [{id, label, sub, category, blurb, swatches,
|
||
previews: [url, url, url]}], categories: [{id, name, order}]}
|
||
— previews is an ARRAY per template.
|
||
GET /api/channels — {channels: pipeline._channels()}.
|
||
POST /api/publish {draft, mode, when, media, channels} — _bg publish_draft.
|
||
POST /api/published/sync — sync_buffer_posts inline.
|
||
GET /api/calendar — {events:[...]}: per-platform delivery (sentAt/dueAt/status)
|
||
when synced, else published_at + status.
|
||
GET /api/settings — store.get_settings() + integrations status BOOLEANS (buffer =
|
||
actual connected channels exist, buffer_token, imgbb,
|
||
perplexity — never the secret values).
|
||
POST /api/settings — save allowed settings keys.
|
||
GET /api/draft/zip?id= — all slide PNGs + caption.txt as a ZIP download.
|
||
POST /api/draft/duplicate {draft} — a fully INDEPENDENT copy: new draft + slide rows,
|
||
copy the drafts/<id>/ dir, clear buffer/publish state; note
|
||
where it came from. Editing the copy must never touch the
|
||
original (and vice versa).
|
||
POST /api/draft/media/delete {draft, what: "motion"|"reel"|"renders"} — delete ONE
|
||
generated section: motion = clips+animated reel (carousel
|
||
stays); reel = the reel video; renders = slide PNGs +
|
||
carousel html + any motion, draft back to status 'review'
|
||
with a note that the COPY is safe — but refuse for
|
||
published/scheduled/drafted drafts ("bring it back to the
|
||
Studio first").
|
||
POST /api/draft/tweaks {draft, tweaks} — the Fine-tune editor's save (spec in the
|
||
Studio prompt): persist tweaks.json in the draft dir,
|
||
re-inject the applier script into the EXISTING carousel.html
|
||
(never rebuilt from the template — imported decks keep their
|
||
markup), re-export the PNGs. Empty tweaks = remove the block.
|
||
AGENT API (lets ANY external agent create posts by template name — self-documenting):
|
||
GET /api/agent — discovery JSON describing these endpoints.
|
||
GET /api/agent/templates — compact list {id, label, category, blurb}.
|
||
GET /api/agent/template?name= — FUZZY match by id or label; returns the template's
|
||
character budgets (fit_spec + a ready budget_text prompt
|
||
block), sampleSlides for tone, the writing rules, and the
|
||
exact submit spec.
|
||
POST /api/agent/draft {title, template, slides:[{kicker,title,body}], caption?,
|
||
hashtags?} — validate, create the draft, render in the
|
||
background, park it in the Studio for HUMAN review with a
|
||
clear "submitted by an external agent" note. Agents can
|
||
never publish.
|
||
GET /api/agent/status?id= — minimal poll: {status, error, slides_rendered}.
|
||
AUTH: if ~/.km-secrets/cadence-agent-key exists, every /api/agent/* call must present
|
||
it (X-Cadence-Key header or ?key=) or get a 401; no file = open on the trusted LAN.
|
||
|
||
Template previews: on startup, in a background thread, render a 3-slide sample per
|
||
template into ~/cadence-dashboard/previews/<template>/ (skip ones already rendered).
|
||
|
||
On startup also run store.init() and reap stuck drafts: any draft left in
|
||
writing/fitting/designing from a dead process → status 'error' with a friendly
|
||
"interrupted — promote again" message.
|
||
|
||
Restart it, then confirm: /api/ideas returns the {ideas, generating, polishing} object,
|
||
and /api/templates lists the built-ins PLUS every pack template in the
|
||
{templates, categories} shape with preview URLs filling in as the background render runs.
|
||
```
|
||
|
||
## Prompt 29
|
||
|
||
### Prompt 29 — Wire the Ideas Tab & the Promote Flow
|
||
|
||
```
|
||
Back up first. IMPORTANT REFRAME for this and every wiring prompt: the template's
|
||
JavaScript already contains each tab's full front-end logic — your job is NOT to write
|
||
UI from scratch; it is to (a) make your SERVER satisfy exactly what the template's JS
|
||
calls and reads (extract the routes + fields for this tab from the template source and
|
||
reconcile your Prompt-20 server where they differ), (b) verify the tab live end to end,
|
||
and (c) delete the tab's demo/mock data so only real content shows. Wire the Ideas tab
|
||
and the PROMOTE flow — the heart of the studio — this way now. (Remember /api/ideas
|
||
returns {ideas, generating, polishing}, not a bare list.)
|
||
|
||
IDEAS TAB: cards from GET /api/ideas (title, angle, rationale, source tag, status chip);
|
||
a "Generate ideas" button → POST /api/ideas/generate (show a working state while the
|
||
`generating` flag returned BY GET /api/ideas is on — the job flags ride on /api/ideas,
|
||
not /api/state); an input to submit my own rough idea → POST /api/ideas/custom; delete
|
||
with confirm.
|
||
|
||
PROMOTE FLOW (a review panel that opens inline under the idea card):
|
||
Step 1 — slide-count chooser (3–10, default from settings.default_slides) with two
|
||
toggles wired for later: research (off) and steroid (off). Confirm starts
|
||
POST /api/ideas/draft → poll GET /api/draft?id= every ~2s.
|
||
Step 2 — REVIEW: when status hits 'review', show every slide's kicker/title/body as
|
||
text (no images yet — no render cost until the words are right), plus caption +
|
||
hashtags. Buttons: "Refine" (a feedback box → POST /api/draft/refine → back to
|
||
polling), "Approve → pick template".
|
||
Step 3 — TEMPLATE PICKER: grid of every template from /api/templates (preview image,
|
||
label, category chip, palette dots), grouped by category. Selecting one shows
|
||
"Fit copy to this →" (then preview & render) — POST /api/draft/fit.
|
||
Step 4 — FIT PREVIEW: when status hits 'fitted', show the REWRITTEN copy (it was
|
||
adapted to this template's character budgets) with a note saying exactly that, and two
|
||
buttons: "← Change template" and "Render this →" → POST /api/draft/render.
|
||
Step 5 — RENDERING: progress state until status 'ready', then close the panel and
|
||
toast that it's in the Studio.
|
||
Handle status 'error' at every step with the draft's error message and a retry path.
|
||
RESUME RULE: reopening an unfinished draft later must drop back into the RIGHT step
|
||
(fitted → the fit preview, review → review), never restart from scratch. AND it must
|
||
survive a page reload: the panel's state lives only in the page, but the DRAFT on the
|
||
server is the source of truth — when the Ideas tab loads and finds an in-flight
|
||
interactive draft (status writing/review/fitting/fitted/designing/error, not a batch
|
||
job), REBUILD the panel from the draft's server state automatically and toast
|
||
"picked up where you left off". Leaving the page must never strand a write.
|
||
|
||
Confirm by promoting ONE tiny real idea (3 slides) end to end and telling me where it
|
||
landed.
|
||
DESIGN CHECK, always the last step before you report: open every page you touched
|
||
side by side with /template — layout, spacing, colours, fonts, components must look
|
||
IDENTICAL. Fix any drift until you cannot tell them apart. Then bump the badge.
|
||
```
|
||
|
||
## Prompt 30
|
||
|
||
### Prompt 30 — Wire the Studio Tab
|
||
|
||
```
|
||
Back up first. Wire the Studio tab. Delete its demo content as you go.
|
||
|
||
GALLERY: every non-published draft as a card (cover thumbnail, title, status chip,
|
||
template name, slide count). An "Unfinished" strip on top for drafts stuck before render
|
||
(status review/fitted/error) with Resume → (drops into the right promote step) and
|
||
Discard. Click a rendered card → detail view.
|
||
|
||
DETAIL VIEW (the important one):
|
||
- All slide images in a grid (mtime-versioned URLs so re-renders show instantly),
|
||
click any slide for a full-size lightbox with prev/next.
|
||
- EDIT TEXT on every slide: opens inline inputs for that slide's kicker, title, AND
|
||
body — everything the agents wrote — with live character counters coloured against
|
||
the template's fit budget (green in band, amber out). Save → POST
|
||
/api/draft/regenerate → the slide re-renders with the new text (takes a few seconds
|
||
to ~a minute depending on the machine) and the image refreshes in place.
|
||
- "Change template" → the same template picker; on confirm, fit + re-render with the
|
||
new design. The images must visibly update (cache-busted), never show stale renders.
|
||
- Caption & hashtags panel with an Edit → textarea + save via /api/draft/meta.
|
||
- Download .zip button (GET /api/draft/zip?id=).
|
||
- A friendly note area: when the draft carries a note (like Kite's overflow-resize
|
||
note), show it as a small agent message.
|
||
- The Studio's background refresh must NEVER wipe an open detail view (guard the poll:
|
||
an open detail always wins over the empty-state).
|
||
- LAYOUT DOCTRINE (non-negotiable UX): actions live ON the section of the thing they
|
||
act on, never in one kitchen-sink toolbar. Order: the CAROUSEL section FIRST (slide
|
||
grid + its own header actions: Fine-tune, Change template, Phone preview, Slides
|
||
.zip, Delete render), then Caption, then the Animated-carousel section, then the
|
||
Reel section — each with its own actions in its own header. The page header keeps
|
||
ONLY deck-level things: the publish bar, ⧉ Duplicate, and (when published) a
|
||
"Back to Studio" button. In overlays/lightboxes the primary action sits top-center,
|
||
ANCHORED to the artifact (not the viewport), yellow and impossible to miss.
|
||
- 📱 PHONE PREVIEW: a "see it as your followers will" modal — a phone-shaped frame
|
||
(white card, rounded, phone proportions) rendering the post Instagram-style: header
|
||
row with a small brand avatar circle + @handle (from settings — show exactly ONE @:
|
||
the stored handle already starts with it, so never render "@@brand"), the slide image
|
||
with ‹ › arrows, a "1/7" counter and swipe dots underneath, and the caption text
|
||
scrolling below — so the owner judges the cover and caption exactly as a follower
|
||
sees them before publishing.
|
||
- ⧉ DUPLICATE: POST /api/draft/duplicate → open the independent copy; toast that the
|
||
original is untouched.
|
||
- PER-SECTION ✕ REMOVE: each generated section header gets a small red Remove button →
|
||
POST /api/draft/media/delete with a confirm dialog that states plainly what goes and
|
||
what stays (deleting renders returns the deck to Unfinished with its COPY intact;
|
||
hidden/refused on published decks).
|
||
- THE FINE-TUNE EDITOR (per-slide, per-element control — a headline feature):
|
||
a full-screen modal that loads the draft's REAL carousel.html in a same-origin
|
||
iframe, scaled to fit the modal's measured stage box (never window math — the modal
|
||
is width-capped). Tag every text/image element with a deterministic id (s<slide>-<n>
|
||
in document order — the SAME eligibility rule the motion engine uses), click to
|
||
select (yellow outline), then a control rail: ↔ Horizontal, ↕ Vertical, ⤢ Size, and
|
||
Aa Text size sliders — ALL on one mental model, 100% = the original design (position
|
||
sliders move as % of slide width/height); plus a text box to rewrite the element's
|
||
words. Everything previews live; Reset element / Reset slide restore captured
|
||
originals; slide ◀ ▶ nav; a tweak counter. Save → POST /api/draft/tweaks: the server
|
||
writes tweaks.json, injects a self-applying <script> into carousel.html (captures
|
||
each element's original transform/font/text into dataset attrs, then applies
|
||
translate/scale/font-size/text overrides on load) and re-exports the PNGs — so
|
||
tweaks survive into exports AND into motion clips automatically. render.py rules: a
|
||
FRESH render (new template/fit) clears tweaks.json (element ids no longer match);
|
||
motion variants inherit it. Also add a "🎛 Fine-tune this slide" button in the slide
|
||
LIGHTBOX — top-center, anchored just above the image — that opens the editor on that
|
||
exact slide (studio decks only, never template previews).
|
||
|
||
ALSO WIRE THE SUPPORTING TAB the template ships with (same reframe — satisfy the
|
||
JS, verify, clean demo data):
|
||
TEMPLATES TAB — the gallery of every design from /api/templates: cards with preview
|
||
image(s), name, category chip, blurb, palette dots; grouped by category; a
|
||
cover/storyboard layout toggle; clicking a cover opens its slides in the lightbox.
|
||
|
||
Confirm with the carousel from the previous prompt: edit one slide's title and show it
|
||
re-rendered; change its template and show the images updated.
|
||
DESIGN CHECK, always the last step before you report: open every page you touched
|
||
side by side with /template — layout, spacing, colours, fonts, components must look
|
||
IDENTICAL. Fix any drift until you cannot tell them apart. Then bump the badge.
|
||
```
|
||
|
||
## Prompt 31
|
||
|
||
### Prompt 31 — Wire Publishing, the Published Tab & the Calendar
|
||
|
||
```
|
||
Back up first. Wire publishing end to end. Needs my Buffer token in the secret store —
|
||
if it's missing, wire everything and show the clear "connect Buffer first" errors.
|
||
|
||
PUBLISH BAR (Studio detail, only when status 'ready'):
|
||
- Channel chips from GET /api/channels (Instagram / TikTok) — all selected by default.
|
||
- "Post now" must NOT fire on a stray click: hovering it reveals the real actions —
|
||
Instagram only / TikTok only / Both. Clicking one asks for explicit confirmation
|
||
naming exactly what will be published and where, shows a busy overlay while pushing,
|
||
and ends with an acknowledged dialog ("Pushed to Buffer — it can take a few minutes
|
||
to appear").
|
||
- "Schedule…" opens a datetime row → mode schedule. Also Queue and Save-as-draft.
|
||
- After a successful publish the draft LEAVES the Studio (it lives in Published now).
|
||
|
||
PUBLISHED TAB: archive grid of published/scheduled/drafted carousels — cover, title,
|
||
when, per-platform delivery badges from the sync (Buffer post status values you'll see:
|
||
"sent" ✓, "error" ✗ in red, plus scheduled/queued states shown neutrally — a post can
|
||
fail on ONE platform silently; surface it), engagement metrics when non-zero, a "↻ Sync
|
||
stats from Buffer" button → POST /api/published/sync. Click a card → opens the Studio
|
||
detail (read-only publish bar + a "↩ Back to Studio for editing" button →
|
||
/api/draft/unpublish, with a confirm explaining Buffer is untouched).
|
||
|
||
CALENDAR TAB: a Monday-first month grid from GET /api/calendar — one chip per post per
|
||
platform on its day (sent / scheduled / failed colour-coded), today ringed, prev/today/
|
||
next navigation, a friendly empty-state explaining the calendar fills as you publish, and
|
||
a "Sync with Buffer" button. Chips open the draft.
|
||
|
||
Confirm: with no token, the flows degrade with clear messages; with my token, show my
|
||
connected channels and STOP — do not actually post anything without me.
|
||
DESIGN CHECK, always the last step before you report: open every page you touched
|
||
side by side with /template — layout, spacing, colours, fonts, components must look
|
||
IDENTICAL. Fix any drift until you cannot tell them apart. Then bump the badge.
|
||
```
|
||
|
||
## Prompt 32
|
||
|
||
### Prompt 32 — Wire the Configuration Tab
|
||
|
||
```
|
||
Back up first. Wire the Configuration tab to GET/POST /api/settings.
|
||
|
||
BRAND PANEL: brand name, handle (@-prefixed input), niche sentence, voice notes →
|
||
saved to settings. THE CRITICAL WIRING: pipeline.py must read these settings at PROMPT
|
||
TIME — every idea, slide, caption, CTA, and rendered @handle uses the CURRENT settings,
|
||
never a hardcoded brand. Prove it end to end: change the handle in the UI, render a test
|
||
slide, and the new handle appears on it (then change it back).
|
||
|
||
DEFAULTS PANEL: default slide count (3–10 picker), research-by-default toggle,
|
||
steroid-by-default toggle → these seed the promote panel's initial state.
|
||
|
||
INTEGRATIONS PANEL: status rows for Buffer / ImgBB / Perplexity from /api/settings —
|
||
booleans only, never key values. Buffer's row must reflect REAL connected channels
|
||
(chips: 📸 name, 🎵 name), and when a token exists but no channels are connected, show
|
||
an amber "token set, but no connected accounts" warning — the UI never lies about
|
||
Buffer. Include a note that keys are added on the server via the secret store, not
|
||
through the browser.
|
||
|
||
Confirm with the handle round-trip test.
|
||
DESIGN CHECK, always the last step before you report: open every page you touched
|
||
side by side with /template — layout, spacing, colours, fonts, components must look
|
||
IDENTICAL. Fix any drift until you cannot tell them apart. Then bump the badge.
|
||
```
|
||
|
||
## Prompt 33
|
||
|
||
### Prompt 33 — The Telegram Remote Control
|
||
|
||
```
|
||
Back up first. Add a Telegram remote control for the studio, riding the EXISTING Hermes
|
||
bot. Key fact: only POLLING a bot token conflicts (two getUpdates listeners fight);
|
||
SENDING through it from a second program is fine. So: Hermes keeps receiving messages,
|
||
a Hermes plugin intercepts Cadence commands and forwards them to the dashboard, and the
|
||
dashboard replies through the same token.
|
||
|
||
Our dedicated command channel is the STUDIO topic, already wired as the Orchestrator's
|
||
second lane in Prompt 8e: Studio topic thread id = [STUDIO TOPIC ID — from your 8d
|
||
notes]. Save it as CADENCE_TELEGRAM_THREAD in ~/.km-secrets/secrets.env. The group
|
||
chat id you can read from your own home's config (~/.hermes/profiles/dev/config.yaml
|
||
→ platforms.telegram.group_allowed_chats, wired in 8e) — save it as
|
||
CADENCE_TELEGRAM_CHAT and show me both values so I can confirm them.
|
||
|
||
1. ~/cadence-dashboard/telegram_bot.py (pure stdlib): the command handlers.
|
||
The token resolves from ~/.hermes/.env (TELEGRAM_BOT_TOKEN — the Orchestrator's
|
||
bot). Only CADENCE_TELEGRAM_CHAT is obeyed, and commands are obeyed ONLY in the
|
||
CADENCE_TELEGRAM_THREAD topic — a command typed in any other topic is silently
|
||
ignored. Replies carry message_thread_id so they land in the Studio topic.
|
||
/ideas → pipeline.generate_ideas in a background thread ("thinking…" ack, then a
|
||
numbered list) · /ideas <text> → pipeline.polish_idea on my rough idea
|
||
/list → proposed ideas as "<id> · <title>" · /status → counts + 5 recent drafts
|
||
/promote <n> [template] → validate id + template, "producing…" ack, run the full
|
||
cascade in the background, then send the rendered slides as a photo ALBUM
|
||
(sendMediaGroup, multipart upload of local PNGs, max 10) + "in the Studio for review"
|
||
/preview <n> → a draft's slides as an album · /caption <n> → caption + hashtags text
|
||
/templates · /help. SAFETY: publishing NEVER happens from Telegram. Plain text
|
||
replies, no em dashes.
|
||
|
||
2. server.py: POST /api/telegram/command {chat_id, text, thread_id} — LOCALHOST-ONLY
|
||
(reject non-127.x clients), runs the handler in a background thread, returns
|
||
{ok:true} immediately.
|
||
|
||
3. The Hermes plugin ~/.hermes/plugins/cadence_commands/ (__init__.py + plugin.yaml,
|
||
copy the structure of any existing plugin in that folder): register(ctx) hooks
|
||
pre_gateway_dispatch; on Telegram messages in OUR group's Studio topic whose text
|
||
starts with /ideas /list /promote /status /preview /caption /templates, POST them
|
||
to the bridge endpoint (timeout 3s) and return {"action": "skip"} so the LLM never
|
||
runs for them. Everything else returns None. (The Studio topic is already the
|
||
Orchestrator's lane, so plain messages there keep flowing to him — this plugin only
|
||
peels off the commands.) FAIL-OPEN: if the dashboard is down, return None so the
|
||
normal agent still answers. Enable the plugin in ~/.hermes/config.yaml
|
||
(plugins.enabled) and restart the hermes-gateway service.
|
||
|
||
Then walk me through the test, one step at a time: /status in the Studio topic must
|
||
answer instantly; /status in the Kite topic must stay silent; a plain sentence in
|
||
Studio ("give me one carousel idea") must get an ORCHESTRATOR reply; /ideas, then
|
||
/promote <n> — and the rendered slides must arrive in the Studio topic as a photo
|
||
album.
|
||
|
||
Last step, once every test passes: write me a SHORT briefing message (3–4 sentences,
|
||
ready to copy) that I will post in the ORCHESTRATOR's topic. It should tell him: the
|
||
Studio topic now has a machine remote — messages starting with /ideas /list /promote
|
||
/status /preview /caption /templates are handled by the studio software before they
|
||
ever reach him, so he must never try to answer those; plain conversation in Studio is
|
||
still his; and drafts appearing in the database without his involvement were triggered
|
||
through this remote. Tell him to save that to memory.
|
||
```
|
||
|
||
## Prompt 34
|
||
|
||
### Prompt 34 — Tailscale Access (Dashboard From Anywhere)
|
||
|
||
```
|
||
Set up Tailscale access to the dashboard, so I can reach it securely from my own devices without the SSH tunnel.
|
||
|
||
1. Install Tailscale on this server and give me the login link so I can connect it
|
||
to my Tailscale account. Wait for my "done".
|
||
2. Expose the dashboard over the tailnet ONLY: use `tailscale serve` to put it
|
||
behind HTTPS on the tailnet, proxying to 127.0.0.1:8892.
|
||
HARD RULES: do NOT use `tailscale funnel` (that is public exposure), and do NOT
|
||
change the server bind away from 127.0.0.1. The loopback-only rule from the
|
||
original build stays exactly as it is - Tailscale is a proxy in front of it.
|
||
3. Make it survive reboots (tailscale serve config persists; confirm it does).
|
||
4. Report back: the Tailscale hostname and 100.x IP, the exact HTTPS URL I open on
|
||
my phone, confirmation the dashboard loads over that URL from a tailnet device,
|
||
and confirmation the public IP still serves NOTHING. All four, with evidence.
|
||
```
|
||
|
||
## Prompt 35
|
||
|
||
### Prompt 35 — The Docs Tab
|
||
|
||
```
|
||
Back up first. Fill the Docs tab with a clear, self-contained guide to THIS studio:
|
||
the crew (who does what), the promote flow step by step (count → write → review/refine →
|
||
template → fit preview → render), how fitting to character budgets works and why, the
|
||
publish modes and what each does on Buffer, the delivery-truth sync, the calendar, brand
|
||
configuration, where the data lives (cadence.db, drafts/, the secret store), and a short
|
||
troubleshooting list (agent rate limits, Buffer org id, renderer browser discovery).
|
||
Match the template's design language exactly.
|
||
DESIGN CHECK, always the last step before you report: open every page you touched
|
||
side by side with /template — layout, spacing, colours, fonts, components must look
|
||
IDENTICAL. Fix any drift until you cannot tell them apart. Then bump the badge.
|
||
```
|
||
|
||
## Prompt 36
|
||
|
||
### Prompt 36 — Final Verification & Version Stamp
|
||
|
||
```
|
||
Final check. Run through this list and report each item pass/fail with evidence:
|
||
|
||
1. Fresh reload: EVERY tab renders with ONLY real data — including Templates,
|
||
Calendar, and Configuration — zero demo values anywhere.
|
||
2. Promote one real 3-slide idea end to end: write → review → fit to a PACK template →
|
||
render → appears in Studio with images.
|
||
3. Edit one slide's title in Studio → it re-renders with the new text.
|
||
4. Change that carousel's template → images visibly update to the new design.
|
||
5. /api/templates: built-ins + all three packs present with previews; the picker groups
|
||
by category.
|
||
6. Publish flow: correct behaviour for my Buffer state (clear errors OR real channel
|
||
chips; no post created without my confirmation).
|
||
7. Calendar + Published: consistent with the drafts' real states.
|
||
8. Configuration: handle round-trip proven.
|
||
9. Agents tab heatmap matches the runs table; logging still flowing.
|
||
10. Server survives a restart (stuck drafts reaped, previews intact, no data loss).
|
||
11. DESIGN FIDELITY: walk every tab side by side with /template — the finished dashboard
|
||
must be visually indistinguishable from the original design (layout, spacing,
|
||
colours, fonts, components). Fix any drift you find.
|
||
|
||
Fix anything that fails (backup first), then stamp the version badge v1.0 and give me a
|
||
one-paragraph summary of what my studio can do.
|
||
```
|
||
|
||
## Prompt 37
|
||
|
||
### Prompt 37 — Animated Carousels (Motion)
|
||
|
||
```
|
||
Back up first. Add motion rendering to the studio.
|
||
|
||
Build ~/carousel-templates/animate-slides.js (same findBrowser logic as the exporter):
|
||
takes <carousel.html> <outDir> [--slides=1,3] [--reel=4:5|9:16|none]; for each selected
|
||
.slide, auto-choreograph its REAL markup — every text block and image reveals in reading
|
||
order (fade + rise ~42px, ease-out-cubic, staggered ~0.14s apart, capped at 1.6s) — by
|
||
tagging elements and exposing a window.__seekSlide(idx, time) that positions the
|
||
animation at an exact time. Render each slide frame-by-frame (30 fps, ~2.4s + 1.25s
|
||
hold): seek → clipped screenshot → ffmpeg into motion/clip-NN.mp4 encoded at THE SLIDE'S
|
||
OWN aspect (1080 wide, height from the slide rect — 4:5 decks give 1350, native-9:16
|
||
decks give 1920; yuv420p, faststart). Then the reel: --reel=4:5 → plain concat of the
|
||
clips; --reel=9:16 → re-encode each clip over a blurred cover-fill 1080×1920 background,
|
||
then concat. Write motion/meta.json {ratio, slides} and print MOTION-DONE JSON.
|
||
FOUR HARD-WON ENGINE RULES (each one is a real production bug):
|
||
1) CLEAN SLATE: at start, delete old clip-*.mp4/reel.mp4/meta.json — stale clips from a
|
||
previous run at another ratio must never mix into the new gallery/reel.
|
||
2) FRAMES ON REAL DISK: temp frames live under <outDir>/.frames, NEVER /tmp — a
|
||
10-slide film is ~700 frames and failed runs fill tmpfs until Chromium dies.
|
||
Clean .frames up on failure too.
|
||
3) VIEWPORT ≥ TALLEST SLIDE: with captureBeyondViewport:false, a slide taller than the
|
||
viewport is captured PARTIALLY and then stretched by the encoder (cut bottoms,
|
||
squashed text). Measure max .slide height after load and resize the viewport up.
|
||
4) ROBUST LOAD: ONE goto with waitUntil 'load' (a second goto after a networkidle
|
||
timeout detaches the frame), then a bounded fonts+images settle; freeze any NATIVE
|
||
css animations/transitions with an injected *{animation:none!important} style — the
|
||
seek choreography must be the only motion.
|
||
|
||
pipeline.animate_draft(draft_id, slides=None, reel_ratio="4:5"): run it on the draft's
|
||
carousel.html; ALSO support reel_ratio "9:16-full" — full-frame vertical with NO blur:
|
||
· reflow-capable templates (your flexbox built-ins + AI themes): re-render the deck's
|
||
html at canvas 1080×1920 into carousel-916.html (html-only, never clobber the PNGs)
|
||
and film THAT;
|
||
· fixed-composition pack templates: auto-degrade to the blur 9:16 with a friendly note
|
||
(stretching them strands content at the top);
|
||
· REELS pack templates are ALREADY 1080×1920: film carousel.html as-is, plain-concat
|
||
the reel, keep the 9:16-full label.
|
||
Correct meta.json's ratio afterwards; note the result on the draft, log as dev.
|
||
server.py: POST /api/draft/animate {draft, slides?, ratio?} (background job + an
|
||
"animating" flag), motion info on the draft view: clip URLs + reel URL (mtime-versioned)
|
||
PLUS the meta ratio — the UI must render tiles and the reel player at the RIGHT aspect,
|
||
never hardcode 4:5.
|
||
|
||
Studio detail: a PREMIUM two-step visual modal (drawn diagram cards, minimal text — this
|
||
is a flagship interaction, not a form):
|
||
Step 1 · what to animate: Everything / Pick slides (tap slides in the grid, yellow
|
||
selection rings + count) / Reel only.
|
||
Step 2 · reel format: 4:5 · full-frame 9:16 (disabled with "not for this design — use
|
||
blur" on fixed packs; "this design is native 9:16" on Reels) · blur 9:16.
|
||
When done, a gallery: the stitched reel player + download, each clip as a small looping
|
||
video with per-slide download, a ratio badge, a Re-animate button, and a red ✕ Remove
|
||
(the media/delete endpoint). Fine-tune tweaks (if any) carry into clips automatically —
|
||
the animator films the same carousel.html the tweaks are injected into.
|
||
|
||
Publish integration: when motion exists, the publish flow asks WHICH format to post —
|
||
🖼 static slides / ✨ animated reel (one video) — with plain-language labels, and the
|
||
confirmation names the choice. (Video posting to Buffer needs a video host — if none is
|
||
configured, show a clear message; static publishing is unaffected.)
|
||
|
||
Confirm on an existing rendered draft: animate it, play the reel, and show the format
|
||
question appearing in the publish flow.
|
||
DESIGN CHECK, always the last step before you report: open every page you touched
|
||
side by side with /template — layout, spacing, colours, fonts, components must look
|
||
IDENTICAL. Fix any drift until you cannot tell them apart. Then bump the badge.
|
||
```
|
||
|
||
## Prompt 38
|
||
|
||
### Prompt 38 — AI Imagery Templates (fal.ai)
|
||
|
||
```
|
||
Back up first. Add AI-imagery templates. Everything reads FAL_API_KEY from the secret
|
||
store; without it, picking one of these templates errors clearly and nothing else breaks.
|
||
|
||
THEMES — add four new builtin templates in a new "AI Imagery" category, all sharing one
|
||
slide layout (full-bleed background image + gradient scrim + kicker / title / body /
|
||
@handle + counter footer) but different moods:
|
||
ai-cinema (Dark Cinema) · ivory-studio (bright airy cream) · neon-pop (electric
|
||
purple/cyan gloss) · golden-hour (warm analog film)
|
||
Each theme defines its mood sentence and its text-zone rule (dark themes reserve a
|
||
near-black bottom 40% for text; light themes a bright one). Slides reference their
|
||
artwork at bg/bg-NN.jpg relative to carousel.html.
|
||
|
||
ART PASS — in pipeline.py:
|
||
art_direction(title, slides, mood): ONE Atlas call returning JSON {style, brands
|
||
(max 2 real products with their GitHub org — single word, validated), cover (concept
|
||
line), scenes (one per content slide that VISUALLY ECHOES that slide's specific
|
||
message — a metaphor for its point, not decoration — tinted with a side's accent
|
||
colour when the deck compares two things)}.
|
||
Cover image: generate via an image-edit model that composes with REAL reference
|
||
images (fal-ai/nano-banana/edit): pass the brands' real GitHub avatar URLs so actual
|
||
logos appear (never let a model draw a logo from memory — it will hallucinate), plus
|
||
a STRICT LAYOUT brief reserving the theme's text zones. 4:5 aspect.
|
||
Content images: fal-ai/flux/dev per scene + the shared style suffix + "no text, no
|
||
letters, no logos".
|
||
ensure_ai_backgrounds(draft_id, slides, template): orchestrates all of it, caches
|
||
everything in drafts/<id>/bg/ (re-renders are FREE — only missing images generate),
|
||
saves art.json alongside for reuse.
|
||
render_draft hooks it: AI template → art pass first, then normal render.
|
||
|
||
Template previews: seed each AI theme's picker preview from a bundled sample image so
|
||
the picker looks right without spending anything.
|
||
|
||
Confirm structurally WITHOUT generating images if I haven't added a fal key (clear
|
||
error path); if I have, ask me before generating ONE test deck (~$0.50).
|
||
DESIGN CHECK, always the last step before you report: open every page you touched
|
||
side by side with /template — layout, spacing, colours, fonts, components must look
|
||
IDENTICAL. Fix any drift until you cannot tell them apart. Then bump the badge.
|
||
```
|
||
|
||
## Prompt 39
|
||
|
||
### Prompt 39 — ImgBB Auto-Cleanup (90-Day Expiration)
|
||
|
||
```
|
||
Back up first. One small change in pipeline.py: the ImgBB upload must request
|
||
auto-deletion after 90 days, so uploaded slide images clean themselves up.
|
||
|
||
In _imgbb_upload, add the expiration parameter to the upload call:
|
||
https://api.imgbb.com/1/upload?key=<key>&expiration=7776000
|
||
(7776000 seconds = 90 days. ImgBB accepts 60 up to 15552000.)
|
||
|
||
WHY 90 and not less: Buffer fetches the image AT POSTING TIME. A post scheduled two
|
||
weeks out must still find its image alive — 90 days outlives any realistic schedule.
|
||
Do not make this configurable and do not touch anything else in the function.
|
||
|
||
Prove it: run one test upload through _imgbb_upload and show me the API response —
|
||
it should include the expiration. Then bump the version badge.
|
||
```
|
||
|
||
## Prompt 40
|
||
|
||
### Prompt 40 — Claude Code as Your Dev (Alternative Builder)
|
||
|
||
```
|
||
You are running on the server where I run my Hermes agent setup. My agents have
|
||
already BUILT the first half of a content-automation studio; from now on YOU take over
|
||
the construction (the engines, the content API, the remaining wiring). Before touching
|
||
anything, get your bearings — explore this machine thoroughly:
|
||
|
||
- Hermes lives at ~/.hermes — look at its layout: config.yaml, .env (do NOT print any
|
||
secret values), profiles/ (each subfolder is a persistent agent with its own SOUL.md,
|
||
USER.md, memory and workspace), state.db, skills/.
|
||
- I have already raised a five-agent content crew in Hermes (Part 1 of this tutorial,
|
||
done in Telegram): the Orchestrator plus four specialists on profiles
|
||
scout (ATLAS), scribe (VERA), dev (KITE), reach (ORIN). Confirm those profiles exist
|
||
and read a couple of their SOUL.md files so you know who they are.
|
||
- Hermes may be new to you: READ THE OFFICIAL HERMES DOCS NOW and keep them as your
|
||
reference for the entire build: https://hermes-agent.nousresearch.com/docs
|
||
Study especially how to invoke an agent non-interactively (HERMES_HOME pointing at a
|
||
profile, the -z / --cli flags), because the pipeline you will build calls these agents
|
||
exactly that way. From now on, whenever ANY Hermes question comes up in a later prompt
|
||
(config format, profile layout, CLI behavior, platform settings), consult these docs
|
||
first — never guess about Hermes.
|
||
- Study what the crew already built in ~/cadence-dashboard: store.py, server.py (v1),
|
||
the running dashboard at port 8892 (GET / serves it), the installed design
|
||
index.html + /template, and the three template packs in the project root. Read the
|
||
code — you EXTEND it from here, matching its stdlib-only style; you never rewrite it.
|
||
- Check the basics of this box too: OS, Python 3, Node (Hermes bundles one at
|
||
~/.hermes/node/bin), free disk.
|
||
|
||
Standing rules for the whole build: you BUILD the system (files, servers, wiring); the
|
||
Hermes agents RUN the content work — never impersonate them, never edit their SOUL/USER/
|
||
memory files, never print secrets from .env, and back up any file you are about to
|
||
modify once a backup script exists. SECURITY: the dashboard binds to 127.0.0.1 ONLY and
|
||
is reached through the owner's SSH tunnel — never rebind it to 0.0.0.0, never open
|
||
firewall ports or reverse proxies for it, on any prompt, ever. Work autonomously, verify each step yourself with
|
||
real commands, and report what you actually did.
|
||
|
||
When you are done exploring, give me a short map of the setup as you understand it and
|
||
confirm you are ready to build.
|
||
```
|
||
|
||
## Prompt 41
|
||
|
||
### Prompt 41 — Database Auto-Cleanup (Log Retention)
|
||
|
||
```
|
||
Back up first. Keep cadence.db from growing forever. The runs table logs every agent
|
||
action and piles up endlessly; ideas and drafts are CONTENT and must NEVER be touched
|
||
by any cleanup — this script may only ever delete from runs.
|
||
|
||
Create ~/cadence-dashboard/cleanup-runs.sh (bash + Python stdlib, no pip) that:
|
||
- reads the DB path from CADENCE_DB (default ~/cadence-dashboard/cadence.db),
|
||
- deletes rows in the runs table older than RETENTION_DAYS (set to 30 at the top
|
||
of the script) using the created_at column,
|
||
- touches NOTHING else — no other table, ever,
|
||
- runs VACUUM afterward to actually reclaim the disk space,
|
||
- prints how many rows it deleted and how many remain,
|
||
- is safe to run on a fresh setup (missing db/table = report zero, exit cleanly).
|
||
Make it executable.
|
||
|
||
Schedule it weekly with cron: Sunday 03:00 server time — deliberately BEFORE the
|
||
nightly backup window, so the backup that night captures the freshly compacted
|
||
database. Append to the existing crontab, never overwrite it, and show me the exact
|
||
line you added.
|
||
|
||
Then run it once now as a test and show me the summary output. Bump the version badge.
|
||
```
|
||
|
||
## Prompt 42
|
||
|
||
### Prompt 42 — Renderer Can't Find a Browser
|
||
|
||
```
|
||
The exporter's findBrowser() checks: CADENCE_CHROME env var → ~/.cache/ms-playwright →
|
||
~/.cache/puppeteer → system chrome/chromium. Diagnose in order:
|
||
1. ls ~/.cache/ms-playwright/ — a standard Hermes install puts Chromium here. If empty:
|
||
run `npx playwright install chromium` once (Node from ~/.hermes/node/bin if needed).
|
||
2. If Chromium exists but isn't found, print the exact path and set CADENCE_CHROME to it
|
||
in the server's environment, then restart.
|
||
3. Remember the server runs under systemd/nohup with a minimal PATH — the service
|
||
environment must include ~/.hermes/node/bin so `node` resolves.
|
||
```
|
||
|
||
## Prompt 43
|
||
|
||
### Prompt 43 — Pack Templates Render in the Wrong Fonts
|
||
|
||
```
|
||
The packs' fonts_css loads families from Google Fonts at render time. The RENDER machine
|
||
must have internet when the exporter runs (networkidle0 waits for the fonts); offline,
|
||
every pack template silently falls back to system fonts with different metrics. Check
|
||
connectivity from the server's environment, re-render, and compare against the pack's
|
||
own sampleHtml preview.
|
||
```
|
||
|
||
## Prompt 44
|
||
|
||
### Prompt 44 — "No buffer accounts are connected" (but they are)
|
||
|
||
```
|
||
This is almost always a missing BUFFER_ORG_ID. The channels query REQUIRES the
|
||
organization id — a valid token alone lists nothing. Fix:
|
||
1. Query { account { organizations { id name } } } with the token.
|
||
2. For each org id, query channels(input:{organizationId:...}); pick the one returning
|
||
your channels.
|
||
3. Save it as BUFFER_ORG_ID in the secret store and restart. The Configuration tab must
|
||
report Buffer by REAL channel discovery, not token presence — if it doesn't, rewire
|
||
that check.
|
||
```
|
||
|
||
## Prompt 45
|
||
|
||
### Prompt 45 — Agent Replies Won't Parse ("copy parse failed")
|
||
|
||
```
|
||
Three usual causes, in order of likelihood:
|
||
1. Model rate limit — the raw output contains a usage-cap error, not JSON. Detect it
|
||
(friendly_error) and show "AI usage limit reached — try again in a bit." instead of
|
||
a parse error. Wait or upgrade the model plan.
|
||
2. The agent wrapped JSON in prose/fences and extract_json is too strict — it must try
|
||
fenced blocks first, then brace-match the first valid object anywhere in the text.
|
||
3. The prompt drifted — the _JSON_SPEC block must be the LAST thing in the writing
|
||
prompt, and must say "Return ONLY JSON, no prose, no code fences."
|
||
```
|