返回 CodeWhale
README.md
根目录 / web / README.md
1 # codewhale-web
2
3 Documentation and community site for [Codewhale](https://github.com/Hmbown/CodeWhale) — lives at **codewhale.net**.
4
5 Next.js 15 (App Router) + Tailwind, deployed to Cloudflare Workers via [`@opennextjs/cloudflare`](https://opennext.js.org/cloudflare). Curated "Today's Dispatch" content is regenerated every 6 hours by a Cloudflare Cron Trigger that calls `deepseek-v4-flash` to summarise recent repo activity, and stored in Workers KV.
6
7 ## Local dev
8
9 ```bash
10 cd web
11 npm install
12 cp .env.example .env.local # fill in the keys you have
13 npm run dev # http://localhost:3000
14 ```
15
16 Env (mirrors `.env.example`):
17
18 | Variable | What | Required? |
19 | --------------------------- | ---------------------------------------------------------------- | -------------------- |
20 | `DEEPSEEK_API_KEY` | DeepSeek platform key (`sk-...`) | only for the `/api/cron` tasks (summarization + community agent) |
21 | `GITHUB_TOKEN` | Fine-grained PAT, public-repo read scope | optional (raises rate limit 60 → 5000 req/h) |
22 | `GITHUB_REPO` | Defaults to `Hmbown/CodeWhale` | optional |
23 | `CRON_SECRET` | Shared secret for manual `/api/cron` invocation | optional (Cloudflare cron triggers don't need it) |
24 | `DEEPSEEK_MODEL` | Defaults to `deepseek-v4-flash` | optional |
25 | `DEEPSEEK_BASE_URL` | Defaults to `https://api.deepseek.com` | optional |
26 | `MAINTAINER_TOKEN` | Admin panel auth; access `/admin?token=<value>` | only for `/admin` |
27 | `MAINTAINER_GITHUB_PAT` | PAT with `issues:write`, for posting comments via `/admin` | only for `/admin` posting |
28 | `NEXT_PUBLIC_GITEE_ENABLED` | Set to `1` once the Gitee mirror exists; blank hides Gitee links | optional |
29
30 The site renders fine without any of them — `Today's Dispatch` falls back to a static editorial; the GitHub feed shows "feed not yet loaded".
31
32 ## Deploy to Cloudflare
33
34 Ordinary pushes and pull requests run the web checks and production build, but
35 they do **not** deploy. The `deploy` job in `.github/workflows/web.yml` runs
36 only for a maintainer-triggered `workflow_dispatch` on `main`. Before approval,
37 record the exact 40-character `origin/main` SHA and trigger that ref:
38
39 ```bash
40 git fetch origin main
41 git rev-parse origin/main
42 gh workflow run web.yml --repo Hmbown/CodeWhale --ref main
43 ```
44
45 The manual job records the pre-deploy source drift, builds the OpenNext bundle,
46 deploys only after the protected Cloudflare inputs pass, and then requires the
47 public `/api/facts` receipt to report the exact workflow SHA. A credential-free
48 local comparison is available without starting a deployment:
49
50 ```bash
51 npm run compare:deployed-facts -- --expected-revision <exact-40-character-sha>
52 ```
53
54 You already own `codewhale.net` on Cloudflare and have a Workers Paid plan. The deploy is two steps:
55
56 1. **Provision KV namespaces once:**
57
58 ```bash
59 npx wrangler kv namespace create CURATED_KV
60 npx wrangler kv namespace create NEXT_INC_CACHE_KV
61 ```
62
63 Copy the printed `id` values into the matching `wrangler.jsonc` bindings
64 (replace each `REPLACE_WITH_KV_ID`).
65
66 2. **Set secrets and deploy:**
67
68 ```bash
69 npx wrangler secret put DEEPSEEK_API_KEY
70 npx wrangler secret put GITHUB_TOKEN # optional
71 npx wrangler secret put CRON_SECRET # optional, for manual /api/cron?task=curate hits
72
73 npm run deploy # builds with OpenNext + uploads
74 ```
75
76 3. **Point the domain:** in the Cloudflare dashboard, add a Worker route for `codewhale.net/*` → the deployed Worker, named `codewhale-web` (see `wrangler.jsonc`).
77
78 The first cron run happens within 6 hours; you can also kick it manually:
79
80 ```bash
81 curl -H "x-cron-secret: $CRON_SECRET" "https://codewhale.net/api/cron?task=curate"
82 ```
83
84 ## What's where
85
86 Pages are bilingual by default: each `app/[locale]/` page renders both
87 English and Chinese from the same file, keyed by the `[locale]` segment
88 (see `lib/i18n/config.ts`). Copy changes must update both locales. The
89 v0.9.2 wave adds routed **partial** locales (ja, vi, ko, ru, uk, es, pt-BR):
90 their shared chrome (nav/footer/switcher) and home-page copy live in
91 `lib/i18n/dictionaries/<code>/` (checked by `npm run check:locales`), and
92 everything else falls back to the English copy. Routing, middleware
93 detection (`lib/i18n/detect.ts`), sitemap, and hreflang all derive from the
94 one registry.
95
96 ```
97 web/
98 ├── app/
99 │ ├── globals.css ocean portal, docs layout, type, and shared surfaces
100 │ ├── [locale]/ 10 routed locales; zh has native page bodies,
101 │ │ the rest fall back to the English body
102 │ │ ├── layout.tsx root + locale layout: html shell, fonts, nav, footer
103 │ │ ├── page.tsx home — hero, ticker, proof, decides, workflow,
104 │ │ │ start, boundaries, surfaces, install band, community
105 │ │ ├── install/page.tsx per-OS install with auto-detection
106 │ │ ├── docs/page.tsx modes / tools / approval / config / mcp / providers
107 │ │ ├── faq/page.tsx frequently asked questions
108 │ │ ├── feed/page.tsx live mirror of issues + PRs
109 │ │ ├── roadmap/page.tsx shipped / underway / considered / ruled out
110 │ │ ├── contribute/page.tsx how to PR + house rules + dev loop
111 │ │ └── admin/ maintainer panel (page.tsx + admin-client.tsx)
112 │ └── api/
113 │ ├── cron/route.ts cron tasks: curate, triage, facts-drift, …
114 │ ├── facts/route.ts public source/deployment receipt
115 │ ├── github/feed/route.ts cached JSON endpoint
116 │ └── admin/ login, logout, post (MAINTAINER_TOKEN-gated)
117 ├── data/
118 │ └── latest-published-release.json manually advanced only after publication
119 ├── components/
120 │ ├── nav.tsx sticky header w/ date strip + CJK accents
121 │ ├── footer.tsx dense 5-column footer
122 │ ├── whale.tsx shared Codewhale mark
123 │ ├── ticker.tsx live wire: merges, issues, releases + handles
124 │ ├── feed-card.tsx one issue/PR card
125 │ ├── locale-switcher.tsx N-locale dropdown with partial badges
126 │ └── install-*.tsx install page blocks (binary, code block, tiles)
127 ├── lib/
128 │ ├── types.ts shared types
129 │ ├── i18n/ locale config, en/zh dictionaries
130 │ ├── github.ts REST client + relative-time formatter
131 │ ├── deepseek.ts v4-flash chat client + curate() prompt
132 │ ├── facts.ts getFacts(): KV value, else build-time FACTS
133 │ ├── facts.generated.ts GENERATED — do not edit by hand
134 │ ├── facts-drift.ts runtime re-derivation for the drift cron
135 │ ├── community-agent.ts triage / pr-review / digest cron tasks
136 │ └── kv.ts Cloudflare KV access via OpenNext bindings
137 ├── scripts/
138 │ ├── derive-facts.mjs prebuild: repo sources → lib/facts.generated.ts
139 │ ├── compare-deployed-facts.mjs credential-free exact-SHA receipt check
140 │ └── check-kv-id.mjs predeploy guard for KV namespace ids
141 ├── wrangler.jsonc CF Worker config + cron + KV binding
142 ├── open-next.config.ts OpenNext adapter config
143 └── tailwind.config.ts design tokens
144 ```
145
146 ## Facts pipeline
147
148 Mechanical facts (version, provider list, sandbox backends, crate names,
149 default model, Node engines) are never hand-written into pages:
150
151 1. **Build time** — `scripts/derive-facts.mjs` runs as `prebuild` (and before
152 `npm run dev`), parses the parent repo (`Cargo.toml`, `crates/tui/src/config.rs`,
153 `crates/tui/src/sandbox/mod.rs`, `npm/codewhale/package.json`) and writes
154 `lib/facts.generated.ts`. Never edit that file by hand.
155 2. **Published release** — `data/latest-published-release.json` records the
156 latest GitHub Release separately from the source candidate. Install commands
157 use this published tag; they never turn the workspace version into a release
158 before publication. The credential-free deployed-facts comparison checks the
159 record against the public receipt.
160 3. **Runtime** — the `/api/cron?task=facts-drift` cron (`lib/facts-drift.ts`)
161 resolves an exact `main` revision, derives every source fact from that SHA,
162 and writes changes to `CURATED_KV` under `facts:current`. Pages accept that
163 snapshot only when its source provenance is the same as or newer than the
164 deployed build. Legacy, malformed, or older KV data cannot replace newer
165 build facts; published-release metadata is resolved independently. Public
166 fact pages revalidate their cached HTML every five minutes.
167
168 `/api/facts` exposes only public provenance and counts: deployed/resolved source
169 revision, version, provider count, tool count, selection reason, and latest
170 published release. It contains no environment values, tokens, or KV contents.
171
172 When a new `ApiProvider` variant lands in `crates/tui/src/config.rs`, it must
173 be added to the `labelMap` in **both** `scripts/derive-facts.mjs` and
174 `lib/facts-drift.ts` (or to the `EXCLUDED` set if deliberately hidden). Both
175 fail loudly on unmapped variants, so the build / cron will tell you.
176
177 ## Visual direction
178
179 The public site is a documentation portal with a restrained underwater atmosphere. Content and navigation come first; ocean depth, currents, and the whale mark provide identity without turning every section into a themed card.
180
181 - **Palette**: cool paper and mist for reading surfaces, deep navy for terminal and community sections, muted current blue for links, and small gold/coral signals where status needs contrast.
182 - **Type**: Space Grotesk for headings, IBM Plex Sans for body copy, and JetBrains Mono for commands and compact interface labels.
183 - **Structure**: compact documentation rows, quiet hairline dividers, generous but bounded reading widths, and responsive layouts that remove chrome before content.
184
185 If you want to retune the palette, edit `:root` in `app/globals.css` and the `colors` block in `tailwind.config.ts`.
186
186 lines MARKDOWN