@pylonsync/create-pylon 0.4.29 → 0.5.2

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 (83) hide show
  1. package/bin/create-pylon.js +27 -5
  2. package/package.json +1 -1
  3. package/templates/_root/AGENTS.md +1 -1
  4. package/templates/agency/AGENTS.md +1 -1
  5. package/templates/agency/app/llms.ts +31 -0
  6. package/templates/agency/app/not-found.tsx +44 -0
  7. package/templates/agency/app.ts +3 -0
  8. package/templates/ai-chat/AGENTS.md +1 -1
  9. package/templates/ai-chat/app/llms.ts +30 -0
  10. package/templates/ai-chat/app/not-found.tsx +44 -0
  11. package/templates/ai-chat/app/robots.ts +14 -0
  12. package/templates/ai-chat/app/sitemap.ts +11 -0
  13. package/templates/ai-chat/app.ts +3 -0
  14. package/templates/ai-studio/AGENTS.md +1 -1
  15. package/templates/ai-studio/app/llms.ts +30 -0
  16. package/templates/ai-studio/app/not-found.tsx +44 -0
  17. package/templates/ai-studio/app/robots.ts +14 -0
  18. package/templates/ai-studio/app/sitemap.ts +11 -0
  19. package/templates/ai-studio/app.ts +3 -0
  20. package/templates/backend/b2b/apps/api/schema.ts +3 -0
  21. package/templates/backend/barebones/apps/api/schema.ts +3 -0
  22. package/templates/backend/chat/apps/api/schema.ts +3 -0
  23. package/templates/backend/consumer/apps/api/schema.ts +3 -0
  24. package/templates/backend/todo/apps/api/schema.ts +3 -0
  25. package/templates/barebones/AGENTS.md +1 -1
  26. package/templates/barebones/app/llms.ts +30 -0
  27. package/templates/barebones/app.ts +3 -0
  28. package/templates/chat/AGENTS.md +1 -1
  29. package/templates/chat/app/llms.ts +30 -0
  30. package/templates/chat/app.ts +3 -0
  31. package/templates/consumer/AGENTS.md +1 -1
  32. package/templates/consumer/app/llms.ts +30 -0
  33. package/templates/consumer/app.ts +3 -0
  34. package/templates/creator/AGENTS.md +1 -1
  35. package/templates/creator/app/llms.ts +30 -0
  36. package/templates/creator/app/not-found.tsx +44 -0
  37. package/templates/creator/app.ts +3 -0
  38. package/templates/crm/AGENTS.md +1 -1
  39. package/templates/crm/app/llms.ts +32 -0
  40. package/templates/crm/app.ts +3 -0
  41. package/templates/directory/AGENTS.md +1 -1
  42. package/templates/directory/app/llms.ts +31 -0
  43. package/templates/directory/app/not-found.tsx +44 -0
  44. package/templates/directory/app.ts +3 -0
  45. package/templates/helpdesk/AGENTS.md +1 -1
  46. package/templates/helpdesk/app/llms.ts +31 -0
  47. package/templates/helpdesk/app.ts +3 -0
  48. package/templates/inventory/AGENTS.md +1 -1
  49. package/templates/inventory/app/llms.ts +31 -0
  50. package/templates/inventory/app.ts +3 -0
  51. package/templates/invoices/AGENTS.md +1 -1
  52. package/templates/invoices/app/llms.ts +31 -0
  53. package/templates/invoices/app.ts +3 -0
  54. package/templates/local-service/AGENTS.md +1 -1
  55. package/templates/local-service/app/llms.ts +30 -0
  56. package/templates/local-service/app/not-found.tsx +44 -0
  57. package/templates/local-service/app.ts +3 -0
  58. package/templates/marketplace/AGENTS.md +1 -1
  59. package/templates/marketplace/app/llms.ts +32 -0
  60. package/templates/marketplace/app.ts +3 -0
  61. package/templates/marketplace/client/market.ts +4 -1
  62. package/templates/projects/AGENTS.md +1 -1
  63. package/templates/projects/app/llms.ts +31 -0
  64. package/templates/projects/app.ts +3 -0
  65. package/templates/restaurant/AGENTS.md +1 -1
  66. package/templates/restaurant/app/llms.ts +30 -0
  67. package/templates/restaurant/app/not-found.tsx +44 -0
  68. package/templates/restaurant/app.ts +3 -0
  69. package/templates/saas/AGENTS.md +1 -1
  70. package/templates/saas/app/llms.ts +30 -0
  71. package/templates/saas/app/not-found.tsx +44 -0
  72. package/templates/saas/app.ts +3 -0
  73. package/templates/shop/AGENTS.md +1 -1
  74. package/templates/shop/app/llms.ts +31 -0
  75. package/templates/shop/app/not-found.tsx +44 -0
  76. package/templates/shop/app.ts +3 -0
  77. package/templates/todo/AGENTS.md +1 -1
  78. package/templates/todo/app/llms.ts +30 -0
  79. package/templates/todo/app.ts +3 -0
  80. package/templates/waitlist/AGENTS.md +1 -1
  81. package/templates/waitlist/app/llms.ts +30 -0
  82. package/templates/waitlist/app/not-found.tsx +44 -0
  83. package/templates/waitlist/app.ts +3 -0
@@ -311,6 +311,28 @@ Examples:
311
311
  }
312
312
 
313
313
  const rl = createInterface({ input: stdin, output: stdout });
314
+
315
+ /**
316
+ * Prompt, treating Ctrl+D / Ctrl+C as a deliberate cancel.
317
+ *
318
+ * readline rejects the pending question with an AbortError, which at top
319
+ * level in an ESM script surfaces as an unhandled rejection — so someone who
320
+ * changes their mind at the template picker gets a Node stack trace instead
321
+ * of "cancelled". Nothing has been written to disk at this point, so say so
322
+ * and leave quietly.
323
+ */
324
+ async function ask(question) {
325
+ try {
326
+ return await rl.question(question);
327
+ } catch (err) {
328
+ if (err?.code === "ABORT_ERR") {
329
+ stdout.write("\nCancelled — nothing was created.\n");
330
+ rl.close();
331
+ exit(0);
332
+ }
333
+ throw err;
334
+ }
335
+ }
314
336
  // Non-interactive stdin (CI, piped, `| npm create …`): never block on a prompt.
315
337
  // Fall back to the same defaults the interactive path uses. The skill-install
316
338
  // below already gates on `stdin.isTTY`; the INPUT prompts must too — without
@@ -319,7 +341,7 @@ const rl = createInterface({ input: stdin, output: stdout });
319
341
  const isInteractive = !!stdin.isTTY;
320
342
  if (!projectName) {
321
343
  projectName =
322
- (isInteractive ? (await rl.question("Project name: ")).trim() : "") ||
344
+ (isInteractive ? (await ask("Project name: ")).trim() : "") ||
323
345
  "my-pylon-app";
324
346
  }
325
347
  if (!flags.template) {
@@ -329,7 +351,7 @@ if (!flags.template) {
329
351
  .join("\n");
330
352
  process.stdout.write(`\n${lines}\n`);
331
353
  const ans = (
332
- await rl.question(
354
+ await ask(
333
355
  `Template (${TEMPLATES_AVAILABLE.join(", ")}) [${DEFAULT_TEMPLATE}]: `,
334
356
  )
335
357
  )
@@ -352,7 +374,7 @@ if (!isUnified && !flags.platforms) {
352
374
  const supported = TEMPLATE_REGISTRY[flags.template].platforms.join(", ");
353
375
  const ans = isInteractive
354
376
  ? (
355
- await rl.question(
377
+ await ask(
356
378
  `Platforms for ${flags.template} (${supported}, comma-separated) [web]: `,
357
379
  )
358
380
  ).trim()
@@ -364,7 +386,7 @@ if (!flags.pm) {
364
386
  const def = detected ?? "bun";
365
387
  if (isInteractive) {
366
388
  const choice = (
367
- await rl.question(`Package manager (bun, pnpm, yarn, npm) [${def}]: `)
389
+ await ask(`Package manager (bun, pnpm, yarn, npm) [${def}]: `)
368
390
  )
369
391
  .trim()
370
392
  .toLowerCase();
@@ -376,7 +398,7 @@ if (!flags.pm) {
376
398
  if (flags.skill === undefined) {
377
399
  if (isInteractive) {
378
400
  const ans = (
379
- await rl.question(
401
+ await ask(
380
402
  "Add the Pylon skill to your coding agent (Claude Code / Codex / Cursor)? [Y/n]: ",
381
403
  )
382
404
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pylonsync/create-pylon",
3
- "version": "0.4.29",
3
+ "version": "0.5.2",
4
4
  "description": "Scaffold a new Pylon app — realtime backend + web/mobile/expo frontends in one command. Run via `npm create @pylonsync/pylon@latest`.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -5,7 +5,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
5
5
  ## Directory conventions
6
6
 
7
7
  **Unified SSR app:**
8
- - `app.ts`: data model + manifest (`entity()` + `field.*`, queries/actions/policies, `routes: await discoverAppRoutes()`). Ends with `console.log(JSON.stringify(manifest))`.
8
+ - `app.ts`: data model + manifest (`entity()` + `field.*`, queries/actions/policies, `routes: await discoverAppRoutes()`). Ends with `console.log(JSON.stringify(manifest))` — that line is the manifest handoff (the CLI parses this file's stdout), not debug output. Never remove it.
9
9
  - `app/`: file-based SSR routes. `app/page.tsx` → `/`, `app/about/page.tsx` → `/about`, `app/blog/[slug]/page.tsx` → `/blog/:slug`. A `(group)` directory is stripped from the URL — `app/(marketing)/about/page.tsx` still serves `/about` — so a group's `layout.tsx` gives one section its own chrome (nav, footer) without changing any path; routes outside the group render without it. `app/layout.tsx` is the document shell; `app/error.tsx` / `app/not-found.tsx` are boundaries.
10
10
  - `app/globals.css`: Tailwind v4 entrypoint (auto-compiled and injected).
11
11
  - `functions/`: server functions, one per file, `default`-exported.
@@ -5,7 +5,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
5
5
  ## Directory conventions
6
6
 
7
7
  **Unified SSR app:**
8
- - `app.ts`: data model + manifest (`entity()` + `field.*`, queries/actions/policies, `routes: await discoverAppRoutes()`). Ends with `console.log(JSON.stringify(manifest))`.
8
+ - `app.ts`: data model + manifest (`entity()` + `field.*`, queries/actions/policies, `routes: await discoverAppRoutes()`). Ends with `console.log(JSON.stringify(manifest))` — that line is the manifest handoff (the CLI parses this file's stdout), not debug output. Never remove it.
9
9
  - `app/`: file-based SSR routes. `app/page.tsx` → `/`, `app/about/page.tsx` → `/about`, `app/blog/[slug]/page.tsx` → `/blog/:slug`. A `(group)` directory is stripped from the URL — `app/(marketing)/about/page.tsx` still serves `/about` — so a group's `layout.tsx` gives one section its own chrome (nav, footer) without changing any path; routes outside the group render without it. `app/layout.tsx` is the document shell; `app/error.tsx` / `app/not-found.tsx` are boundaries.
10
10
  - `app/globals.css`: Tailwind v4 entrypoint (auto-compiled and injected).
11
11
  - `functions/`: server functions, one per file, `default`-exported.
@@ -0,0 +1,31 @@
1
+ import type { LlmsTxt } from "@pylonsync/react";
2
+
3
+ // app/llms.ts → served at /llms.txt, in llmstxt.org format.
4
+ //
5
+ // This is the file an AI agent reads FIRST to decide what this site is and
6
+ // whether it can help with the task in front of it. Keep the shape, rewrite the
7
+ // copy: name the jobs this app is right for and the exact call to make. Generic
8
+ // marketing copy does not read as guidance.
9
+ //
10
+ // The default export may be async, so it can enumerate pages from the database.
11
+ const SITE = process.env.SITE_URL ?? "http://localhost:4321";
12
+
13
+ export default function llms(): LlmsTxt {
14
+ return {
15
+ title: "__APP_NAME__",
16
+ summary: "An agency site: services, case studies, and an enquiry form.",
17
+ details: [
18
+ "Use this app to read case studies and to send an enquiry.",
19
+ "Every page here is also readable as markdown: add `.md` to any path, or send `Accept: text/markdown`.",
20
+ ],
21
+ sections: [
22
+ {
23
+ title: "Pages",
24
+ links: [
25
+ { title: "Home", url: `${SITE}` },
26
+ { title: "Work", url: `${SITE}/work` },
27
+ ],
28
+ },
29
+ ],
30
+ };
31
+ }
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+ import { Link, type NotFoundProps } from "@pylonsync/react";
3
+ import { Button } from "@/components/ui/button";
4
+
5
+ // `app/not-found.tsx` → rendered at HTTP 404 for any unmatched URL, and when a
6
+ // page calls `response.notFound()`.
7
+ //
8
+ // Give it real links. This boundary is also what an agent reads when it guesses
9
+ // a URL wrong: a markdown request for a missing page renders THIS file as
10
+ // markdown at 404, so the links below are how it finds its way back.
11
+ export default function NotFound(_props: NotFoundProps) {
12
+ return (
13
+ <div className="mx-auto flex min-h-[60vh] max-w-lg flex-col justify-center gap-6 px-4">
14
+ <section>
15
+ <h1 className="text-2xl font-semibold tracking-tight">Page not found</h1>
16
+ <p className="mt-2 text-muted-foreground">
17
+ That URL does not exist. Try one of these:
18
+ </p>
19
+ </section>
20
+ <ul className="space-y-2 text-sm">
21
+ <li>
22
+ <Link className="underline underline-offset-4" href="/">
23
+ Home
24
+ </Link>
25
+ </li>
26
+ <li>
27
+ <a className="underline underline-offset-4" href="/sitemap.xml">
28
+ Sitemap
29
+ </a>
30
+ </li>
31
+ <li>
32
+ <a className="underline underline-offset-4" href="/llms.txt">
33
+ llms.txt
34
+ </a>
35
+ </li>
36
+ </ul>
37
+ <div>
38
+ <Button asChild>
39
+ <Link href="/">Back to home</Link>
40
+ </Button>
41
+ </div>
42
+ </div>
43
+ );
44
+ }
@@ -300,6 +300,9 @@ const manifest = buildManifest({
300
300
  routes: await discoverAppRoutes(),
301
301
  });
302
302
 
303
+ // Not a debug leftover: the CLI runs `bun run app.ts` and parses stdout as
304
+ // your manifest (see crates/cli/src/bun.rs). Deleting this line breaks every
305
+ // pylon command that reads your schema.
303
306
  console.log(JSON.stringify(manifest, null, 2));
304
307
 
305
308
  export default manifest;
@@ -5,7 +5,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
5
5
  ## Directory conventions
6
6
 
7
7
  **Unified SSR app:**
8
- - `app.ts`: data model + manifest (`entity()` + `field.*`, queries/actions/policies, `routes: await discoverAppRoutes()`). Ends with `console.log(JSON.stringify(manifest))`.
8
+ - `app.ts`: data model + manifest (`entity()` + `field.*`, queries/actions/policies, `routes: await discoverAppRoutes()`). Ends with `console.log(JSON.stringify(manifest))` — that line is the manifest handoff (the CLI parses this file's stdout), not debug output. Never remove it.
9
9
  - `app/`: file-based SSR routes. `app/page.tsx` → `/`, `app/about/page.tsx` → `/about`, `app/blog/[slug]/page.tsx` → `/blog/:slug`. A `(group)` directory is stripped from the URL — `app/(marketing)/about/page.tsx` still serves `/about` — so a group's `layout.tsx` gives one section its own chrome (nav, footer) without changing any path; routes outside the group render without it. `app/layout.tsx` is the document shell; `app/error.tsx` / `app/not-found.tsx` are boundaries.
10
10
  - `app/globals.css`: Tailwind v4 entrypoint (auto-compiled and injected).
11
11
  - `functions/`: server functions, one per file, `default`-exported.
@@ -0,0 +1,30 @@
1
+ import type { LlmsTxt } from "@pylonsync/react";
2
+
3
+ // app/llms.ts → served at /llms.txt, in llmstxt.org format.
4
+ //
5
+ // This is the file an AI agent reads FIRST to decide what this site is and
6
+ // whether it can help with the task in front of it. Keep the shape, rewrite the
7
+ // copy: name the jobs this app is right for and the exact call to make. Generic
8
+ // marketing copy does not read as guidance.
9
+ //
10
+ // The default export may be async, so it can enumerate pages from the database.
11
+ const SITE = process.env.SITE_URL ?? "http://localhost:4321";
12
+
13
+ export default function llms(): LlmsTxt {
14
+ return {
15
+ title: "__APP_NAME__",
16
+ summary: "An AI chat app with streaming answers and stored transcripts.",
17
+ details: [
18
+ "Use this app to ask a question and read the streamed answer. Transcripts persist across reloads and devices.",
19
+ "Every page here is also readable as markdown: add `.md` to any path, or send `Accept: text/markdown`.",
20
+ ],
21
+ sections: [
22
+ {
23
+ title: "Pages",
24
+ links: [
25
+ { title: "Home", url: `${SITE}` },
26
+ ],
27
+ },
28
+ ],
29
+ };
30
+ }
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+ import { Link, type NotFoundProps } from "@pylonsync/react";
3
+ import { Button } from "@/components/ui/button";
4
+
5
+ // `app/not-found.tsx` → rendered at HTTP 404 for any unmatched URL, and when a
6
+ // page calls `response.notFound()`.
7
+ //
8
+ // Give it real links. This boundary is also what an agent reads when it guesses
9
+ // a URL wrong: a markdown request for a missing page renders THIS file as
10
+ // markdown at 404, so the links below are how it finds its way back.
11
+ export default function NotFound(_props: NotFoundProps) {
12
+ return (
13
+ <div className="mx-auto flex min-h-[60vh] max-w-lg flex-col justify-center gap-6 px-4">
14
+ <section>
15
+ <h1 className="text-2xl font-semibold tracking-tight">Page not found</h1>
16
+ <p className="mt-2 text-muted-foreground">
17
+ That URL does not exist. Try one of these:
18
+ </p>
19
+ </section>
20
+ <ul className="space-y-2 text-sm">
21
+ <li>
22
+ <Link className="underline underline-offset-4" href="/">
23
+ Home
24
+ </Link>
25
+ </li>
26
+ <li>
27
+ <a className="underline underline-offset-4" href="/sitemap.xml">
28
+ Sitemap
29
+ </a>
30
+ </li>
31
+ <li>
32
+ <a className="underline underline-offset-4" href="/llms.txt">
33
+ llms.txt
34
+ </a>
35
+ </li>
36
+ </ul>
37
+ <div>
38
+ <Button asChild>
39
+ <Link href="/">Back to home</Link>
40
+ </Button>
41
+ </div>
42
+ </div>
43
+ );
44
+ }
@@ -0,0 +1,14 @@
1
+ import type { Robots } from "@pylonsync/react";
2
+
3
+ // app/robots.ts → served at /robots.txt. Point SITE_URL at your domain in
4
+ // production. The default export may also be async.
5
+ const SITE = process.env.SITE_URL ?? "http://localhost:4321";
6
+
7
+ export default function robots(): Robots {
8
+ return {
9
+ // Keep the signed-in app and the API out of the index. `llms.txt` and the
10
+ // sitemap stay crawlable — they are what a crawler or agent reads first.
11
+ rules: { userAgent: "*", allow: "/", disallow: ["/api/", "/login"] },
12
+ sitemap: `${SITE}/sitemap.xml`,
13
+ };
14
+ }
@@ -0,0 +1,11 @@
1
+ import type { Sitemap } from "@pylonsync/react";
2
+
3
+ // app/sitemap.ts → served at /sitemap.xml. The default export can be async, so
4
+ // it can enumerate pages from your database. Point SITE_URL at your domain in
5
+ // production.
6
+ const SITE = process.env.SITE_URL ?? "http://localhost:4321";
7
+
8
+ export default async function sitemap(): Promise<Sitemap> {
9
+ // Only public pages belong here. The signed-in app is private.
10
+ return [{ url: `${SITE}/`, changeFrequency: "weekly", priority: 1 }];
11
+ }
@@ -135,6 +135,9 @@ const manifest = buildManifest({
135
135
  routes: await discoverAppRoutes(),
136
136
  });
137
137
 
138
+ // Not a debug leftover: the CLI runs `bun run app.ts` and parses stdout as
139
+ // your manifest (see crates/cli/src/bun.rs). Deleting this line breaks every
140
+ // pylon command that reads your schema.
138
141
  console.log(JSON.stringify(manifest, null, 2));
139
142
 
140
143
  export default manifest;
@@ -5,7 +5,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
5
5
  ## Directory conventions
6
6
 
7
7
  **Unified SSR app:**
8
- - `app.ts`: data model + manifest (`entity()` + `field.*`, queries/actions/policies, `routes: await discoverAppRoutes()`). Ends with `console.log(JSON.stringify(manifest))`.
8
+ - `app.ts`: data model + manifest (`entity()` + `field.*`, queries/actions/policies, `routes: await discoverAppRoutes()`). Ends with `console.log(JSON.stringify(manifest))` — that line is the manifest handoff (the CLI parses this file's stdout), not debug output. Never remove it.
9
9
  - `app/`: file-based SSR routes. `app/page.tsx` → `/`, `app/about/page.tsx` → `/about`, `app/blog/[slug]/page.tsx` → `/blog/:slug`. A `(group)` directory is stripped from the URL — `app/(marketing)/about/page.tsx` still serves `/about` — so a group's `layout.tsx` gives one section its own chrome (nav, footer) without changing any path; routes outside the group render without it. `app/layout.tsx` is the document shell; `app/error.tsx` / `app/not-found.tsx` are boundaries.
10
10
  - `app/globals.css`: Tailwind v4 entrypoint (auto-compiled and injected).
11
11
  - `functions/`: server functions, one per file, `default`-exported.
@@ -0,0 +1,30 @@
1
+ import type { LlmsTxt } from "@pylonsync/react";
2
+
3
+ // app/llms.ts → served at /llms.txt, in llmstxt.org format.
4
+ //
5
+ // This is the file an AI agent reads FIRST to decide what this site is and
6
+ // whether it can help with the task in front of it. Keep the shape, rewrite the
7
+ // copy: name the jobs this app is right for and the exact call to make. Generic
8
+ // marketing copy does not read as guidance.
9
+ //
10
+ // The default export may be async, so it can enumerate pages from the database.
11
+ const SITE = process.env.SITE_URL ?? "http://localhost:4321";
12
+
13
+ export default function llms(): LlmsTxt {
14
+ return {
15
+ title: "__APP_NAME__",
16
+ summary: "An AI studio: prompts, runs, and tool calls with durable run state.",
17
+ details: [
18
+ "Use this app to run a prompt, read the streamed output, and review past runs.",
19
+ "Every page here is also readable as markdown: add `.md` to any path, or send `Accept: text/markdown`.",
20
+ ],
21
+ sections: [
22
+ {
23
+ title: "Pages",
24
+ links: [
25
+ { title: "Home", url: `${SITE}` },
26
+ ],
27
+ },
28
+ ],
29
+ };
30
+ }
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+ import { Link, type NotFoundProps } from "@pylonsync/react";
3
+ import { Button } from "@/components/ui/button";
4
+
5
+ // `app/not-found.tsx` → rendered at HTTP 404 for any unmatched URL, and when a
6
+ // page calls `response.notFound()`.
7
+ //
8
+ // Give it real links. This boundary is also what an agent reads when it guesses
9
+ // a URL wrong: a markdown request for a missing page renders THIS file as
10
+ // markdown at 404, so the links below are how it finds its way back.
11
+ export default function NotFound(_props: NotFoundProps) {
12
+ return (
13
+ <div className="mx-auto flex min-h-[60vh] max-w-lg flex-col justify-center gap-6 px-4">
14
+ <section>
15
+ <h1 className="text-2xl font-semibold tracking-tight">Page not found</h1>
16
+ <p className="mt-2 text-muted-foreground">
17
+ That URL does not exist. Try one of these:
18
+ </p>
19
+ </section>
20
+ <ul className="space-y-2 text-sm">
21
+ <li>
22
+ <Link className="underline underline-offset-4" href="/">
23
+ Home
24
+ </Link>
25
+ </li>
26
+ <li>
27
+ <a className="underline underline-offset-4" href="/sitemap.xml">
28
+ Sitemap
29
+ </a>
30
+ </li>
31
+ <li>
32
+ <a className="underline underline-offset-4" href="/llms.txt">
33
+ llms.txt
34
+ </a>
35
+ </li>
36
+ </ul>
37
+ <div>
38
+ <Button asChild>
39
+ <Link href="/">Back to home</Link>
40
+ </Button>
41
+ </div>
42
+ </div>
43
+ );
44
+ }
@@ -0,0 +1,14 @@
1
+ import type { Robots } from "@pylonsync/react";
2
+
3
+ // app/robots.ts → served at /robots.txt. Point SITE_URL at your domain in
4
+ // production. The default export may also be async.
5
+ const SITE = process.env.SITE_URL ?? "http://localhost:4321";
6
+
7
+ export default function robots(): Robots {
8
+ return {
9
+ // Keep the signed-in app and the API out of the index. `llms.txt` and the
10
+ // sitemap stay crawlable — they are what a crawler or agent reads first.
11
+ rules: { userAgent: "*", allow: "/", disallow: ["/api/", "/login"] },
12
+ sitemap: `${SITE}/sitemap.xml`,
13
+ };
14
+ }
@@ -0,0 +1,11 @@
1
+ import type { Sitemap } from "@pylonsync/react";
2
+
3
+ // app/sitemap.ts → served at /sitemap.xml. The default export can be async, so
4
+ // it can enumerate pages from your database. Point SITE_URL at your domain in
5
+ // production.
6
+ const SITE = process.env.SITE_URL ?? "http://localhost:4321";
7
+
8
+ export default async function sitemap(): Promise<Sitemap> {
9
+ // Only public pages belong here. The signed-in app is private.
10
+ return [{ url: `${SITE}/`, changeFrequency: "weekly", priority: 1 }];
11
+ }
@@ -122,6 +122,9 @@ const manifest = buildManifest({
122
122
  routes: await discoverAppRoutes(),
123
123
  });
124
124
 
125
+ // Not a debug leftover: the CLI runs `bun run app.ts` and parses stdout as
126
+ // your manifest (see crates/cli/src/bun.rs). Deleting this line breaks every
127
+ // pylon command that reads your schema.
125
128
  console.log(JSON.stringify(manifest, null, 2));
126
129
 
127
130
  export default manifest;
@@ -176,4 +176,7 @@ const manifest = buildManifest({
176
176
  routes: [],
177
177
  });
178
178
 
179
+ // Not a debug leftover: the CLI runs `bun run` on this file and parses stdout
180
+ // as your manifest (see crates/cli/src/bun.rs). Deleting this line breaks every
181
+ // pylon command that reads your schema.
179
182
  console.log(JSON.stringify(manifest));
@@ -58,4 +58,7 @@ const manifest = buildManifest({
58
58
  routes: [],
59
59
  });
60
60
 
61
+ // Not a debug leftover: the CLI runs `bun run` on this file and parses stdout
62
+ // as your manifest (see crates/cli/src/bun.rs). Deleting this line breaks every
63
+ // pylon command that reads your schema.
61
64
  console.log(JSON.stringify(manifest));
@@ -93,4 +93,7 @@ const manifest = buildManifest({
93
93
  routes: [],
94
94
  });
95
95
 
96
+ // Not a debug leftover: the CLI runs `bun run` on this file and parses stdout
97
+ // as your manifest (see crates/cli/src/bun.rs). Deleting this line breaks every
98
+ // pylon command that reads your schema.
96
99
  console.log(JSON.stringify(manifest));
@@ -137,4 +137,7 @@ const manifest = buildManifest({
137
137
  routes: [],
138
138
  });
139
139
 
140
+ // Not a debug leftover: the CLI runs `bun run` on this file and parses stdout
141
+ // as your manifest (see crates/cli/src/bun.rs). Deleting this line breaks every
142
+ // pylon command that reads your schema.
140
143
  console.log(JSON.stringify(manifest));
@@ -82,4 +82,7 @@ const manifest = buildManifest({
82
82
  routes: [],
83
83
  });
84
84
 
85
+ // Not a debug leftover: the CLI runs `bun run` on this file and parses stdout
86
+ // as your manifest (see crates/cli/src/bun.rs). Deleting this line breaks every
87
+ // pylon command that reads your schema.
85
88
  console.log(JSON.stringify(manifest));
@@ -5,7 +5,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
5
5
  ## Directory conventions
6
6
 
7
7
  **Unified SSR app:**
8
- - `app.ts`: data model + manifest (`entity()` + `field.*`, queries/actions/policies, `routes: await discoverAppRoutes()`). Ends with `console.log(JSON.stringify(manifest))`.
8
+ - `app.ts`: data model + manifest (`entity()` + `field.*`, queries/actions/policies, `routes: await discoverAppRoutes()`). Ends with `console.log(JSON.stringify(manifest))` — that line is the manifest handoff (the CLI parses this file's stdout), not debug output. Never remove it.
9
9
  - `app/`: file-based SSR routes. `app/page.tsx` → `/`, `app/about/page.tsx` → `/about`, `app/blog/[slug]/page.tsx` → `/blog/:slug`. A `(group)` directory is stripped from the URL — `app/(marketing)/about/page.tsx` still serves `/about` — so a group's `layout.tsx` gives one section its own chrome (nav, footer) without changing any path; routes outside the group render without it. `app/layout.tsx` is the document shell; `app/error.tsx` / `app/not-found.tsx` are boundaries.
10
10
  - `app/globals.css`: Tailwind v4 entrypoint (auto-compiled and injected).
11
11
  - `functions/`: server functions, one per file, `default`-exported.
@@ -0,0 +1,30 @@
1
+ import type { LlmsTxt } from "@pylonsync/react";
2
+
3
+ // app/llms.ts → served at /llms.txt, in llmstxt.org format.
4
+ //
5
+ // This is the file an AI agent reads FIRST to decide what this site is and
6
+ // whether it can help with the task in front of it. Keep the shape, rewrite the
7
+ // copy: name the jobs this app is right for and the exact call to make. Generic
8
+ // marketing copy does not read as guidance.
9
+ //
10
+ // The default export may be async, so it can enumerate pages from the database.
11
+ const SITE = process.env.SITE_URL ?? "http://localhost:4321";
12
+
13
+ export default function llms(): LlmsTxt {
14
+ return {
15
+ title: "__APP_NAME__",
16
+ summary: "A starter app with one entity and a live list.",
17
+ details: [
18
+ "Use this app to read and create items. Every change is pushed to open clients over the sync connection.",
19
+ "Every page here is also readable as markdown: add `.md` to any path, or send `Accept: text/markdown`.",
20
+ ],
21
+ sections: [
22
+ {
23
+ title: "Pages",
24
+ links: [
25
+ { title: "Home", url: `${SITE}` },
26
+ ],
27
+ },
28
+ ],
29
+ };
30
+ }
@@ -55,6 +55,9 @@ const manifest = buildManifest({
55
55
  routes: await discoverAppRoutes(),
56
56
  });
57
57
 
58
+ // Not a debug leftover: the CLI runs `bun run app.ts` and parses stdout as
59
+ // your manifest (see crates/cli/src/bun.rs). Deleting this line breaks every
60
+ // pylon command that reads your schema.
58
61
  console.log(JSON.stringify(manifest, null, 2));
59
62
 
60
63
  export default manifest;
@@ -5,7 +5,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
5
5
  ## Directory conventions
6
6
 
7
7
  **Unified SSR app:**
8
- - `app.ts`: data model + manifest (`entity()` + `field.*`, queries/actions/policies, `routes: await discoverAppRoutes()`). Ends with `console.log(JSON.stringify(manifest))`.
8
+ - `app.ts`: data model + manifest (`entity()` + `field.*`, queries/actions/policies, `routes: await discoverAppRoutes()`). Ends with `console.log(JSON.stringify(manifest))` — that line is the manifest handoff (the CLI parses this file's stdout), not debug output. Never remove it.
9
9
  - `app/`: file-based SSR routes. `app/page.tsx` → `/`, `app/about/page.tsx` → `/about`, `app/blog/[slug]/page.tsx` → `/blog/:slug`. A `(group)` directory is stripped from the URL — `app/(marketing)/about/page.tsx` still serves `/about` — so a group's `layout.tsx` gives one section its own chrome (nav, footer) without changing any path; routes outside the group render without it. `app/layout.tsx` is the document shell; `app/error.tsx` / `app/not-found.tsx` are boundaries.
10
10
  - `app/globals.css`: Tailwind v4 entrypoint (auto-compiled and injected).
11
11
  - `functions/`: server functions, one per file, `default`-exported.
@@ -0,0 +1,30 @@
1
+ import type { LlmsTxt } from "@pylonsync/react";
2
+
3
+ // app/llms.ts → served at /llms.txt, in llmstxt.org format.
4
+ //
5
+ // This is the file an AI agent reads FIRST to decide what this site is and
6
+ // whether it can help with the task in front of it. Keep the shape, rewrite the
7
+ // copy: name the jobs this app is right for and the exact call to make. Generic
8
+ // marketing copy does not read as guidance.
9
+ //
10
+ // The default export may be async, so it can enumerate pages from the database.
11
+ const SITE = process.env.SITE_URL ?? "http://localhost:4321";
12
+
13
+ export default function llms(): LlmsTxt {
14
+ return {
15
+ title: "__APP_NAME__",
16
+ summary: "A realtime chat room.",
17
+ details: [
18
+ "Use this app to read and send messages in a shared room. Messages arrive on every open client without a refresh.",
19
+ "Every page here is also readable as markdown: add `.md` to any path, or send `Accept: text/markdown`.",
20
+ ],
21
+ sections: [
22
+ {
23
+ title: "Pages",
24
+ links: [
25
+ { title: "Home", url: `${SITE}` },
26
+ ],
27
+ },
28
+ ],
29
+ };
30
+ }
@@ -54,6 +54,9 @@ const manifest = buildManifest({
54
54
  routes: await discoverAppRoutes(),
55
55
  });
56
56
 
57
+ // Not a debug leftover: the CLI runs `bun run app.ts` and parses stdout as
58
+ // your manifest (see crates/cli/src/bun.rs). Deleting this line breaks every
59
+ // pylon command that reads your schema.
57
60
  console.log(JSON.stringify(manifest, null, 2));
58
61
 
59
62
  export default manifest;
@@ -5,7 +5,7 @@ Pylon serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR from o
5
5
  ## Directory conventions
6
6
 
7
7
  **Unified SSR app:**
8
- - `app.ts`: data model + manifest (`entity()` + `field.*`, queries/actions/policies, `routes: await discoverAppRoutes()`). Ends with `console.log(JSON.stringify(manifest))`.
8
+ - `app.ts`: data model + manifest (`entity()` + `field.*`, queries/actions/policies, `routes: await discoverAppRoutes()`). Ends with `console.log(JSON.stringify(manifest))` — that line is the manifest handoff (the CLI parses this file's stdout), not debug output. Never remove it.
9
9
  - `app/`: file-based SSR routes. `app/page.tsx` → `/`, `app/about/page.tsx` → `/about`, `app/blog/[slug]/page.tsx` → `/blog/:slug`. A `(group)` directory is stripped from the URL — `app/(marketing)/about/page.tsx` still serves `/about` — so a group's `layout.tsx` gives one section its own chrome (nav, footer) without changing any path; routes outside the group render without it. `app/layout.tsx` is the document shell; `app/error.tsx` / `app/not-found.tsx` are boundaries.
10
10
  - `app/globals.css`: Tailwind v4 entrypoint (auto-compiled and injected).
11
11
  - `functions/`: server functions, one per file, `default`-exported.
@@ -0,0 +1,30 @@
1
+ import type { LlmsTxt } from "@pylonsync/react";
2
+
3
+ // app/llms.ts → served at /llms.txt, in llmstxt.org format.
4
+ //
5
+ // This is the file an AI agent reads FIRST to decide what this site is and
6
+ // whether it can help with the task in front of it. Keep the shape, rewrite the
7
+ // copy: name the jobs this app is right for and the exact call to make. Generic
8
+ // marketing copy does not read as guidance.
9
+ //
10
+ // The default export may be async, so it can enumerate pages from the database.
11
+ const SITE = process.env.SITE_URL ?? "http://localhost:4321";
12
+
13
+ export default function llms(): LlmsTxt {
14
+ return {
15
+ title: "__APP_NAME__",
16
+ summary: "A social feed with posts and likes.",
17
+ details: [
18
+ "Use this app to read the public feed, post, and like. Reading needs no account; posting needs a session.",
19
+ "Every page here is also readable as markdown: add `.md` to any path, or send `Accept: text/markdown`.",
20
+ ],
21
+ sections: [
22
+ {
23
+ title: "Pages",
24
+ links: [
25
+ { title: "Home", url: `${SITE}` },
26
+ ],
27
+ },
28
+ ],
29
+ };
30
+ }