Articles/*.md → publish_articles.py → ingest-content (Edge Function) → Supabase `guides` (DRAFT)
you write builds payload holds INGEST_KEY only published=false
│
you flip published=true in Supabase Studio
▼
live on indexalabs.com
The website reads Supabase, never Obsidian. “Publish” = push a note into the guides table, then approve it in Studio. You never touch the database directly or hold the service key.
Important: the 86 existing articles are already live
Every current Articles/ note is already published in Supabase (it was mirrored from there). You do not re-push these. This tool is for new articles you write from now on. (Re-pushing an existing one as a draft can flip it back to published=false and pull it off the site — so the script never sends anything unless you pass --post.)
One-time setup
Store the key locally so it’s never pasted into chat (you likely already have this):
mkdir -p ~/.indexacat > ~/.indexa/ingest.env <<'EOF'INGEST_URL=https://gqkhhfuafflcfkjczsxg.supabase.co/functions/v1/ingest-contentINGEST_KEY=ik_REPLACE_WITH_YOUR_KEYEOFchmod 600 ~/.indexa/ingest.envpip install pyyaml --break-system-packages # the script's only dependency
It prints the function’s JSON response (counts + any skipped/errors).
Review → publish: Supabase Studio → Table editor → guides → find the row → set published = true. Live within ~5 min.
Publish several at once: list multiple filenames, or --all for everything in Articles/.
Go live immediately (skip the Studio step): add --publish to the --post command — use sparingly.
Safe end-to-end test (optional)
To confirm your key works without touching real content, push the throwaway draft from the ingest guide, then delete it in Studio:
source ~/.indexa/ingest.envcurl -s -X POST "$INGEST_URL" -H "content-type: application/json" -H "x-ingest-key: $INGEST_KEY" -d '{ "guides":[{"id":"hello-draft","slug":"hello-draft","title":"Hello Draft","abstract":"test", "sections":[{"id":"intro","title":"Intro","content":"hi"}]}]}' | jq .# check guides table for hello-draft (published=false), then delete the row
New-article frontmatter
publish_articles.py reads these keys (each ## heading in the body becomes a section; the > **Abstract:** blockquote becomes the abstract; wikilinks are flattened to plain text):
---type: kb-articleid: ART-my-new-articledb_guide_id: my-new-article-guide # the guide id in Supabase (omit for a brand-new guide; then `id` is used)slug: my-new-articletitle: "My New Article"subtitle: "One-line hook"health_goal: tissue-repair # must be a known health-goal id, else stored nullread_time: 10published: 2026-06-02last_updated: 2026-06-02products: [bpc-157] # relatedProductIds; unknown ids are skipped, not fatalreferences_raw: ["Author (Year). Title. Journal."]tags_raw: [healing]difficulty_level: intermediate # optionalresearch_type: clinical-research # optional---# My New Article*One-line hook*> **Abstract:** Two-sentence summary shown on the card and at the top of the guide.## 1. OverviewBody in Markdown...## 2. MechanismMore...
Security
INGEST_KEY grants content writes only (no orders/discounts/PII). Keep it in ~/.indexa/ingest.env (chmod 600) — not in git or chat.
Rotate it any time: supabase secrets set INGEST_KEY=ik_$(openssl rand -hex 24), then update the local file.
Everything lands as a draft carrying source.obsidian_path, auditable, and invisible until you publish.