create-shibumi 0.2.7 → 0.2.9

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 (34) hide show
  1. package/package.json +1 -1
  2. package/scripts/shibumi.lock.json +2 -2
  3. package/scripts/ship.lock.json +2 -2
  4. package/src/templates/blog/agents.md +3 -1
  5. package/src/templates/blog/bun.lock +1 -0
  6. package/src/templates/blog/package.json +2 -1
  7. package/src/templates/blog/public/favicon.svg +1 -0
  8. package/src/templates/blog/public/style.css +77 -4
  9. package/src/templates/blog/src/components/BaseHead.astro +4 -0
  10. package/src/templates/blog/src/content/blog/one-vps-is-plenty.md +7 -5
  11. package/src/templates/blog/src/content/blog/own-your-source.md +9 -5
  12. package/src/templates/blog/src/content/blog/writing-for-agents.md +7 -5
  13. package/src/templates/blog/src/content.config.ts +3 -0
  14. package/src/templates/blog/src/layouts/Base.astro +12 -1
  15. package/src/templates/blog/src/pages/index.astro +5 -2
  16. package/src/templates/blog/src/pages/llms.txt.ts +1 -1
  17. package/src/templates/blog/src/pages/posts/[id].astro +2 -1
  18. package/src/templates/blog/src/pages/posts/[id].md.ts +1 -1
  19. package/src/templates/blog/src/pages/rss.xml.ts +1 -1
  20. package/src/templates/full-stack/public/favicon.svg +1 -0
  21. package/src/templates/full-stack/public/style.css +11 -6
  22. package/src/templates/full-stack/public/vendor/shibumi.css +18 -14
  23. package/src/templates/full-stack/src/app.ts +5 -3
  24. package/src/templates/shibumi.ts +2 -2
  25. package/src/templates/ship.ts +27 -6
  26. package/src/templates/static/bun.lock +25 -0
  27. package/src/templates/static/package.json +4 -1
  28. package/src/templates/static/public/favicon.svg +1 -0
  29. package/src/templates/static/public/index.html +11 -3
  30. package/src/templates/static/public/style.css +66 -20
  31. package/src/templates/web/public/favicon.svg +1 -0
  32. package/src/templates/web/public/style.css +11 -6
  33. package/src/templates/web/public/vendor/shibumi.css +18 -14
  34. package/src/templates/web/src/app.ts +4 -2
@@ -25,7 +25,7 @@ const SERVER_HOSTNAME = /^[A-Za-z0-9](?:[A-Za-z0-9.-]{0,251}[A-Za-z0-9])?$/;
25
25
  const COMMIT = /^[a-f0-9]{40}$/;
26
26
  const SERVER_CLI = "~/.local/bin/shibumi-server";
27
27
  const LATEST_SOURCE = "https://shibumistack.dev/ship/latest.ts";
28
- const CURRENT_SOURCE = "https://shibumistack.dev/ship/v46.ts";
28
+ const CURRENT_SOURCE = "https://shibumistack.dev/ship/v47.ts";
29
29
  let sshControlDirectory: string | undefined;
30
30
  let sshControlTarget: string | undefined;
31
31
 
@@ -96,6 +96,7 @@ interface ShipOptions {
96
96
  outputDir?: string;
97
97
  buildScript?: string;
98
98
  spa: boolean;
99
+ noSpa?: boolean;
99
100
  }
100
101
 
101
102
  export interface StaticSiteConfig {
@@ -160,6 +161,7 @@ export function parseShipArgs(args: string[]): ShipOptions {
160
161
  else if (argument === "--yes" || argument === "-y") parsed.yes = true;
161
162
  else if (argument === "--static") parsed.staticSite = true;
162
163
  else if (argument === "--spa") parsed.spa = true;
164
+ else if (argument === "--no-spa") parsed.noSpa = true;
163
165
  else if (argument === "--server" || argument === "--domain" || argument === "--trigger" || argument === "--output-dir" || argument === "--build-script") {
164
166
  const value = args[index + 1];
165
167
  if (!value || value.startsWith("-")) throw new Error(`${argument} requires a value`);
@@ -175,8 +177,9 @@ export function parseShipArgs(args: string[]): ShipOptions {
175
177
  if ([parsed.setup, parsed.update, parsed.rollback, parsed.logs, parsed.status, parsed.dev].filter(Boolean).length > 1) throw new Error("choose only one ship action");
176
178
  if (parsed.rebuild && (parsed.setup || parsed.update || parsed.rollback || parsed.logs || parsed.status || parsed.dev)) throw new Error("--rebuild applies only to shipping");
177
179
  if (parsed.trigger && !parsed.setup) throw new Error("--trigger requires --setup");
178
- if ((parsed.staticSite || parsed.outputDir || parsed.buildScript || parsed.spa) && !parsed.setup) throw new Error("--static, --output-dir, --build-script, and --spa require --setup");
179
- if ((parsed.outputDir || parsed.buildScript || parsed.spa) && !parsed.staticSite) throw new Error("--output-dir, --build-script, and --spa require --static");
180
+ if (parsed.spa && parsed.noSpa) throw new Error("--spa and --no-spa are mutually exclusive");
181
+ if ((parsed.staticSite || parsed.outputDir || parsed.buildScript || parsed.spa || parsed.noSpa) && !parsed.setup) throw new Error("--static, --output-dir, --build-script, and --spa require --setup");
182
+ if ((parsed.outputDir || parsed.buildScript || parsed.spa || parsed.noSpa) && !parsed.staticSite) throw new Error("--output-dir, --build-script, and --spa require --static");
180
183
  if (parsed.outputDir) {
181
184
  const problem = staticOutputDirProblem(parsed.outputDir);
182
185
  if (problem) throw new Error(problem);
@@ -483,7 +486,6 @@ CMD ["bun", "run", "start"]
483
486
  environment:
484
487
  HOST: 0.0.0.0
485
488
  PORT: "3000"
486
- init: true
487
489
  restart: unless-stopped
488
490
  healthcheck:
489
491
  test: ["CMD", "bun", "-e", "fetch('http://127.0.0.1:3000/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
@@ -657,7 +659,6 @@ ENTRYPOINT ["/busybox", "httpd", "-f", "-p", "3000", "-h", "/www", "-c", "/www/h
657
659
  - "127.0.0.1:\${SHIBUMI_PORT:-9001}:3000"
658
660
  labels:
659
661
  ${staticComposeLabels(config)}
660
- init: true
661
662
  restart: unless-stopped
662
663
  healthcheck:
663
664
  test: ${healthcheck}
@@ -806,6 +807,7 @@ async function prepareCompose(): Promise<boolean> {
806
807
 
807
808
  if (wantStatic) {
808
809
  await generateStaticDeployment();
810
+ if (await offerGeneratedCommit(["Dockerfile", "compose.yaml", ".dockerignore", "scripts/static-server.ts", "package.json", "bun.lock"])) return false;
809
811
  outro("Review generated deployment files.\n\nNext: commit and push these changes, then run bun ship:setup.");
810
812
  return true;
811
813
  }
@@ -824,6 +826,8 @@ async function prepareCompose(): Promise<boolean> {
824
826
  written.push(name);
825
827
  }
826
828
  log.success(`Generated ${written.join(", ")}`);
829
+ log.info("Verify the app binds to 0.0.0.0 and reads PORT before shipping.");
830
+ if (await offerGeneratedCommit(written)) return false;
827
831
  outro("Review generated deployment files and verify app binds to 0.0.0.0 and reads PORT.\n\nNext: commit and push these changes, then run bun ship:setup.");
828
832
  return true;
829
833
  }
@@ -836,6 +840,23 @@ const SHIP_SCRIPTS = {
836
840
  "ship:logs": "bun scripts/ship.ts --logs",
837
841
  };
838
842
 
843
+
844
+ // After generating deployment files interactively, offer to commit and push
845
+ // them in the same run so setup continues without a manual rerun. Returns
846
+ // true when the files are committed and pushed.
847
+ async function offerGeneratedCommit(files: string[]): Promise<boolean> {
848
+ if (agentRun || !process.stdin.isTTY || !process.stdout.isTTY) return false;
849
+ const accepted = await confirm({ message: "Commit and push the generated files, then continue setup?", initialValue: true });
850
+ if (isCancel(accepted) || !accepted) return false;
851
+ const present: string[] = [];
852
+ for (const file of files) if (await Bun.file(join(root, file)).exists()) present.push(file);
853
+ await run(["git", "add", "--", ...present]);
854
+ await run(["git", "commit", "--only", "-m", "Add deployment configuration", "--", ...present], { inherit: true });
855
+ await run(["git", "push"], { inherit: true });
856
+ log.success("Committed and pushed deployment files");
857
+ return true;
858
+ }
859
+
839
860
  async function generateStaticDeployment(): Promise<void> {
840
861
  // Script-less generators (Jekyll) have no package.json; create a minimal one
841
862
  // so bun ship commands and an optional build script have a home.
@@ -880,7 +901,7 @@ async function generateStaticDeployment(): Promise<void> {
880
901
  if (problem) throw new Error(problem);
881
902
 
882
903
  let spa = options.spa;
883
- if (!spa && !agentRun && !options.yes) {
904
+ if (!spa && !options.noSpa && !agentRun && !options.yes) {
884
905
  const answer = await confirm({ message: "Single-page app? (unknown paths serve index.html)", initialValue: false });
885
906
  if (isCancel(answer)) throw new Error("setup cancelled");
886
907
  spa = answer;
@@ -0,0 +1,25 @@
1
+ {
2
+ "lockfileVersion": 2,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "shibumi-static-site",
7
+ "devDependencies": {
8
+ "@clack/prompts": "1.7.0",
9
+ },
10
+ },
11
+ },
12
+ "packages": {
13
+ "@clack/core": ["@clack/core@1.4.3", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ=="],
14
+
15
+ "@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=="],
16
+
17
+ "fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="],
18
+
19
+ "fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="],
20
+
21
+ "fast-wrap-ansi": ["fast-wrap-ansi@0.2.2", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q=="],
22
+
23
+ "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
24
+ }
25
+ }
@@ -6,12 +6,15 @@
6
6
  "scripts": {
7
7
  "dev": "bun scripts/preview.ts",
8
8
  "ship": "bun scripts/ship.ts",
9
- "ship:setup": "bun scripts/ship.ts --setup --static --output-dir public",
9
+ "ship:setup": "bun scripts/ship.ts --setup --static --output-dir public --no-spa",
10
10
  "ship:update": "bun scripts/ship.ts --update",
11
11
  "ship:status": "bun scripts/ship.ts --status",
12
12
  "ship:logs": "bun scripts/ship.ts --logs"
13
13
  },
14
14
  "engines": {
15
15
  "bun": ">=1.4.0"
16
+ },
17
+ "devDependencies": {
18
+ "@clack/prompts": "1.7.0"
16
19
  }
17
20
  }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><style>text{fill:#e95f19;font-family:"Hiragino Sans","Noto Sans JP",sans-serif}@media (prefers-color-scheme:dark){text{fill:#ff8648}}</style><text x="50" y="55" font-size="62" text-anchor="middle" dominant-baseline="central">渋</text></svg>
@@ -3,14 +3,22 @@
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <title>Shibumi static site</title>
6
+ <title>It ships · shibumi static</title>
7
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
8
+ <meta name="generator" content="create-shibumi (shibumistack.dev)" />
7
9
  <link rel="stylesheet" href="/style.css" />
8
10
  </head>
9
11
  <body>
10
12
  <main>
13
+ <p class="masthead"><span class="masthead-mark">渋み</span> shibumi static</p>
11
14
  <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>
15
+ <p class="lede">Every file in <code>public/</code> is a page on your site. You are looking at <code>public/index.html</code>.</p>
16
+ <ol class="steps">
17
+ <li>Edit this file. <code>bun dev</code> shows it locally.</li>
18
+ <li>Connect your server once with <code>bun ship:setup</code>.</li>
19
+ <li>After that, every deploy is <code>git commit</code> and <code>bun ship</code>.</li>
20
+ </ol>
21
+ <p class="aside">Writing with Astro, Jekyll, or Eleventy instead? Run <code>bun ship:setup</code> inside that project; Ship deploys any build folder it can verify.</p>
14
22
  </main>
15
23
  </body>
16
24
  </html>
@@ -1,36 +1,71 @@
1
+ /* Self-contained styles on the shibumi palette. Yours to edit. */
1
2
  :root {
2
3
  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
- }
4
+ --paper: light-dark(#f5f0e4, #1b130f);
5
+ --ink: light-dark(#272016, #eee5d7);
6
+ --muted: light-dark(#746b5d, #aaa092);
7
+ --faint: light-dark(rgba(39, 32, 22, 0.055), rgba(238, 229, 215, 0.07));
8
+ --line: light-dark(rgba(39, 32, 22, 0.14), rgba(238, 229, 215, 0.16));
9
+ --accent: light-dark(#e95f19, #ff8648);
10
+ --font-serif: ui-serif, Georgia, "Iowan Old Style", Cambria, serif;
11
+ --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
14
12
  }
15
13
 
16
14
  body {
17
15
  margin: 0;
18
16
  background: var(--paper);
19
17
  color: var(--ink);
20
- font-family: system-ui, sans-serif;
18
+ font-family: ui-sans-serif, system-ui, sans-serif;
21
19
  font-size: 1.125rem;
22
- line-height: 1.6;
20
+ line-height: 1.65;
23
21
  }
24
22
 
25
23
  main {
26
- max-width: 40rem;
24
+ max-width: 40.625rem;
27
25
  margin: 0 auto;
28
- padding: 4rem 1.5rem;
26
+ padding: clamp(3rem, 9vh, 6.5rem) 1.5rem 4rem;
27
+ }
28
+
29
+ .masthead {
30
+ display: flex;
31
+ align-items: baseline;
32
+ gap: 0.6rem;
33
+ margin: 0 0 3.5rem;
34
+ padding-bottom: 1rem;
35
+ border-bottom: 1px solid var(--line);
36
+ color: var(--muted);
37
+ font-size: 0.875rem;
38
+ letter-spacing: 0.04em;
39
+ }
40
+
41
+ .masthead-mark {
42
+ color: var(--accent);
43
+ font-size: 1rem;
29
44
  }
30
45
 
31
46
  h1 {
32
- font-family: Georgia, "Times New Roman", serif;
33
- font-weight: 500;
47
+ margin: 0 0 0.75rem;
48
+ font-family: var(--font-serif);
49
+ font-size: clamp(2.6rem, 7vw, 3.8rem);
50
+ font-weight: 450;
51
+ letter-spacing: -0.03em;
52
+ line-height: 1.05;
53
+ }
54
+
55
+ .lede {
56
+ margin: 0 0 3rem;
57
+ max-width: 34rem;
58
+ color: var(--muted);
59
+ font-size: 1.3125rem;
60
+ line-height: 1.55;
61
+ }
62
+
63
+ code {
64
+ padding: 0.12em 0.42em;
65
+ border-radius: 0.35rem;
66
+ background: var(--faint);
67
+ font-family: var(--font-mono);
68
+ font-size: 0.85em;
34
69
  }
35
70
 
36
71
  a {
@@ -44,7 +79,18 @@ a:hover {
44
79
  text-decoration-color: var(--accent);
45
80
  }
46
81
 
47
- code {
48
- font-family: ui-monospace, monospace;
49
- font-size: 0.9em;
82
+ .steps {
83
+ margin: 0 0 2.5rem;
84
+ padding-left: 1.4rem;
85
+ }
86
+
87
+ .steps li {
88
+ margin-bottom: 0.6rem;
89
+ }
90
+
91
+ .aside {
92
+ padding-top: 1.5rem;
93
+ border-top: 1px solid var(--line);
94
+ color: var(--muted);
95
+ font-size: 1rem;
50
96
  }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><style>text{fill:#e95f19;font-family:"Hiragino Sans","Noto Sans JP",sans-serif}@media (prefers-color-scheme:dark){text{fill:#ff8648}}</style><text x="50" y="55" font-size="62" text-anchor="middle" dominant-baseline="central">渋</text></svg>
@@ -2,6 +2,12 @@
2
2
  Edit freely; this file is yours. Tokens: --paper --ink --muted --line
3
3
  --accent, type scale --text-xs..--text-2xl, --font-sans/serif/mono. */
4
4
 
5
+ /* The vendored shibumi.css sets a kozo paper background for the sites; apps
6
+ stay flat, and this override also stops the browser fetching the image. */
7
+ body {
8
+ background-image: none;
9
+ }
10
+
5
11
  .scaffold {
6
12
  max-width: 40.625rem;
7
13
  margin: 0 auto;
@@ -83,7 +89,7 @@
83
89
  border-top: 1px solid var(--line);
84
90
  }
85
91
 
86
- main .endpoints a {
92
+ main .endpoints a:not(.btn):not(.button) {
87
93
  display: flex;
88
94
  align-items: baseline;
89
95
  justify-content: space-between;
@@ -95,13 +101,12 @@ main .endpoints a {
95
101
  transition: background 160ms ease;
96
102
  }
97
103
 
98
- main .endpoints a:hover,
99
- main .endpoints a:focus-visible {
104
+ main .endpoints a:not(.btn):not(.button):hover,
105
+ main .endpoints a:not(.btn):not(.button):focus-visible {
100
106
  background: var(--faint);
101
107
  }
102
108
 
103
- .endpoint-path {
104
- font-family: var(--font-mono);
109
+ main .endpoints code {
105
110
  font-size: var(--text-sm);
106
111
  }
107
112
  /* Inline code as rounded chips on the faint layer. */
@@ -114,7 +119,7 @@ main .endpoints a:focus-visible {
114
119
  }
115
120
 
116
121
 
117
- main .endpoints a:hover .endpoint-path {
122
+ main .endpoints a:hover code {
118
123
  color: var(--accent);
119
124
  }
120
125
 
@@ -60,19 +60,23 @@ body {
60
60
  background: var(--bg);
61
61
  }
62
62
 
63
- h1, h2, h3 {
64
- font-family: var(--font-serif);
63
+ /* Kozo paper texture, light theme only; dark stays flat ink. The image lives
64
+ next to this stylesheet in every consumer, hence the relative URL. */
65
+ body {
66
+ background-image: url("kozo.webp");
67
+ background-size: cover;
68
+ background-position: center;
69
+ background-attachment: fixed;
70
+ background-repeat: no-repeat;
65
71
  }
72
+ @media (prefers-color-scheme: dark) {
73
+ body { background-image: none; }
74
+ }
75
+ [data-theme="light"] body { background-image: url("kozo.webp"); }
76
+ [data-theme="dark"] body { background-image: none; }
66
77
 
67
- /* theme-independent noise texture, copied from shibumi-server */
68
- body::before {
69
- content: "";
70
- position: fixed;
71
- inset: 0;
72
- z-index: -1;
73
- pointer-events: none;
74
- opacity: 0.33;
75
- background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.88' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.08'/%3E%3C/svg%3E");
78
+ h1, h2, h3 {
79
+ font-family: var(--font-serif);
76
80
  }
77
81
 
78
82
  a { color: inherit; }
@@ -123,9 +127,9 @@ main a:not(.btn):not(.button):focus-visible {
123
127
  width: 100vw;
124
128
  height: 100%;
125
129
  transform: translateX(-50%);
126
- background: light-dark(rgb(245 240 228 / 78%), rgb(27 19 15 / 72%));
127
- backdrop-filter: blur(1.125rem);
128
- -webkit-backdrop-filter: blur(1.125rem);
130
+ background: light-dark(rgb(245 240 228 / 0%), rgb(27 19 15 / 72%));
131
+ backdrop-filter: blur(1.25rem);
132
+ -webkit-backdrop-filter: blur(1.25rem);
129
133
  }
130
134
 
131
135
  .mark {
@@ -63,6 +63,8 @@ const page = `<!doctype html>
63
63
  <meta charset="utf-8" />
64
64
  <meta name="viewport" content="width=device-width, initial-scale=1" />
65
65
  <title>Web app · shibumi</title>
66
+ <link rel="icon" type="image/svg+xml" href="/public/favicon.svg" />
67
+ <meta name="generator" content="create-shibumi (shibumistack.dev)" />
66
68
  <link rel="stylesheet" href="/public/vendor/shibumi.css" />
67
69
  <link rel="stylesheet" href="/public/style.css" />
68
70
  <!-- app.js must load before Alpine so the alpine:init listener exists
@@ -81,8 +83,8 @@ const page = `<!doctype html>
81
83
  <span class="demo-hint">client state without a build step</span>
82
84
  </section>
83
85
  <ul class="endpoints">
84
- <li><a href="/api/hello?name=you"><span class="endpoint-path">GET /api/hello</span><span>query validated with Zod</span></a></li>
85
- <li><a href="/healthz"><span class="endpoint-path">GET /healthz</span><span>the check every deploy waits for</span></a></li>
86
+ <li><a href="/api/hello?name=you"><code>GET /api/hello</code><span>query validated with Zod</span></a></li>
87
+ <li><a href="/healthz"><code>GET /healthz</code><span>the check every deploy waits for</span></a></li>
86
88
  </ul>
87
89
  <footer class="colophon">
88
90
  <p>This page lives in <code>src/app.ts</code>. House rules for coding agents are in <code>agents.md</code>. Add features with <code>bun shi add email</code>.</p>