carlos / carloku Public

Clone
git clone https://amadan.net/carlos/carloku

Plain git — no account needed to clone.

Download

Download this file

πŸ€– AGENTS.md

The canonical agent-instructions file for this repo β€” keep it current here, not in a tool-specific file. (Claude Code reads it via the CLAUDE.md pointer.)

The marketing site for Carloku β€” the hosted CARLOS platform (console.carloku.com). Brand boundary: Carloku is the hosted platform brand; CARLOS is the framework/CLI/console name. Never rename CARLOS things to Carloku here.

Voice lives in PRODUCT.md β€” the one feeling is lightness ("finally, simple"); the landing page appeals to the heart, /get-started carries the tech. Read it before touching copy.

Rules inherited from the website repo, which governs the sibling site:

  • Small site, nothing fetched. / and /get-started/ are hand-written and share one site.css. No frameworks, no analytics, and nothing pulled from another origin β€” the two Geist faces in fonts/ are self-hosted. The only script is site.js: 2KB, hand-written, and pure enhancement (the hero terminal types itself, the header grows a hairline on scroll). The pages read fine with it blocked; keep it that way, and don't add a second one.
  • /docs is built; the rest of the site is not. Docs pages come from the markdown in src/docs/ through Eleventy, the same pipeline rastrillo.org uses, run here before you ship. _site/ is gitignored and never committed; Eleventy and markdown-it are devDependencies that no visitor ever meets. See "Building the docs" below.
  • Light and dark via prefers-color-scheme β€” keep both working.
  • AI authorship is always marked with a visible πŸ€– (cascades from a heading); person-emoji (πŸ‘¨/πŸ‘€/πŸ§‘) blocks are certified human and off-limits to LLM edits. Baseline: everything here is AI-written unless marked otherwise.
  • Don't overclaim. The console requires Keymail sign-in; pricing does not exist yet, so the site says nothing about it.

Building the docs

/docs is ten pages of markdown under src/docs/, rendered by Eleventy into _site/ before you ship. The corpus is authored here, by hand. That is the opposite of carlosframework.com, whose /docs corpus is vendored out of the platform repo by a sync script: there is no sync step in this repo, nothing under src/docs/ or src/_data/docsnav.json is generated, and nothing will overwrite an edit you make. The markdown IS the source β€” edit it here.

npm install
npm run check          # builds, then gates the rendered output
npm run serve          # localhost:8080, live reload

npm run check is eleventy followed by hack/check-docs.mjs, which reads _site/ after the build: the hand-written pages and shared assets were emitted at all; every nav entry has a built page and a built .md twin; every internal /docs href resolves to a built file, and to a real id="…" when it carries a fragment; and slugify in eleventy.config.js agrees with Go's internal/docsite.Anchor on every case in src/_data/docsanchors.json. That last one is why the anchor fixture is vendored rather than retyped: a heading whose fragment the platform's Go gate accepts cannot 404 in a browser here because the two slug rules drifted.

Eleventy does not clean stale output. Delete or rename a page and its old built HTML stays in _site/ until you remove it β€” and the gate will not notice, because every check it runs is satisfied by a file that should no longer exist. _site/ is gitignored, so a stale page never shows up in a diff either. Build clean whenever you are verifying anything:

rm -rf _site && npm run check

The deploy sequence below gets this for free: it builds inside a fresh git archive export, which has no _site/ in it at all.

index.html and get-started/index.html are hand-written and pass through the build byte-identical β€” templateFormats excludes html on purpose, so Eleventy copies them instead of rendering them as templates. If either changes in _site/ without you editing its source, something is wrong.

Deploying

Merging does not publish. The live site is stale until someone deploys.

Since 2026-10-04 the site is an ordinary console app (platform #209):

| | | | --- | --- | | console | https://console.carloku.com | | account / app | bes (named carloku) / website | | channel | edge, the app's only channel | | kind | static | | platform URL | https://website.bes.oncarlos.com | | custom domains | carloku.com, www.carloku.com |

carloku.com and www.carloku.com are platform claims on that app. They are in the flagship's own zone, which carlos domains attach refuses to members, so the operator attached them with carlos domains attach --platform. Only an ops account owner or a CARLOS_SYSTEM_OPERATORS address can change or detach them. A deploy never touches them: deploy the app and both hostnames follow.

There is a build step, so what ships is _site/, never the repo tree. A tree ship has no _site/ in it: it would publish index.html buried under src/, no built pages, and no /docs at all.

SHA=$(git rev-parse --short HEAD)      # the sha you are shipping

# 1. Clean export. Never deploy a working checkout: the packer takes every
#    regular file it sees, including .git, node_modules/ and .claude/.
rm -rf "$TMPDIR/carloku-ship" && mkdir -p "$TMPDIR/carloku-ship"
git archive "$SHA" --prefix=export/ | tar -x -C "$TMPDIR/carloku-ship"
cd "$TMPDIR/carloku-ship/export"

# 2. Build, and gate the rendered output. `check` runs eleventy, then
#    hack/check-docs.mjs over what it produced.
npm ci
npm run check

# 3. Cache-bust BOTH stylesheets across every built page β€” see Caching
#    below; this is not optional.
find _site -name '*.html' -exec sed -i \
  -e "s/\.css\"/.css?v=$SHA\"/g" \
  -e "s/\.css?v=[^\"]*\"/.css?v=$SHA\"/g" {} +

grep -rho '\.css?v=[^"]*"' _site --include='*.html' | sort -u   # ONE line: your sha
grep -rn '\.css"' _site --include='*.html' \
  && echo "UNBUSTED LINK β€” do not ship" || echo "cache-bust ok"

# 4. Deploy the BUILT OUTPUT through the console. --version is required:
#    the export has no .git, so the default (git rev-parse) has nothing to
#    read.
carlos deploy --console https://console.carloku.com \
  --account bes --app website --kind static \
  --version "$SHA" --label "<one line about the change>" --no-prompt _site

carlos deploy ships, promotes onto edge and watches the platform URL until X-Carlos-Version reports the new sha. It runs as whoever is signed in (carlos auth login --console https://console.carloku.com) and needs that address to be a member of bes. Use a current carlos binary. An old one on your PATH may not know this console's API.

Step 3 explained, because both halves are load-bearing. The links in src/ are plain (href="/site.css"), so the first expression stamps a token onto a bare link and the second re-stamps a link that already carries one. Both end at the closing quote on purpose, and so does the verification grep. A sha beginning with 0 β€” this app has already shipped 01b5365 β€” makes ?v=01b5365 contain the literal substring ?v=0, so an unanchored grep for ?v=0 calls every freshly bumped file stale (13 of 13, measured), and an unanchored sed run twice yields ?v=01b536501b5365. Anchoring on " fixes both and makes step 3 idempotent: run it again and again, the answer is the same. Don't hard-code link or page counts anywhere; the corpus grows, which is also why the find walks _site instead of naming files.

Caching: the edge sends no Cache-Control and no ETag on static routes, only Last-Modified (confirmed live on carloku.com 2026-08-27). Browsers therefore apply HEURISTIC caching, roughly 10% of the age since Last-Modified, so a returning visitor can hold a stale page for days. This bit the sibling site the day its redesign shipped: one browser served the whole old page, another served the NEW html against the OLD stylesheet. The real fix is server-side Cache-Control on static routes β€” platform issue carlosframework/platform#234. Until that lands, step 3 is a required part of deploying this site, and it is required for docs.css exactly as much as for site.css: the docs pages link both, and a docs page rendered against a stale docs.css loses its whole layout.

Verify by header and by content, and verify /docs specifically: the landing page looks right whether or not the built docs made it into the artifact.

carlos channels --console https://console.carloku.com --account bes --app website
carlos releases --console https://console.carloku.com --account bes --app website

curl -sI https://carloku.com/docs/ | grep -i x-carlos-version      # your sha
curl -s https://carloku.com/ | grep -i "<something from the change>"
curl -s https://carloku.com/docs/ | grep -o 'docs\.css?v=[^"]*'    # your sha

Every edge serves the site, so to check one directly add --resolve carloku.com:443:<edge-ip>. The four flagship edges are 99.81.104.219 (Ireland), 16.60.78.128 (London), 44.210.206.26 (Virginia) and 54.252.186.195 (Sydney). -I sends a HEAD, and HEAD is excluded from edge compression, so anything about Content-Encoding needs a GET.

Rollback is a pointer move. Every release stays in the bucket, cache-busted as it was shipped, so a rollback restores a self-consistent page-plus-stylesheet pair:

carlos rollback --console https://console.carloku.com --account bes --app website edge
# or, to a named release:
carlos promote --console https://console.carloku.com --account bes --app website <sha> edge

DNS is in Route53, in the carloku-infrastructure tofu (dns-carloku.tf), since 2026-10-05. The apex carries one latency-routed A record per edge, each behind a health check that asks that edge for carloku.com, so every visitor gets their nearest healthy edge. www is a CNAME to website.bes.oncarlos.com, which the platform steers the same way. dashboard and console are CNAMEs to the console and are not this site's. A new flagship region needs a line in that file, or the apex never sends anyone there. The domain is still registered at DNSimple; only the delegation moved.

History. Until 2026-10-04 the site was bab/carloku, a direct-bucket app shipped with the pinfra ship-app.sh onto canary/rehearsal, with box-local routes on the Ireland box. Those routes were removed when the hostnames moved to bes/website. The old app's releases are still in the bucket, but nothing routes to it: never ship to bab/carloku again.