create-shibumi 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -2
  3. package/package.json +57 -5
  4. package/scripts/shibumi.lock.json +22 -0
  5. package/scripts/ship.lock.json +4 -0
  6. package/src/args.ts +162 -0
  7. package/src/cli.ts +232 -0
  8. package/src/create.ts +272 -0
  9. package/src/templates/blog/README.md +10 -0
  10. package/src/templates/blog/agents.md +36 -0
  11. package/src/templates/blog/astro.config.mjs +13 -0
  12. package/src/templates/blog/bun.lock +787 -0
  13. package/src/templates/blog/gitignore +4 -0
  14. package/src/templates/blog/package.json +29 -0
  15. package/src/templates/blog/public/og-default.png +0 -0
  16. package/src/templates/blog/public/style.css +147 -0
  17. package/src/templates/blog/src/components/BaseHead.astro +37 -0
  18. package/src/templates/blog/src/content/blog/one-vps-is-plenty.md +13 -0
  19. package/src/templates/blog/src/content/blog/own-your-source.md +13 -0
  20. package/src/templates/blog/src/content/blog/writing-for-agents.md +13 -0
  21. package/src/templates/blog/src/content.config.ts +17 -0
  22. package/src/templates/blog/src/layouts/Base.astro +26 -0
  23. package/src/templates/blog/src/pages/404.astro +11 -0
  24. package/src/templates/blog/src/pages/index.astro +30 -0
  25. package/src/templates/blog/src/pages/llms.txt.ts +29 -0
  26. package/src/templates/blog/src/pages/posts/[id].astro +31 -0
  27. package/src/templates/blog/src/pages/posts/[id].md.ts +18 -0
  28. package/src/templates/blog/src/pages/robots.txt.ts +6 -0
  29. package/src/templates/blog/src/pages/rss.xml.ts +21 -0
  30. package/src/templates/blog/src/site.ts +7 -0
  31. package/src/templates/blog/tsconfig.json +5 -0
  32. package/src/templates/full-stack/.dockerignore +14 -0
  33. package/src/templates/full-stack/Dockerfile +23 -0
  34. package/src/templates/full-stack/README.md +10 -0
  35. package/src/templates/full-stack/agents.md +60 -0
  36. package/src/templates/full-stack/bun.lock +225 -0
  37. package/src/templates/full-stack/compose.yaml +24 -0
  38. package/src/templates/full-stack/drizzle.config.ts +11 -0
  39. package/src/templates/full-stack/gitignore +6 -0
  40. package/src/templates/full-stack/package.json +39 -0
  41. package/src/templates/full-stack/public/app.js +11 -0
  42. package/src/templates/full-stack/public/style.css +60 -0
  43. package/src/templates/full-stack/public/vendor/alpine-csp-3.16.2.min.js +23 -0
  44. package/src/templates/full-stack/scripts/db.ts +39 -0
  45. package/src/templates/full-stack/src/app.ts +106 -0
  46. package/src/templates/full-stack/src/db/index.ts +11 -0
  47. package/src/templates/full-stack/src/db/lifecycle.ts +214 -0
  48. package/src/templates/full-stack/src/db/migrations/0001_notes.sql +9 -0
  49. package/src/templates/full-stack/src/db/schema.ts +13 -0
  50. package/src/templates/full-stack/src/env.ts +24 -0
  51. package/src/templates/full-stack/src/server.ts +30 -0
  52. package/src/templates/full-stack/test/app.test.ts +144 -0
  53. package/src/templates/full-stack/test/db.test.ts +169 -0
  54. package/src/templates/full-stack/tsconfig.json +19 -0
  55. package/src/templates/shibumi.ts +809 -0
  56. package/src/templates/ship.ts +2056 -0
  57. package/src/templates/static/README.md +10 -0
  58. package/src/templates/static/agents.md +31 -0
  59. package/src/templates/static/gitignore +2 -0
  60. package/src/templates/static/package.json +17 -0
  61. package/src/templates/static/public/404.html +15 -0
  62. package/src/templates/static/public/index.html +16 -0
  63. package/src/templates/static/public/style.css +50 -0
  64. package/src/templates/static/scripts/preview.ts +42 -0
  65. package/src/templates/web/.dockerignore +12 -0
  66. package/src/templates/web/Dockerfile +24 -0
  67. package/src/templates/web/README.md +10 -0
  68. package/src/templates/web/agents.md +54 -0
  69. package/src/templates/web/bun.lock +45 -0
  70. package/src/templates/web/compose.yaml +17 -0
  71. package/src/templates/web/gitignore +5 -0
  72. package/src/templates/web/package.json +33 -0
  73. package/src/templates/web/public/app.js +11 -0
  74. package/src/templates/web/public/style.css +60 -0
  75. package/src/templates/web/public/vendor/alpine-csp-3.16.2.min.js +23 -0
  76. package/src/templates/web/src/app.ts +96 -0
  77. package/src/templates/web/src/env.ts +21 -0
  78. package/src/templates/web/src/server.ts +23 -0
  79. package/src/templates/web/test/app.test.ts +137 -0
  80. package/src/templates/web/tsconfig.json +13 -0
@@ -0,0 +1,10 @@
1
+ # Shibumi static site
2
+
3
+ Committed files in `public/`, served by a 1.4 MB pinned static image on your VPS.
4
+
5
+ ```sh
6
+ bun dev # preview locally
7
+ bun ship # deploy
8
+ ```
9
+
10
+ See `agents.md` for the project rules.
@@ -0,0 +1,31 @@
1
+ # Agent Notes
2
+
3
+ Static site generated by create-shibumi. Plain committed files in `public/`, no build step, no framework.
4
+
5
+ ## Commands
6
+
7
+ ```sh
8
+ bun dev # preview public/ locally (scripts/preview.ts, dev only)
9
+ bun ship:setup # configure the deploy target (preconfigured: static, public/)
10
+ bun ship # verify public/, build the static image, upload, deploy
11
+ ```
12
+
13
+ ## Routes and templates
14
+
15
+ Every file in `public/` is a route. `public/index.html` is `/`, `public/404.html` answers unknown paths. There are no templates or generators; add a generator later by pointing its output directory at `bun ship:setup --static --output-dir <dir> --build-script <script>`.
16
+
17
+ ## Trust boundaries and validation
18
+
19
+ Before every image build, Ship verifies the output directory from a clean checkout of the committed HEAD: inside the project root, non-empty, `index.html` present, no symlinks. The image contains only that verified output plus a pinned BusyBox httpd running as an unprivileged user; there is no runtime code in production.
20
+
21
+ ## Database
22
+
23
+ None. Static files only.
24
+
25
+ ## Files Ship generates
26
+
27
+ `bun ship:setup` writes `Dockerfile`, `compose.yaml`, `.dockerignore`, and `shibumi-server.json` (all committed); static settings live as `dev.shibumistack.static.*` labels in `compose.yaml`. SSH targets stay in `~/.config/shibumi/config.json`, never committed.
28
+
29
+ ## Checks required before commit
30
+
31
+ `public/index.html` must exist; without a build script, `public/` must be committed or `bun ship` refuses to package it.
@@ -0,0 +1,2 @@
1
+ node_modules/
2
+ .DS_Store
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "shibumi-static-site",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "bun scripts/preview.ts",
8
+ "ship": "bun scripts/ship.ts",
9
+ "ship:setup": "bun scripts/ship.ts --setup --static --output-dir public",
10
+ "ship:update": "bun scripts/ship.ts --update",
11
+ "ship:status": "bun scripts/ship.ts --status",
12
+ "ship:logs": "bun scripts/ship.ts --logs"
13
+ },
14
+ "engines": {
15
+ "bun": ">=1.4.0"
16
+ }
17
+ }
@@ -0,0 +1,15 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Not found</title>
7
+ <link rel="stylesheet" href="/style.css" />
8
+ </head>
9
+ <body>
10
+ <main>
11
+ <h1>Not found.</h1>
12
+ <p><a href="/">Back to the start.</a></p>
13
+ </main>
14
+ </body>
15
+ </html>
@@ -0,0 +1,16 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Shibumi static site</title>
7
+ <link rel="stylesheet" href="/style.css" />
8
+ </head>
9
+ <body>
10
+ <main>
11
+ <h1>It ships.</h1>
12
+ <p>To get started, open <code>public/index.html</code>; this page is that file. Edit, commit, run <code>bun ship</code>.</p>
13
+ <p>Using Astro, Jekyll, Eleventy, or another generator? Point its output here, or run <code>bun ship:setup</code> in that project instead; the Ship client packages any verified build directory.</p>
14
+ </main>
15
+ </body>
16
+ </html>
@@ -0,0 +1,50 @@
1
+ :root {
2
+ color-scheme: light dark;
3
+ --paper: #f5f0e4;
4
+ --ink: #1b130f;
5
+ --accent: #e95f19;
6
+ }
7
+
8
+ @media (prefers-color-scheme: dark) {
9
+ :root {
10
+ --paper: #1b130f;
11
+ --ink: #f5f0e4;
12
+ --accent: #ff8648;
13
+ }
14
+ }
15
+
16
+ body {
17
+ margin: 0;
18
+ background: var(--paper);
19
+ color: var(--ink);
20
+ font-family: system-ui, sans-serif;
21
+ font-size: 1.125rem;
22
+ line-height: 1.6;
23
+ }
24
+
25
+ main {
26
+ max-width: 40rem;
27
+ margin: 0 auto;
28
+ padding: 4rem 1.5rem;
29
+ }
30
+
31
+ h1 {
32
+ font-family: Georgia, "Times New Roman", serif;
33
+ font-weight: 500;
34
+ }
35
+
36
+ a {
37
+ color: inherit;
38
+ text-decoration: underline;
39
+ text-decoration-color: color-mix(in srgb, var(--accent) 30%, transparent);
40
+ text-underline-offset: 0.18em;
41
+ }
42
+
43
+ a:hover {
44
+ text-decoration-color: var(--accent);
45
+ }
46
+
47
+ code {
48
+ font-family: ui-monospace, monospace;
49
+ font-size: 0.9em;
50
+ }
@@ -0,0 +1,42 @@
1
+ // Local preview of public/. Production serving is the pinned static image
2
+ // that bun ship builds; this file never ships.
3
+ import { realpathSync } from "node:fs";
4
+ import { join, normalize } from "node:path";
5
+
6
+ const ROOT = join(import.meta.dir, "..", "public");
7
+ function withHeaders(res: Response): Response {
8
+ res.headers.set("X-Content-Type-Options", "nosniff");
9
+ res.headers.set("X-Frame-Options", "DENY");
10
+ res.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
11
+ return res;
12
+ }
13
+ const server = Bun.serve({
14
+ port: Number(process.env.PORT) || 4173,
15
+ async fetch(request) {
16
+ if (request.method !== "GET" && request.method !== "HEAD") return withHeaders(new Response("Not found", { status: 404 }));
17
+ let pathname;
18
+ try {
19
+ pathname = decodeURIComponent(new URL(request.url).pathname);
20
+ } catch {
21
+ return withHeaders(new Response("Bad request", { status: 400 }));
22
+ }
23
+ const safe = normalize(pathname).replaceAll("\\", "/");
24
+ if (safe.includes("..")) return withHeaders(new Response("Not found", { status: 404 }));
25
+ const candidate = safe.endsWith("/") ? join(ROOT, safe, "index.html") : join(ROOT, safe);
26
+ if (!candidate.startsWith(ROOT)) return withHeaders(new Response("Not found", { status: 404 }));
27
+ let resolved;
28
+ try {
29
+ resolved = realpathSync(candidate);
30
+ } catch {
31
+ resolved = undefined;
32
+ }
33
+ // realpath containment: a symlink inside public/ must not escape it.
34
+ if (resolved && (resolved === realpathSync(ROOT) || resolved.startsWith(realpathSync(ROOT) + "/"))) {
35
+ const file = Bun.file(resolved);
36
+ if (await file.exists()) return withHeaders(new Response(file));
37
+ }
38
+ const notFound = Bun.file(join(ROOT, "404.html"));
39
+ return withHeaders(new Response((await notFound.exists()) ? notFound : "Not found", { status: 404 }));
40
+ },
41
+ });
42
+ console.log(`Previewing public/ on http://localhost:${server.port}`);
@@ -0,0 +1,12 @@
1
+ .git
2
+ node_modules
3
+ dist
4
+ test
5
+ scripts
6
+ .env
7
+ .env.*
8
+ *.md
9
+ .gitignore
10
+ .dockerignore
11
+ compose.yaml
12
+ shibumi-server.json
@@ -0,0 +1,24 @@
1
+ FROM oven/bun:1-alpine AS deps
2
+ WORKDIR /app
3
+ COPY package.json bun.lock ./
4
+ RUN bun install --frozen-lockfile --production
5
+
6
+ # Major-pinned: minors and patches flow, a future Bun 2 cannot break deploys.
7
+ FROM oven/bun:1-alpine AS build
8
+ WORKDIR /app
9
+ COPY package.json bun.lock ./
10
+ RUN bun install --frozen-lockfile
11
+ COPY tsconfig.json ./
12
+ COPY src ./src
13
+ RUN bun run build
14
+
15
+ FROM oven/bun:1-alpine
16
+ WORKDIR /app
17
+ COPY --from=build /app/dist ./dist
18
+ COPY public ./public
19
+ COPY package.json ./
20
+ # Production mode: extensions gate security behavior on this. Never unset it.
21
+ ENV NODE_ENV=production
22
+ USER bun
23
+ EXPOSE 3000
24
+ CMD ["bun", "dist/server.js"]
@@ -0,0 +1,10 @@
1
+ # Shibumi web app
2
+
3
+ Bun, Hono, Zod, and Alpine. Everything here is your source.
4
+
5
+ ```sh
6
+ bun install
7
+ bun dev
8
+ ```
9
+
10
+ Deploy to your VPS with `bun ship:setup`, then `bun ship`. See `agents.md` for the project map and the rules tests enforce.
@@ -0,0 +1,54 @@
1
+ # Agent Notes
2
+
3
+ Bun web app generated by create-shibumi. Hono routes, Zod validation, Alpine behavior, no framework runtime. All source is owned by this project; edit anything.
4
+
5
+ ## Commands
6
+
7
+ ```sh
8
+ bun dev # Ship wrapper starts hot reload on the configured app port
9
+ bun dev:app # plain watch mode on PORT (default 3000)
10
+ bun test # route and security-header tests
11
+ bun run check # TypeScript check
12
+ bun run build # bundle src/server.ts to dist/server.js
13
+ bun ship # build image, upload, deploy via shibumi-server
14
+ bun ship:setup # configure the deploy target
15
+ bun run shibumi add <name> # install an extension (auth, email); --dry-run previews
16
+ ```
17
+
18
+ ## Routes and templates
19
+
20
+ - `src/app.ts`: all routes and the HTML page. `src/server.ts`: startup and graceful shutdown. `src/env.ts`: environment schema.
21
+ - Static assets under `public/`, served at `/public/*`.
22
+ - Alpine is the vendored CSP build (`public/vendor/alpine-csp-*.min.js`): register components with `Alpine.data()` in `public/app.js` and reference methods by name in markup; inline expressions do not evaluate.
23
+
24
+ ## Trust boundaries and validation
25
+
26
+ - Every environment variable goes through the Zod schema in `src/env.ts`; the app never reads `process.env` elsewhere.
27
+ - Every request input (query, params, body) is validated with Zod at the route before use; `/api/hello` is the pattern.
28
+ - The response header set in `SECURITY_HEADERS` (`src/app.ts`) is asserted verbatim by tests on success, error, API, and static responses; change both together. The CSP allows no inline or eval'd script.
29
+ - Add no unauthenticated mutation endpoints (POST/PUT/PATCH/DELETE); tests fail if a route registers one. Add authentication first.
30
+
31
+ ## Database
32
+
33
+ This template has no database. The full-stack template adds Drizzle and SQLite under `/data`.
34
+
35
+ ## Files Ship generates
36
+
37
+ `bun ship:setup` writes `shibumi-server.json` (committed) and stores SSH targets in `~/.config/shibumi/config.json` (never committed). `scripts/ship.ts` is owned source, vendored at generation time; update it with `bun ship:update`.
38
+
39
+ ## Secrets and runtime config
40
+
41
+ Set production env vars (secrets, per-deploy config) on the server, never in git or the image:
42
+
43
+ ```sh
44
+ bun ship:env set KEY=VALUE # one or more
45
+ bun ship:env import .env.production # bulk from a local file
46
+ bun ship:env list # names only
47
+ bun ship:env rm KEY
48
+ ```
49
+
50
+ Values are stored per-app on the server and injected at deploy; run `bun ship` to apply. Non-secret tunables that belong in git live in `src/config/*.yaml` instead.
51
+
52
+ ## Checks required before commit
53
+
54
+ `bun test` and `bun run check` must pass. The Docker image copies only `dist/`, `public/`, and `package.json`; if you add runtime files elsewhere, update the Dockerfile and `.dockerignore` together.
@@ -0,0 +1,45 @@
1
+ {
2
+ "lockfileVersion": 2,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "shibumi-web-app",
7
+ "dependencies": {
8
+ "hono": "4.13.3",
9
+ "zod": "4.4.3",
10
+ },
11
+ "devDependencies": {
12
+ "@clack/prompts": "1.7.0",
13
+ "@types/bun": "latest",
14
+ "typescript": "5.9.2",
15
+ },
16
+ },
17
+ },
18
+ "packages": {
19
+ "@clack/core": ["@clack/core@1.4.3", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ=="],
20
+
21
+ "@clack/prompts": ["@clack/prompts@1.7.0", "", { "dependencies": { "@clack/core": "1.4.3", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A=="],
22
+
23
+ "@types/bun": ["@types/bun@1.4.0", "", { "dependencies": { "bun-types": "1.4.0" } }, "sha512-K+lZULY23vRgK/CfTjFIV+tyifaNdSMlPh9j+6mQ/cLfpOznLyAuzgV/JQysyECpkBQLVMSyvjlr2fBUSA9wFQ=="],
24
+
25
+ "@types/node": ["@types/node@26.2.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg=="],
26
+
27
+ "bun-types": ["bun-types@1.4.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-iIKw23BspnQQYd3prITOBxeUsxBHnwzX6YJfGMuNOZzeNcMmVqzIIVGRm1l69ogaPQmb4wB6BN8mA5bE9YuC5Q=="],
28
+
29
+ "fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="],
30
+
31
+ "fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="],
32
+
33
+ "fast-wrap-ansi": ["fast-wrap-ansi@0.2.2", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q=="],
34
+
35
+ "hono": ["hono@4.13.3", "", {}, "sha512-r8AO2mYHoLxSHkgafNeC/BXyb2vWRxD3jem4Ts+ptav8oTG5FIRifAjuJEmZI4bSvvc2ns0GxmIYiZnHqN3mMw=="],
36
+
37
+ "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
38
+
39
+ "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
40
+
41
+ "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
42
+
43
+ "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
44
+ }
45
+ }
@@ -0,0 +1,17 @@
1
+ services:
2
+ app:
3
+ build: .
4
+ ports:
5
+ - "127.0.0.1:${SHIBUMI_PORT:-9001}:3000"
6
+ restart: unless-stopped
7
+ healthcheck:
8
+ test: ["CMD", "wget", "-q", "-T", "5", "-O", "/dev/null", "http://127.0.0.1:3000/healthz"]
9
+ interval: 60s
10
+ timeout: 10s
11
+ retries: 3
12
+ start_period: 10s
13
+ deploy:
14
+ resources:
15
+ limits:
16
+ cpus: "1.0"
17
+ memory: 512M
@@ -0,0 +1,5 @@
1
+ node_modules/
2
+ dist/
3
+ .env
4
+ .env.*
5
+ .DS_Store
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "shibumi-web-app",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "bun scripts/ship.ts --dev",
8
+ "dev:app": "bun --watch src/server.ts",
9
+ "start": "bun dist/server.js",
10
+ "build": "bun build src/server.ts --outdir dist --target bun",
11
+ "test": "bun test",
12
+ "check": "tsc --noEmit",
13
+ "ship": "bun scripts/ship.ts",
14
+ "ship:setup": "bun scripts/ship.ts --setup",
15
+ "ship:update": "bun scripts/ship.ts --update",
16
+ "ship:status": "bun scripts/ship.ts --status",
17
+ "ship:logs": "bun scripts/ship.ts --logs",
18
+ "ship:env": "bun scripts/ship.ts --env",
19
+ "shibumi": "bun scripts/shibumi.ts"
20
+ },
21
+ "dependencies": {
22
+ "hono": "4.13.3",
23
+ "zod": "4.4.3"
24
+ },
25
+ "devDependencies": {
26
+ "@clack/prompts": "1.7.0",
27
+ "@types/bun": "1.4.0",
28
+ "typescript": "5.9.2"
29
+ },
30
+ "engines": {
31
+ "bun": ">=1.4.0"
32
+ }
33
+ }
@@ -0,0 +1,11 @@
1
+ // Alpine behavior lives here. The vendored build is the CSP-safe variant:
2
+ // register components with Alpine.data() and reference methods by name in
3
+ // markup; inline expressions are not evaluated.
4
+ document.addEventListener("alpine:init", () => {
5
+ Alpine.data("counter", () => ({
6
+ count: 0,
7
+ inc() {
8
+ this.count++;
9
+ },
10
+ }));
11
+ });
@@ -0,0 +1,60 @@
1
+ :root {
2
+ color-scheme: light dark;
3
+ --paper: #f5f0e4;
4
+ --ink: #1b130f;
5
+ --accent: #e95f19;
6
+ }
7
+
8
+ @media (prefers-color-scheme: dark) {
9
+ :root {
10
+ --paper: #1b130f;
11
+ --ink: #f5f0e4;
12
+ --accent: #ff8648;
13
+ }
14
+ }
15
+
16
+ body {
17
+ margin: 0;
18
+ background: var(--paper);
19
+ color: var(--ink);
20
+ font-family: system-ui, sans-serif;
21
+ font-size: 1.125rem;
22
+ line-height: 1.6;
23
+ }
24
+
25
+ main {
26
+ max-width: 40rem;
27
+ margin: 0 auto;
28
+ padding: 4rem 1.5rem;
29
+ }
30
+
31
+ h1 {
32
+ font-family: Georgia, "Times New Roman", serif;
33
+ font-weight: 500;
34
+ }
35
+
36
+ a {
37
+ color: inherit;
38
+ text-decoration: underline;
39
+ text-decoration-color: color-mix(in srgb, var(--accent) 30%, transparent);
40
+ text-underline-offset: 0.18em;
41
+ }
42
+
43
+ a:hover {
44
+ text-decoration-color: var(--accent);
45
+ }
46
+
47
+ button {
48
+ font: inherit;
49
+ padding: 0.4rem 1rem;
50
+ border: 1px solid var(--ink);
51
+ background: transparent;
52
+ color: inherit;
53
+ border-radius: 4px;
54
+ cursor: pointer;
55
+ }
56
+
57
+ output {
58
+ margin-left: 0.75rem;
59
+ font-variant-numeric: tabular-nums;
60
+ }