carlos / carloku Public

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

Plain git — no account needed to clone.

Download

Download this file

1# πŸ€– AGENTS.md
2
3The canonical agent-instructions file for this repo β€” keep it current here,
4not in a tool-specific file. (Claude Code reads it via the `CLAUDE.md`
5pointer.)
6
7The marketing site for **Carloku** β€” the hosted CARLOS platform
8(console.carloku.com). Brand boundary: Carloku is the *hosted platform*
9brand; CARLOS is the framework/CLI/console name. Never rename CARLOS things
10to Carloku here.
11
12**Voice lives in PRODUCT.md** β€” the one feeling is lightness ("finally,
13simple"); the landing page appeals to the heart, /get-started carries the
14tech. Read it before touching copy.
15
16Rules inherited from the `website` repo, which governs the sibling site:
17
18- **Small site, nothing fetched.** `/` and `/get-started/` are hand-written
19 and share one `site.css`. No frameworks, no analytics, and nothing pulled
20 from another origin β€” the two Geist faces in `fonts/` are self-hosted. The
21 only script is `site.js`: 2KB, hand-written, and pure enhancement (the hero
22 terminal types itself, the header grows a hairline on scroll). The pages
23 read fine with it blocked; keep it that way, and don't add a second one.
24- **`/docs` is built; the rest of the site is not.** Docs pages come from the
25 markdown in `src/docs/` through Eleventy, the same pipeline rastrillo.org
26 uses, run here before you ship. `_site/` is gitignored and never committed;
27 Eleventy and markdown-it are devDependencies that no visitor ever meets.
28 See "Building the docs" below.
29- **Light and dark** via `prefers-color-scheme` β€” keep both working.
30- **AI authorship is always marked** with a visible πŸ€– (cascades from a
31 heading); person-emoji (πŸ‘¨/πŸ‘€/πŸ§‘) blocks are certified human and
32 off-limits to LLM edits. Baseline: everything here is AI-written unless
33 marked otherwise.
34- **Don't overclaim.** The console requires Keymail sign-in; pricing does
35 not exist yet, so the site says nothing about it.
36
37## Building the docs
38
39`/docs` is ten pages of markdown under `src/docs/`, rendered by Eleventy into
40`_site/` before you ship. **The corpus is authored here, by hand.** That is the
41opposite of carlosframework.com, whose `/docs` corpus is vendored out of the
42platform repo by a sync script: there is no sync step in this repo, nothing
43under `src/docs/` or `src/_data/docsnav.json` is generated, and nothing will
44overwrite an edit you make. The markdown IS the source β€” edit it here.
45
46```
47npm install
48npm run check # builds, then gates the rendered output
49npm run serve # localhost:8080, live reload
50```
51
52`npm run check` is `eleventy` followed by `hack/check-docs.mjs`, which reads
53`_site/` after the build: the hand-written pages and shared assets were
54emitted at all; every nav entry has a built page and a built `.md` twin; every
55internal `/docs` href resolves to a built file, and to a real `id="…"` when it
56carries a fragment; and `slugify` in `eleventy.config.js` agrees with Go's
57`internal/docsite.Anchor` on every case in `src/_data/docsanchors.json`. That
58last one is why the anchor fixture is vendored rather than retyped: a heading
59whose fragment the platform's Go gate accepts cannot 404 in a browser here
60because the two slug rules drifted.
61
62**Eleventy does not clean stale output.** Delete or rename a page and its old
63built HTML stays in `_site/` until you remove it β€” and the gate will *not*
64notice, because every check it runs is satisfied by a file that should no
65longer exist. `_site/` is gitignored, so a stale page never shows up in a diff
66either. **Build clean whenever you are verifying anything:**
67
68```
69rm -rf _site && npm run check
70```
71
72The deploy sequence below gets this for free: it builds inside a fresh
73`git archive` export, which has no `_site/` in it at all.
74
75`index.html` and `get-started/index.html` are hand-written and pass through the
76build byte-identical β€” `templateFormats` excludes `html` on purpose, so
77Eleventy copies them instead of rendering them as templates. If either changes
78in `_site/` without you editing its source, something is wrong.
79
80## Deploying
81
82Merging does **not** publish. The live site is stale until someone deploys.
83
84**Since 2026-10-04 the site is an ordinary console app** (platform #209):
85
86| | |
87| --- | --- |
88| console | `https://console.carloku.com` |
89| account / app | `bes` (named `carloku`) / `website` |
90| channel | `edge`, the app's only channel |
91| kind | `static` |
92| platform URL | `https://website.bes.oncarlos.com` |
93| custom domains | `carloku.com`, `www.carloku.com` |
94
95carloku.com and www.carloku.com are **platform claims** on that app. They are
96in the flagship's own zone, which `carlos domains attach` refuses to members,
97so the operator attached them with `carlos domains attach --platform`. Only an
98ops account owner or a `CARLOS_SYSTEM_OPERATORS` address can change or detach
99them. A deploy never touches them: deploy the app and both hostnames follow.
100
101**There is a build step, so what ships is `_site/`, never the repo tree.** A
102tree ship has no `_site/` in it: it would publish `index.html` buried under
103`src/`, no built pages, and no `/docs` at all.
104
105```
106SHA=$(git rev-parse --short HEAD) # the sha you are shipping
107
108# 1. Clean export. Never deploy a working checkout: the packer takes every
109# regular file it sees, including .git, node_modules/ and .claude/.
110rm -rf "$TMPDIR/carloku-ship" && mkdir -p "$TMPDIR/carloku-ship"
111git archive "$SHA" --prefix=export/ | tar -x -C "$TMPDIR/carloku-ship"
112cd "$TMPDIR/carloku-ship/export"
113
114# 2. Build, and gate the rendered output. `check` runs eleventy, then
115# hack/check-docs.mjs over what it produced.
116npm ci
117npm run check
118
119# 3. Cache-bust BOTH stylesheets across every built page β€” see Caching
120# below; this is not optional.
121find _site -name '*.html' -exec sed -i \
122 -e "s/\.css\"/.css?v=$SHA\"/g" \
123 -e "s/\.css?v=[^\"]*\"/.css?v=$SHA\"/g" {} +
124
125grep -rho '\.css?v=[^"]*"' _site --include='*.html' | sort -u # ONE line: your sha
126grep -rn '\.css"' _site --include='*.html' \
127 && echo "UNBUSTED LINK β€” do not ship" || echo "cache-bust ok"
128
129# 4. Deploy the BUILT OUTPUT through the console. --version is required:
130# the export has no .git, so the default (git rev-parse) has nothing to
131# read.
132carlos deploy --console https://console.carloku.com \
133 --account bes --app website --kind static \
134 --version "$SHA" --label "<one line about the change>" --no-prompt _site
135```
136
137`carlos deploy` ships, promotes onto `edge` and watches the platform URL until
138`X-Carlos-Version` reports the new sha. It runs as whoever is signed in
139(`carlos auth login --console https://console.carloku.com`) and needs that
140address to be a member of `bes`. Use a current `carlos` binary. An old one on
141your `PATH` may not know this console's API.
142
143**Step 3 explained, because both halves are load-bearing.** The links in `src/`
144are plain (`href="/site.css"`), so the first expression stamps a token onto a
145bare link and the second re-stamps a link that already carries one. Both end at
146the closing quote on purpose, and so does the verification grep. A sha
147beginning with `0` β€” this app has already shipped `01b5365` β€” makes
148`?v=01b5365` contain the literal substring `?v=0`, so an unanchored grep for
149`?v=0` calls every freshly bumped file stale (13 of 13, measured), and an
150unanchored `sed` run twice yields `?v=01b536501b5365`. Anchoring on `"` fixes
151both and makes step 3 idempotent: run it again and again, the answer is the
152same. Don't hard-code link or page counts anywhere; the corpus grows, which is
153also why the `find` walks `_site` instead of naming files.
154
155**Caching: the edge sends no `Cache-Control` and no `ETag` on static routes,
156only `Last-Modified`** (confirmed live on carloku.com 2026-08-27). Browsers
157therefore apply HEURISTIC caching, roughly 10% of the age since
158`Last-Modified`, so a returning visitor can hold a stale page for days. This
159bit the sibling site the day its redesign shipped: one browser served the whole
160old page, another served the NEW html against the OLD stylesheet. The real fix
161is server-side `Cache-Control` on static routes β€” platform issue
162**carlosframework/platform#234**. Until that lands, **step 3 is a required part
163of deploying this site**, and it is required for `docs.css` exactly as much as
164for `site.css`: the docs pages link both, and a docs page rendered against a
165stale `docs.css` loses its whole layout.
166
167**Verify** by header and by content, and verify `/docs` **specifically**: the
168landing page looks right whether or not the built docs made it into the
169artifact.
170
171```
172carlos channels --console https://console.carloku.com --account bes --app website
173carlos releases --console https://console.carloku.com --account bes --app website
174
175curl -sI https://carloku.com/docs/ | grep -i x-carlos-version # your sha
176curl -s https://carloku.com/ | grep -i "<something from the change>"
177curl -s https://carloku.com/docs/ | grep -o 'docs\.css?v=[^"]*' # your sha
178```
179
180Every edge serves the site, so to check one directly add
181`--resolve carloku.com:443:<edge-ip>`. The four flagship edges are
18299.81.104.219 (Ireland), 16.60.78.128 (London), 44.210.206.26 (Virginia) and
18354.252.186.195 (Sydney). `-I` sends a HEAD, and HEAD is excluded from edge
184compression, so anything about `Content-Encoding` needs a GET.
185
186**Rollback** is a pointer move. Every release stays in the bucket, cache-busted
187as it was shipped, so a rollback restores a self-consistent page-plus-stylesheet
188pair:
189
190```
191carlos rollback --console https://console.carloku.com --account bes --app website edge
192# or, to a named release:
193carlos promote --console https://console.carloku.com --account bes --app website <sha> edge
194```
195
196**DNS** is in Route53, in the carloku-infrastructure tofu (`dns-carloku.tf`),
197since 2026-10-05. The apex carries one latency-routed A record per edge, each
198behind a health check that asks that edge for carloku.com, so every visitor
199gets their nearest healthy edge. `www` is a CNAME to `website.bes.oncarlos.com`,
200which the platform steers the same way. `dashboard` and `console` are CNAMEs to
201the console and are not this site's. A new flagship region needs a line in that
202file, or the apex never sends anyone there. The domain is still registered at
203DNSimple; only the delegation moved.
204
205**History.** Until 2026-10-04 the site was `bab/carloku`, a direct-bucket app
206shipped with the pinfra `ship-app.sh` onto `canary/rehearsal`, with box-local
207routes on the Ireland box. Those routes were removed when the hostnames moved
208to `bes/website`. The old app's releases are still in the bucket, but nothing
209routes to it: **never ship to `bab/carloku` again.**
210