Article Information

Category: Development

Published: June 14, 2026

Author: Chris de Gruijter

Reading Time: 14 min

Tags

Content SystemsAI AutomationWorkflowBrandHiggsfieldAgentsTooling
High-end content production workspace with cameras, lenses and editing monitors

Building a Content Powerhouse: How I Turned One Client Project Into a Cross-Workspace Production System

Published: June 14, 2026

The first time I batched social posts for a client I treated it like a one-off. Sixteen posts for a chiropractic practice in Malaysia, mostly ImageMagick scripts and Higgsfield prompts wired together with bash. It worked well enough to turn a RM 750/month Google Ads engagement into a RM 1,750/month growth package, which got my attention. If a duct-taped Friday afternoon could do that, what would a proper system do?

This post is the build log for what that proper system turned into. I called it the Content Powerhouse — partly tongue-in-cheek, partly because that's genuinely what it has become. It sits at /Projects/Content on my machine, it serves every Webfluentia client, my personal projects and my Byte24 freelance clients from the same toolchain, and it has reshaped how I think about content production more than any single piece of AI tooling I've adopted in the last two years.

Why a system, not a folder

Most freelancers and small agencies I know run content production as a per-client scramble. Photoshop file here, Canva template there, a folder on Drive named "Final final v3", a captions doc in someone's inbox. It works until you have five clients posting four times a week and you cannot remember which brand uses which font in which weight, never mind which photos you are licensed to use across which accounts. I have been there. It scales until it suddenly doesn't.

The thing I wanted was not "an AI tool that posts for me". I wanted a repeatable production pipeline where the rules of each brand live in code, the assets live somewhere durable, and the decisions a content person actually makes — what to say, which photo to use, whether a generated image is allowed in this industry — happen in a place I can review and version. AI helps inside the pipeline. It is not the pipeline.

So I started with a few non-negotiables before I wrote any code:

  • Cross-workspace. The same tools have to work for agency clients, my own projects and my freelance employer.
  • Brand-driven. Every visual decision flows from a single source of truth per client — palette, typography, voice, logos, integrity rules — not from my memory.
  • Integrity-safe. Healthcare, trades and regulated industries have hard rules about AI-generated faces and fake testimonials. The pipeline has to refuse the unsafe option, not rely on me catching it.
  • Replicable on the laptop. If I cannot rebuild it from git after a clean install, it does not exist.

The shape of the repo

The whole thing lives in one repo at /Projects/Content. The top-level layout is boring on purpose:

/Projects/Content/
├── tools/                # shared bash generation scripts
│   ├── build-social.sh
│   ├── build-title-variant.sh
│   ├── build-real.sh
│   └── lib.sh
├── shared/
│   ├── fonts/            # decompressed brand TTFs
│   └── templates/        # reusable layout primitives
├── docs/                 # workflow, pricing, integrity, setup
├── webfluentia/          # one folder per agency client
│   ├── kindchiro/
│   ├── gevelpro/
│   └── ...
├── personal/
│   ├── waubooks/
│   └── amp/
└── byte24/
    └── unwind/

Three workspaces, one toolchain. Each per-client folder is a tiny self-contained world: a brand.env, a prompts/ directory and a posts/<YYYY-MM>/ folder with the month's briefs, captions and schedule. That's it for the code side. The actual binaries — photos, AI generations, finished webp deliverables — do not live here at all.

Why the visual library lives on Drive, not in git

This was the design decision I went back and forth on for the longest. Putting every image in the repo is tempting because it makes the whole thing self-contained. It is also the wrong answer the moment you have more than a handful of clients producing content monthly. A single 20-post batch is hundreds of megabytes of intermediate webp files, raw Higgsfield generations and source photography. Git is not a CDN.

So visuals live on Google Drive at /mnt/shared-data/Google Drive Sync/Content/, and the per-client folders expose them through symlinks:

  • assets/own/ — the client's own photography
  • assets/stock/ — per-client licensed stock
  • assets/ai/ — prior Higgsfield generations, reused before re-spending credits
  • assets-shared/<lib>/ — shared libraries gated by a USAGE.md file that lists which clients are allowed to pull from them
  • raw/ — per-batch working files
  • outputs/ — finished deliverables, the things that actually ship

The .gitignore blocks git from ever traversing into the Drive paths. The repo stays under a few megabytes; the visual library can grow without limit. Drive gives me offsite backup, cross-device sync and a version history I did not have to build. And if I ever rm -rf /Projects in a moment of madness, the library survives.

There is one rule that goes with this model and matters more than the rest: source-selection priority. Whenever a piece of imagery is needed, the agents walk a ladder in order — client's own photos, then licensed stock, then prior AI generations, then shared libraries (if licensed for the current client), and only then a fresh Higgsfield call. The point is to spend AI credits as a last resort, not a first instinct. Reusing is free; generating is not.

brand.env: a contract, not a config

The single most important file per client is brand.env. It is a plain shell-sourced env file, committed to git, and every script and every agent reads it. Think of it as a contract: if you set these variables correctly, the pipeline will produce on-brand output. If you forget a variable, the scripts fail loudly instead of guessing.

# kindchiro/brand.env (excerpt)
CLIENT_ID="kindchiro"
CLIENT_NAME="Kind Chiropractic"
TAGLINE="Gentle chiropractic for the whole family"
DOMAIN="kindchiropracticmy.com"
LOCATION="Petaling Jaya, Malaysia"
INDUSTRY="healthcare"
VOICE="warm, reassuring, plain-language"

PRIMARY="#1A6B6B"
SECONDARY="#F4E6C1"
ACCENT="#D97757"
BG="#FFFFFF"
BG_ALT="#FAF5EC"

FONT_HEADING="shared/fonts/PlayfairDisplay-Bold.ttf"
FONT_BODY="shared/fonts/Inter-Regular.ttf"
FONT_BODY_BOLD="shared/fonts/Inter-Bold.ttf"

LOGO_DARK="/home/cjvdeg/Projects/Webfluentia/kindchiro-nuxt-wf/public/logo-dark.svg"
LOGO_LIGHT="/home/cjvdeg/Projects/Webfluentia/kindchiro-nuxt-wf/public/logo-light.svg"

AI_RULES="forbid:photoreal_faces forbid:medical_claims allow:abstract,illustrated"

A few of these fields deserve a callout. VOICE tunes the captions agent. AI_RULES is the integrity backstop — for a chiropractic clinic, generating a photoreal face and implying that person is a patient is not just bad taste, it is a regulatory problem. The visual designer agent reads AI_RULES before any Higgsfield call and refuses anything that violates it. The rule is encoded; it is not "remember to not do this".

CLIENT_PROJECT_DIR, when set, points at the client's actual site repo. That lets the captions agent read the real menu, services, opening hours and about page rather than make plausible-sounding things up. Hallucination shrinks dramatically when the model can read the source.

Figma as the design source of truth

For clients with a real design system in Figma, I no longer hand-port the palette and typography into brand.env and hope it stays in sync. Instead, brand.env carries a FIGMA_FILE_URL, and a dedicated content-figma-extractor agent pulls the design tokens directly from Figma via its MCP server.

The flow is simple in principle. At the start of any monthly batch, the orchestrator agent checks the FIGMA_LAST_EXTRACTED timestamp. If it is missing or more than thirty days old, it delegates to the extractor. The extractor walks a careful budget — a handful of Figma calls per session because the View seat I use is rate-limited at around five to eight requests — and writes a canonical DESIGN-SYSTEM.md, per-frame screenshots and raw token JSON into the client's design/ folder.

Then it proposes a diff against the current brand.env. If the palette has drifted, the extractor tells me. If a font in the Figma file is missing from shared/fonts/, it tells me. I review, accept the diff if it makes sense, and the visuals generated from that point on reproduce the Figma source. The first production run of this — extracting the Byte24 design system from a shared Figma file — surfaced a navy colour that had drifted from #003056 to #07375C, a missing testimonial background colour and a confirmed type scale of JetBrains Mono ExtraBold 80 / Inter Medium 36 / Caveat Bold 96. None of that would have been caught by eyeballing.

If the Figma file is the source of truth for design, then brand.env should drift toward Figma, not the other way around.

— Chris de Gruijter

The agent layer

The pipeline is operated by a small set of Claude Code agents that all share the content- prefix. They are intentionally narrow and each one does one job well.

  • content-orchestrator — the entry point. Reads brand.env, plans the monthly batch, delegates to the others, then assembles the briefs, captions and schedule in the repo and the final visuals on Drive.
  • content-visual-designer — walks the source-selection ladder, picks real photos when they exist, and only reaches for Higgsfield when it must.
  • content-brand-applier — runs the tools/build-*.sh scripts to apply footers, logos, type and brand cards over the raw imagery.
  • content-copy-writer — writes platform-tuned captions for Instagram, Facebook, LinkedIn and X per post.
  • content-figma-extractor — the Figma sync agent described above.

Crucially, every agent only knows what the workspace allows it to know. The orchestrator reads brand.env and the client notes. The copy writer reads the client's real site. The visual designer reads AI_RULES. None of them have a free hand to invent claims about the client because they are constrained at the edges by files I control.

A monthly batch, end to end

What actually happens when I run a batch? Roughly this:

  1. I cd into the client folder and ask the orchestrator to plan a 20-post batch for the upcoming month.
  2. It reads brand.env, checks Figma freshness, reads CLIENT-NOTES.md if present, and drafts a briefs.md covering organic feed, story, paid and any video posts.
  3. It delegates raw imagery to the visual designer, who walks the asset ladder and only generates new images when the existing library has nothing usable.
  4. The brand-applier composites the raw imagery into finished webp files at the correct sizes — 1200x1200 feed, 1080x1920 story, paid sizes per platform — and writes them to outputs/<channel>/<YYYY-MM>/ on Drive.
  5. The copy writer drafts captions tuned per platform, written to posts/<YYYY-MM>/captions.md.
  6. I review, edit and approve. The schedule gets locked in schedule.csv.

A 20-post batch costs roughly forty to eighty Higgsfield credits and about three to four hours of my time on top — most of which is curation and editing, not generation. The cost per post is small enough that the constraint is now creative quality, not throughput.

Filenames are not a detail

One small thing I am pedantic about: every output file carries its real dimensions in its name, in WIDTHxHEIGHT form. 01-curves-feed-1200x1200.webp, never 01-curves-1200.webp. It sounds trivial. It saves a remarkable amount of time when you are deciding whether a file is the right asset for an Instagram feed (square) or a Facebook story (vertical) six weeks later without opening it.

Most of the system is like that — small conventions, applied consistently, multiplied by every client. The compounding effect over a year is genuinely large.

Templates: when a recipe earns its name

After enough batches, certain visual recipes become worth locking down. A title-card on a teal-to-navy gradient with a subtle blob overlay and a dot-grid corner is not a one-off; if I am going to use it for three or more posts, it deserves to be a template. So I checkpoint the invocation as a small bash wrapper in the client's templates/ folder, with the decorative arguments frozen and only the variable inputs (title, subtitle, body, optional cursive accent) exposed.

# byte24/byte24/templates/feed-gradient-blob.sh
#!/usr/bin/env bash
# Family A — title-card on brand gradient with blob + dot-grid + cursive accent

set -euo pipefail
source ../../shared/lib.sh
set -a; source ../brand.env; set +a

TITLE="${1:?title required}"
SUBTITLE="${2:-}"
BODY="${3:-}"
CURSIVE="${4:-}"

../../tools/build-social-byte24.sh \
  --canvas 1200x1200 \
  --bg-gradient "$PRIMARY $SECONDARY" \
  --overlay blob-8pct \
  --corner dot-grid \
  --plus-accent on \
  --title "$TITLE" \
  --subtitle "$SUBTITLE" \
  --body "$BODY" \
  --cursive "$CURSIVE" \
  --arrow curved-down

A canonical rendered preview with lorem ipsum text lives on Drive at outputs/templates/<name>-<WIDTHxHEIGHT>.jpg, and a one-line description goes into templates/README.md. The whole system is built on executable bash rather than YAML manifests for one reason — there is no parser to maintain, and anyone reading the script can see exactly how the image is built in thirty seconds.

The dual-device split

I work on two machines: a desktop and a ThinkPad I take everywhere. Most of my AI config syncs between them through git. The Content Powerhouse splits cleanly into a text side and a visual side, and the split lines up with where each machine is strong.

The text side — brand.env, prompts, briefs, captions, schedules — is entirely git-tracked and works identically on both machines. The visual side depends on reliable read-write access to the Drive-backed library. On the desktop that library is a real local partition with the official Google Drive client syncing it upward. On the laptop it is only reachable through gvfs as a "Shared with me" mount, which is flaky and slow for many small files. Writing to it from the laptop also risks sync conflicts against the desktop's authoritative copy.

So the rule I settled on is simple: plan and write content on the laptop, generate visuals on the desktop. The visual symlinks are intentionally not created on the laptop. Because they are gitignored, their absence breaks nothing. When I am abroad and genuinely need to generate something offline, the symlinks are machine-local — I can point them at a local folder temporarily and reconcile back to the desktop later. Higgsfield itself is a remote MCP, so it works identically on either machine.

Integrity rules: what the system refuses to do

I want to be careful here because this is where most "AI content" systems quietly do harm. A visual designer agent that will generate a photoreal woman holding a baby for a chiropractic clinic is a liability — both ethically and regulatorily. The Powerhouse has a docs file specifically for this (docs/integrity.md) and the rules are short:

  • Healthcare: no AI photoreal faces depicted as patients or practitioners; no implied medical claims.
  • Trades: no fake "completed project" composites.
  • Consumer: no fake testimonials or fake UGC.
  • All industries: respect Meta's AI-disclosure rules wherever applicable.

These rules are wired into AI_RULES in brand.env and enforced by the visual designer agent. If the rule says forbid:photoreal_faces and a prompt would produce one, the agent refuses and falls back to abstract, illustrated or stock imagery. The integrity rule does not depend on me remembering it at 11pm on a Sunday.

What changed about my workflow

The honest answer is: most of the work shifted upstream. I now spend more time on brand.env, CLIENT-NOTES.md, the prompts library and the integrity rules than I do on any individual post. Once those upstream artefacts are correct, a monthly batch is largely supervised execution rather than original work. The cost of an additional post is small. The cost of an additional client is also small — onboarding is mostly filling out the contract.

The other thing that changed is my confidence in what shipped. Every output traces back to a brand contract, a real photo or a deliberate generation, a captioned brief and an approved schedule. If a client asks why a post looks the way it does, I can point at the recipe. That kind of provenance was completely absent in the Photoshop-folder era. It turns out clients like it when you can answer that question without checking three different inboxes.

What I would do differently if I started today

A few honest reflections after a year of building this incrementally:

  • Start with the integrity rules. I bolted AI_RULES on once I had a healthcare client that needed it. I should have had it from post one.
  • Treat Figma as canonical earlier. The first six months of hand-porting palettes into brand.env produced exactly the kind of slow drift you would predict. The Figma extractor pays for itself the moment a brand updates.
  • Lean harder on the asset ladder. AI generation is so easy to reach for that it can quietly become the default. Forcing the ladder — own, stock, prior AI, shared, new generation — produces visibly better work and visibly lower costs.
  • Filenames carry meaning. The WIDTHxHEIGHT convention was a late addition. Earlier batches are notably harder to navigate. Pick a filename convention before the first batch.

Where it goes next

The next obvious step is video — I have a Higgsfield-driven pipeline for short reels in production, sitting alongside the still-image one. The interesting part is that everything upstream of the generation step is the same: same brand.env, same agent layer, same source-selection ladder. The system absorbs new output types with very little friction, which is the test I care about for any piece of infrastructure I rely on.

The other direction is closer integration with the rest of my stack — Webfluentia's agency dashboard, the agentic SEO pipeline, the paid-ads audit agents. Content does not exist in isolation. The blog post that ranks on Google should feed the social calendar; the social calendar should feed the paid creative; the paid creative should feed the next month's analytics review. Pulling those threads together is the next year of work.

But the foundation — one cross-workspace repo, per-client brand contracts, a Drive-backed asset library, a narrow set of agents and a handful of bash scripts — is settled. The Content Powerhouse earns its name in proportion to how boring it has become to operate. That, more than any single AI demo, is what convinces me the time spent building it was worth it.

Frequently Asked Questions

Why store visuals on Google Drive instead of in the git repo?

A 20-post batch produces hundreds of megabytes of intermediate files. Git is the wrong tool for that — it bloats clone times, kills CI and adds nothing in return. Drive gives me offsite backup, cross-device sync and a version history for free. The repo stays under a few megabytes; the visual library grows without limit. Symlinks bridge the two so the per-client folders feel local while the binaries live on Drive.

What does brand.env actually control?

Everything that varies per client: client ID and name, tagline, domain, location, industry, voice, the full palette (primary, secondary, accent, backgrounds), the type system (heading, body, bold, optional display/cursive fonts), logo paths, integrity rules for AI generation, and optionally a Figma file URL when Figma is the design source of truth. Every script and every agent sources it before doing any work, and if a required value is missing the script fails loudly rather than guessing.

How do you stop the AI from inventing claims about a client?

Two ways. First, brand.env carries a CLIENT_PROJECT_DIR pointing at the client's real site repo, and the captions agent reads the actual services, menu, opening hours and about page from there. Second, the visual designer reads an AI_RULES field that explicitly forbids unsafe categories per industry — no photoreal patient faces for healthcare, no fake completed-project composites for trades, no fake testimonials. The agent refuses anything that violates the rule rather than relying on me to catch it.

How does the Figma extractor actually work?

It runs through the Figma MCP server (under a dedicated mcp-design profile). The orchestrator checks the FIGMA_LAST_EXTRACTED date at batch start; if stale or missing, it delegates to the extractor. The extractor budgets its calls carefully — usually around five to eight per session because View-seat accounts are rate-limited — pulls page and frame metadata, screenshots a handful of representative frames, fetches design context for the most valuable ones, and writes a canonical DESIGN-SYSTEM.md plus raw token JSON into the client's design folder. It then proposes a brand.env diff if anything has drifted.

What does a 20-post batch cost?

Roughly 40 to 80 Higgsfield credits plus about three to four hours of my time. Most of that time is curation, editing and approval rather than generation — the system handles the mechanical part. Reuse is encouraged by the source-selection ladder, so credit spend trends down as a client's asset library grows.

Why bash scripts instead of YAML or JSON manifests for templates?

Bash wrappers are executable — there is no parser to maintain, no schema to keep in sync. They are also self-documenting: reading the script tells you exactly how the image is built in thirty seconds. The template family lives in the same shape as the underlying tools/build-*.sh scripts, so anyone reading the repo does not have to context-switch between formats.

Does this work on a laptop without the Drive partition?

The text side (brand.env, prompts, briefs, captions, schedules) works identically on a laptop because it is fully git-tracked. The visual side depends on reliable Drive access, which is unreliable on the laptop via gvfs. The default is to plan and write content on the laptop and generate visuals on the desktop. When abroad, the symlinks are machine-local and can be repointed at a local folder temporarily, then reconciled back to the desktop's authoritative Drive copy on return.