@pylonsync/create-pylon 0.3.274 → 0.3.275

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 (323) hide show
  1. package/bin/create-pylon.js +80 -0
  2. package/package.json +1 -1
  3. package/templates/ARCHETYPES.md +339 -0
  4. package/templates/agency/.env.example +12 -0
  5. package/templates/agency/AGENTS.md +61 -0
  6. package/templates/agency/README.md +90 -0
  7. package/templates/agency/app/auth-form.tsx +129 -0
  8. package/templates/agency/app/contact-form.tsx +258 -0
  9. package/templates/agency/app/dashboard/dashboard-client.tsx +286 -0
  10. package/templates/agency/app/dashboard/page.tsx +70 -0
  11. package/templates/agency/app/error.tsx +26 -0
  12. package/templates/agency/app/globals.css +148 -0
  13. package/templates/agency/app/layout.tsx +174 -0
  14. package/templates/agency/app/login/page.tsx +39 -0
  15. package/templates/agency/app/not-found.tsx +19 -0
  16. package/templates/agency/app/page.tsx +207 -0
  17. package/templates/agency/app/robots.ts +12 -0
  18. package/templates/agency/app/sitemap.ts +9 -0
  19. package/templates/agency/app.ts +135 -0
  20. package/templates/agency/components/marketing.tsx +148 -0
  21. package/templates/agency/components/section-scroller.tsx +35 -0
  22. package/templates/agency/components/ui/button.tsx +56 -0
  23. package/templates/agency/components/ui/card.tsx +90 -0
  24. package/templates/agency/components.json +20 -0
  25. package/templates/agency/functions/bookInquiry.ts +42 -0
  26. package/templates/agency/functions/declineInquiry.ts +41 -0
  27. package/templates/agency/functions/inquiriesForOwner.ts +31 -0
  28. package/templates/agency/functions/seedCapacity.ts +26 -0
  29. package/templates/agency/functions/setCapacity.ts +32 -0
  30. package/templates/agency/functions/submitInquiry.ts +55 -0
  31. package/templates/agency/gitignore +10 -0
  32. package/templates/agency/lib/agency.ts +27 -0
  33. package/templates/agency/lib/owner.ts +26 -0
  34. package/templates/agency/lib/site.config.ts +239 -0
  35. package/templates/agency/lib/utils.ts +10 -0
  36. package/templates/agency/package.json +34 -0
  37. package/templates/agency/tsconfig.json +18 -0
  38. package/templates/ai-chat/.env.example +33 -0
  39. package/templates/ai-chat/AGENTS.md +61 -0
  40. package/templates/ai-chat/README.md +99 -0
  41. package/templates/ai-chat/app/auth-form.tsx +124 -0
  42. package/templates/ai-chat/app/chat-client.tsx +414 -0
  43. package/templates/ai-chat/app/error.tsx +26 -0
  44. package/templates/ai-chat/app/globals.css +148 -0
  45. package/templates/ai-chat/app/layout.tsx +75 -0
  46. package/templates/ai-chat/app/login/page.tsx +39 -0
  47. package/templates/ai-chat/app/not-found.tsx +19 -0
  48. package/templates/ai-chat/app/page.tsx +23 -0
  49. package/templates/ai-chat/app.ts +121 -0
  50. package/templates/ai-chat/components.json +20 -0
  51. package/templates/ai-chat/gitignore +10 -0
  52. package/templates/ai-chat/lib/site.config.ts +103 -0
  53. package/templates/ai-chat/lib/utils.ts +10 -0
  54. package/templates/ai-chat/package.json +34 -0
  55. package/templates/ai-chat/tsconfig.json +18 -0
  56. package/templates/ai-studio/.env.example +19 -0
  57. package/templates/ai-studio/AGENTS.md +61 -0
  58. package/templates/ai-studio/README.md +83 -0
  59. package/templates/ai-studio/app/auth-form.tsx +124 -0
  60. package/templates/ai-studio/app/error.tsx +26 -0
  61. package/templates/ai-studio/app/globals.css +148 -0
  62. package/templates/ai-studio/app/layout.tsx +75 -0
  63. package/templates/ai-studio/app/login/page.tsx +39 -0
  64. package/templates/ai-studio/app/not-found.tsx +19 -0
  65. package/templates/ai-studio/app/page.tsx +34 -0
  66. package/templates/ai-studio/app/studio-client.tsx +214 -0
  67. package/templates/ai-studio/app.ts +108 -0
  68. package/templates/ai-studio/components.json +20 -0
  69. package/templates/ai-studio/functions/_getGeneration.ts +25 -0
  70. package/templates/ai-studio/functions/_updateGeneration.ts +37 -0
  71. package/templates/ai-studio/functions/generate.ts +42 -0
  72. package/templates/ai-studio/functions/pollGeneration.ts +134 -0
  73. package/templates/ai-studio/gitignore +10 -0
  74. package/templates/ai-studio/lib/site.config.ts +80 -0
  75. package/templates/ai-studio/lib/studio.ts +52 -0
  76. package/templates/ai-studio/lib/utils.ts +10 -0
  77. package/templates/ai-studio/package.json +34 -0
  78. package/templates/ai-studio/tsconfig.json +18 -0
  79. package/templates/creator/.env.example +12 -0
  80. package/templates/creator/AGENTS.md +61 -0
  81. package/templates/creator/README.md +67 -0
  82. package/templates/creator/app/auth-form.tsx +129 -0
  83. package/templates/creator/app/dashboard/dashboard-client.tsx +297 -0
  84. package/templates/creator/app/dashboard/page.tsx +70 -0
  85. package/templates/creator/app/error.tsx +26 -0
  86. package/templates/creator/app/globals.css +148 -0
  87. package/templates/creator/app/layout.tsx +160 -0
  88. package/templates/creator/app/login/page.tsx +39 -0
  89. package/templates/creator/app/newsletter-signup.tsx +162 -0
  90. package/templates/creator/app/not-found.tsx +19 -0
  91. package/templates/creator/app/page.tsx +160 -0
  92. package/templates/creator/app/robots.ts +12 -0
  93. package/templates/creator/app/sitemap.ts +9 -0
  94. package/templates/creator/app.ts +134 -0
  95. package/templates/creator/components/marketing.tsx +148 -0
  96. package/templates/creator/components/section-scroller.tsx +35 -0
  97. package/templates/creator/components/ui/button.tsx +56 -0
  98. package/templates/creator/components/ui/card.tsx +90 -0
  99. package/templates/creator/components.json +20 -0
  100. package/templates/creator/functions/subscribe.ts +82 -0
  101. package/templates/creator/functions/subscriberStats.ts +75 -0
  102. package/templates/creator/gitignore +10 -0
  103. package/templates/creator/lib/owner.ts +26 -0
  104. package/templates/creator/lib/site.config.ts +173 -0
  105. package/templates/creator/lib/stats.ts +30 -0
  106. package/templates/creator/lib/utils.ts +10 -0
  107. package/templates/creator/package.json +34 -0
  108. package/templates/creator/tsconfig.json +18 -0
  109. package/templates/default/app/layout.tsx +26 -27
  110. package/templates/default/app/page.tsx +90 -274
  111. package/templates/default/lib/products.ts +9 -122
  112. package/templates/default/lib/site.config.ts +739 -0
  113. package/templates/default/lib/site.ts +14 -261
  114. package/templates/directory/.env.example +12 -0
  115. package/templates/directory/AGENTS.md +61 -0
  116. package/templates/directory/README.md +80 -0
  117. package/templates/directory/app/auth-form.tsx +129 -0
  118. package/templates/directory/app/dashboard/dashboard-client.tsx +205 -0
  119. package/templates/directory/app/dashboard/page.tsx +70 -0
  120. package/templates/directory/app/directory-browse.tsx +328 -0
  121. package/templates/directory/app/error.tsx +26 -0
  122. package/templates/directory/app/globals.css +148 -0
  123. package/templates/directory/app/layout.tsx +171 -0
  124. package/templates/directory/app/login/page.tsx +39 -0
  125. package/templates/directory/app/not-found.tsx +19 -0
  126. package/templates/directory/app/page.tsx +50 -0
  127. package/templates/directory/app/robots.ts +12 -0
  128. package/templates/directory/app/sitemap.ts +9 -0
  129. package/templates/directory/app/submit/page.tsx +30 -0
  130. package/templates/directory/app/submit-form.tsx +151 -0
  131. package/templates/directory/app.ts +146 -0
  132. package/templates/directory/components/marketing.tsx +148 -0
  133. package/templates/directory/components/section-scroller.tsx +35 -0
  134. package/templates/directory/components/ui/button.tsx +56 -0
  135. package/templates/directory/components/ui/card.tsx +90 -0
  136. package/templates/directory/components.json +20 -0
  137. package/templates/directory/functions/approveSubmission.ts +45 -0
  138. package/templates/directory/functions/rejectSubmission.ts +20 -0
  139. package/templates/directory/functions/seedListings.ts +33 -0
  140. package/templates/directory/functions/submissionsForOwner.ts +29 -0
  141. package/templates/directory/functions/submitListing.ts +63 -0
  142. package/templates/directory/functions/upvote.ts +24 -0
  143. package/templates/directory/gitignore +10 -0
  144. package/templates/directory/lib/directory.ts +45 -0
  145. package/templates/directory/lib/owner.ts +26 -0
  146. package/templates/directory/lib/site.config.ts +130 -0
  147. package/templates/directory/lib/utils.ts +10 -0
  148. package/templates/directory/package.json +34 -0
  149. package/templates/directory/tsconfig.json +18 -0
  150. package/templates/local-service/.env.example +12 -0
  151. package/templates/local-service/AGENTS.md +61 -0
  152. package/templates/local-service/README.md +82 -0
  153. package/templates/local-service/app/auth-form.tsx +129 -0
  154. package/templates/local-service/app/booking-widget.tsx +399 -0
  155. package/templates/local-service/app/dashboard/dashboard-client.tsx +304 -0
  156. package/templates/local-service/app/dashboard/page.tsx +63 -0
  157. package/templates/local-service/app/error.tsx +26 -0
  158. package/templates/local-service/app/globals.css +148 -0
  159. package/templates/local-service/app/layout.tsx +151 -0
  160. package/templates/local-service/app/login/page.tsx +39 -0
  161. package/templates/local-service/app/not-found.tsx +19 -0
  162. package/templates/local-service/app/page.tsx +233 -0
  163. package/templates/local-service/app/robots.ts +12 -0
  164. package/templates/local-service/app/sitemap.ts +9 -0
  165. package/templates/local-service/app.ts +131 -0
  166. package/templates/local-service/components/marketing.tsx +162 -0
  167. package/templates/local-service/components/section-scroller.tsx +35 -0
  168. package/templates/local-service/components/ui/button.tsx +56 -0
  169. package/templates/local-service/components/ui/card.tsx +90 -0
  170. package/templates/local-service/components.json +20 -0
  171. package/templates/local-service/functions/bookingsForOwner.ts +30 -0
  172. package/templates/local-service/functions/cancelBooking.ts +27 -0
  173. package/templates/local-service/functions/confirmBooking.ts +18 -0
  174. package/templates/local-service/functions/createBooking.ts +98 -0
  175. package/templates/local-service/gitignore +10 -0
  176. package/templates/local-service/lib/booking.ts +24 -0
  177. package/templates/local-service/lib/owner.ts +26 -0
  178. package/templates/local-service/lib/site.config.ts +232 -0
  179. package/templates/local-service/lib/slots.ts +97 -0
  180. package/templates/local-service/lib/utils.ts +10 -0
  181. package/templates/local-service/package.json +34 -0
  182. package/templates/local-service/tsconfig.json +18 -0
  183. package/templates/marketplace/.env.example +9 -0
  184. package/templates/marketplace/AGENTS.md +61 -0
  185. package/templates/marketplace/README.md +78 -0
  186. package/templates/marketplace/app/_components/CategoryIcon.tsx +40 -0
  187. package/templates/marketplace/app/error.tsx +26 -0
  188. package/templates/marketplace/app/globals.css +64 -0
  189. package/templates/marketplace/app/layout.tsx +60 -0
  190. package/templates/marketplace/app/listing/[id]/page.tsx +163 -0
  191. package/templates/marketplace/app/me/page.tsx +15 -0
  192. package/templates/marketplace/app/not-found.tsx +20 -0
  193. package/templates/marketplace/app/page.tsx +159 -0
  194. package/templates/marketplace/app/robots.ts +12 -0
  195. package/templates/marketplace/app/sell/page.tsx +26 -0
  196. package/templates/marketplace/app/sitemap.ts +14 -0
  197. package/templates/marketplace/app.ts +190 -0
  198. package/templates/marketplace/client/AuthNav.tsx +46 -0
  199. package/templates/marketplace/client/LiveTicker.tsx +104 -0
  200. package/templates/marketplace/client/LoginCard.tsx +130 -0
  201. package/templates/marketplace/client/MarketProvider.tsx +148 -0
  202. package/templates/marketplace/client/MyMarket.tsx +180 -0
  203. package/templates/marketplace/client/OfferPanel.tsx +355 -0
  204. package/templates/marketplace/client/SeedOnEmpty.tsx +26 -0
  205. package/templates/marketplace/client/SellForm.tsx +160 -0
  206. package/templates/marketplace/client/WatchButton.tsx +88 -0
  207. package/templates/marketplace/client/market.ts +341 -0
  208. package/templates/marketplace/functions/buyNow.ts +78 -0
  209. package/templates/marketplace/functions/makeOffer.ts +65 -0
  210. package/templates/marketplace/functions/respondToOffer.ts +62 -0
  211. package/templates/marketplace/functions/seedMarket.ts +90 -0
  212. package/templates/marketplace/gitignore +10 -0
  213. package/templates/marketplace/package.json +35 -0
  214. package/templates/marketplace/tsconfig.json +14 -0
  215. package/templates/marketplace/ui/badge.tsx +30 -0
  216. package/templates/marketplace/ui/button.tsx +49 -0
  217. package/templates/marketplace/ui/card.tsx +48 -0
  218. package/templates/marketplace/ui/input.tsx +17 -0
  219. package/templates/marketplace/ui/label.tsx +18 -0
  220. package/templates/marketplace/ui/textarea.tsx +17 -0
  221. package/templates/marketplace/ui/tokens.css +32 -0
  222. package/templates/marketplace/ui/utils.ts +6 -0
  223. package/templates/restaurant/.env.example +12 -0
  224. package/templates/restaurant/AGENTS.md +61 -0
  225. package/templates/restaurant/README.md +77 -0
  226. package/templates/restaurant/app/auth-form.tsx +129 -0
  227. package/templates/restaurant/app/dashboard/dashboard-client.tsx +263 -0
  228. package/templates/restaurant/app/dashboard/page.tsx +59 -0
  229. package/templates/restaurant/app/error.tsx +26 -0
  230. package/templates/restaurant/app/globals.css +148 -0
  231. package/templates/restaurant/app/layout.tsx +151 -0
  232. package/templates/restaurant/app/login/page.tsx +39 -0
  233. package/templates/restaurant/app/not-found.tsx +19 -0
  234. package/templates/restaurant/app/page.tsx +194 -0
  235. package/templates/restaurant/app/reservation-widget.tsx +359 -0
  236. package/templates/restaurant/app/robots.ts +12 -0
  237. package/templates/restaurant/app/sitemap.ts +9 -0
  238. package/templates/restaurant/app.ts +115 -0
  239. package/templates/restaurant/components/marketing.tsx +162 -0
  240. package/templates/restaurant/components/section-scroller.tsx +35 -0
  241. package/templates/restaurant/components/ui/button.tsx +56 -0
  242. package/templates/restaurant/components/ui/card.tsx +90 -0
  243. package/templates/restaurant/components.json +20 -0
  244. package/templates/restaurant/functions/cancelReservation.ts +26 -0
  245. package/templates/restaurant/functions/confirmReservation.ts +17 -0
  246. package/templates/restaurant/functions/createReservation.ts +92 -0
  247. package/templates/restaurant/functions/reservationsForOwner.ts +28 -0
  248. package/templates/restaurant/gitignore +10 -0
  249. package/templates/restaurant/lib/owner.ts +26 -0
  250. package/templates/restaurant/lib/reservation.ts +22 -0
  251. package/templates/restaurant/lib/site.config.ts +218 -0
  252. package/templates/restaurant/lib/slots.ts +55 -0
  253. package/templates/restaurant/lib/utils.ts +10 -0
  254. package/templates/restaurant/package.json +34 -0
  255. package/templates/restaurant/tsconfig.json +18 -0
  256. package/templates/shop/.env.example +32 -0
  257. package/templates/shop/AGENTS.md +61 -0
  258. package/templates/shop/README.md +102 -0
  259. package/templates/shop/app/auth-form.tsx +129 -0
  260. package/templates/shop/app/dashboard/dashboard-client.tsx +264 -0
  261. package/templates/shop/app/dashboard/page.tsx +59 -0
  262. package/templates/shop/app/error.tsx +26 -0
  263. package/templates/shop/app/globals.css +148 -0
  264. package/templates/shop/app/layout.tsx +160 -0
  265. package/templates/shop/app/login/page.tsx +39 -0
  266. package/templates/shop/app/not-found.tsx +19 -0
  267. package/templates/shop/app/page.tsx +95 -0
  268. package/templates/shop/app/robots.ts +12 -0
  269. package/templates/shop/app/shop-client.tsx +436 -0
  270. package/templates/shop/app/sitemap.ts +9 -0
  271. package/templates/shop/app/success/page.tsx +33 -0
  272. package/templates/shop/app.ts +134 -0
  273. package/templates/shop/components/marketing.tsx +96 -0
  274. package/templates/shop/components/section-scroller.tsx +35 -0
  275. package/templates/shop/components/ui/button.tsx +56 -0
  276. package/templates/shop/components/ui/card.tsx +90 -0
  277. package/templates/shop/components.json +20 -0
  278. package/templates/shop/functions/cancelOrder.ts +33 -0
  279. package/templates/shop/functions/checkout.ts +130 -0
  280. package/templates/shop/functions/fulfillOrder.ts +17 -0
  281. package/templates/shop/functions/markGroupPaid.ts +26 -0
  282. package/templates/shop/functions/ordersForOwner.ts +28 -0
  283. package/templates/shop/functions/releaseGroup.ts +36 -0
  284. package/templates/shop/functions/reserveCart.ts +87 -0
  285. package/templates/shop/functions/restockProduct.ts +23 -0
  286. package/templates/shop/functions/seedProducts.ts +30 -0
  287. package/templates/shop/functions/stripeWebhook.ts +72 -0
  288. package/templates/shop/gitignore +10 -0
  289. package/templates/shop/lib/owner.ts +26 -0
  290. package/templates/shop/lib/shop.ts +45 -0
  291. package/templates/shop/lib/site.config.ts +198 -0
  292. package/templates/shop/lib/utils.ts +10 -0
  293. package/templates/shop/package.json +35 -0
  294. package/templates/shop/tsconfig.json +18 -0
  295. package/templates/waitlist/.env.example +12 -0
  296. package/templates/waitlist/AGENTS.md +61 -0
  297. package/templates/waitlist/README.md +81 -0
  298. package/templates/waitlist/app/auth-form.tsx +129 -0
  299. package/templates/waitlist/app/dashboard/dashboard-client.tsx +297 -0
  300. package/templates/waitlist/app/dashboard/page.tsx +70 -0
  301. package/templates/waitlist/app/error.tsx +26 -0
  302. package/templates/waitlist/app/globals.css +148 -0
  303. package/templates/waitlist/app/layout.tsx +158 -0
  304. package/templates/waitlist/app/login/page.tsx +39 -0
  305. package/templates/waitlist/app/not-found.tsx +19 -0
  306. package/templates/waitlist/app/page.tsx +119 -0
  307. package/templates/waitlist/app/robots.ts +12 -0
  308. package/templates/waitlist/app/sitemap.ts +9 -0
  309. package/templates/waitlist/app/waitlist-hero.tsx +219 -0
  310. package/templates/waitlist/app.ts +134 -0
  311. package/templates/waitlist/components/marketing.tsx +96 -0
  312. package/templates/waitlist/components/ui/button.tsx +56 -0
  313. package/templates/waitlist/components/ui/card.tsx +90 -0
  314. package/templates/waitlist/components.json +20 -0
  315. package/templates/waitlist/functions/joinWaitlist.ts +82 -0
  316. package/templates/waitlist/functions/waitlistStats.ts +75 -0
  317. package/templates/waitlist/gitignore +10 -0
  318. package/templates/waitlist/lib/owner.ts +26 -0
  319. package/templates/waitlist/lib/site.config.ts +178 -0
  320. package/templates/waitlist/lib/stats.ts +30 -0
  321. package/templates/waitlist/lib/utils.ts +10 -0
  322. package/templates/waitlist/package.json +34 -0
  323. package/templates/waitlist/tsconfig.json +18 -0
@@ -0,0 +1,39 @@
1
+ import React from "react";
2
+ import { Link, type Metadata, type PageProps } from "@pylonsync/react";
3
+ import { AuthForm } from "../auth-form";
4
+ import { siteConfig } from "@/lib/site.config";
5
+
6
+ export const metadata: Metadata = {
7
+ title: `Sign in — ${siteConfig.brand.name}`,
8
+ robots: "noindex",
9
+ };
10
+
11
+ // `app/login/page.tsx` → `/login`. Sign-in is required to use the chat (chats
12
+ // are tied to your account). Rendered bare. Already signed in? Skip back to the
13
+ // chat — `response.redirect` in the synchronous shell render is a real 307
14
+ // before any HTML is sent.
15
+ export default function LoginPage({ auth, response }: PageProps) {
16
+ // Already signed in (a real account, not an anonymous guest)? Skip the form.
17
+ if (auth.user_id && !auth.user_id.startsWith("guest_")) response.redirect("/");
18
+ const { brand } = siteConfig;
19
+ return (
20
+ <div className="flex min-h-screen items-center justify-center bg-white px-6 py-12">
21
+ <div className="w-full max-w-[400px] rounded-2xl border border-zinc-200/70 p-8">
22
+ <Link href="/" className="inline-flex">
23
+ <span className="flex size-9 items-center justify-center rounded-xl bg-zinc-900 text-base font-bold text-white">
24
+ {brand.letter}
25
+ </span>
26
+ </Link>
27
+ <h1 className="mt-5 text-[22px] font-semibold tracking-tight text-zinc-900">
28
+ Sign in to {brand.name}
29
+ </h1>
30
+ <p className="mt-1 text-[13px] text-zinc-500">
31
+ Create an account or sign in to start chatting.
32
+ </p>
33
+ <div className="mt-6">
34
+ <AuthForm />
35
+ </div>
36
+ </div>
37
+ </div>
38
+ );
39
+ }
@@ -0,0 +1,19 @@
1
+ import React from "react";
2
+ import { Link, type NotFoundProps } from "@pylonsync/react";
3
+
4
+ // `app/not-found.tsx` → rendered at HTTP 404 for any unmatched URL (and when a
5
+ // page calls `response.notFound()`). Hydrated, so the link is a client nav.
6
+ export default function NotFound(_props: NotFoundProps) {
7
+ return (
8
+ <div className="mx-auto flex min-h-[60vh] max-w-3xl flex-col items-center justify-center px-6 text-center">
9
+ <h1 className="text-3xl font-semibold tracking-tight">404</h1>
10
+ <p className="mt-2 text-zinc-500">We couldn&apos;t find that page.</p>
11
+ <Link
12
+ href="/"
13
+ className="mt-6 inline-flex h-10 items-center rounded-full bg-zinc-900 px-5 text-sm font-medium text-white transition-colors hover:bg-zinc-700"
14
+ >
15
+ Back home
16
+ </Link>
17
+ </div>
18
+ );
19
+ }
@@ -0,0 +1,23 @@
1
+ import React from "react";
2
+ import { type Metadata, type PageProps } from "@pylonsync/react";
3
+ import { ChatApp } from "./chat-client";
4
+ import { siteConfig } from "@/lib/site.config";
5
+
6
+ export const metadata: Metadata = {
7
+ title: siteConfig.seo.title,
8
+ description: siteConfig.seo.description,
9
+ openGraph: { title: siteConfig.seo.title, description: siteConfig.seo.description, type: "website" },
10
+ };
11
+
12
+ // `app/page.tsx` → `/`. The whole app is the chat island — a sidebar of
13
+ // conversations + the streaming thread. Sign-in is REQUIRED: chats are tied to
14
+ // your account, so an unauthenticated visitor is redirected to /login (a real
15
+ // 307 from the synchronous shell render, before any HTML is sent).
16
+ export default function Home({ auth, response }: PageProps) {
17
+ // Real account required — reject anonymous + guest (guest_…) sessions.
18
+ if (!auth.user_id || auth.user_id.startsWith("guest_")) {
19
+ response.redirect("/login");
20
+ return null;
21
+ }
22
+ return <ChatApp />;
23
+ }
@@ -0,0 +1,121 @@
1
+ import {
2
+ entity,
3
+ field,
4
+ policy,
5
+ auth,
6
+ buildManifest,
7
+ discoverAppRoutes,
8
+ } from "@pylonsync/sdk";
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // ai-chat — a streaming AI chat app. Tokens stream from the built-in
12
+ // `POST /api/ai/stream` endpoint (your PYLON_AI_API_KEY never leaves the
13
+ // server); the conversation itself is sync-backed, so your chats follow you
14
+ // across tabs and devices in realtime — open two tabs and a reply you send in
15
+ // one shows up in the other as it's saved.
16
+ //
17
+ // Two data entities (+ User):
18
+ // • Conversation — a chat thread. Owner-scoped: you only ever see your own.
19
+ // • Message — one turn (user or assistant). Owner-scoped too. Both use
20
+ // `field.owner()` on userId, so the client can insert with a
21
+ // plain optimistic `db.insert` and the id is stamped from the
22
+ // session — unspoofable, no server function needed to write.
23
+ // • User — the account (email/password is built in).
24
+ //
25
+ // There's no single "owner"/admin — it's multi-user: every signed-in (or guest)
26
+ // visitor gets their own private chats.
27
+ // ---------------------------------------------------------------------------
28
+
29
+ const Conversation = entity(
30
+ "Conversation",
31
+ {
32
+ userId: field.string().owner(),
33
+ title: field.string().default("New chat"),
34
+ createdAt: field.datetime().defaultNow(),
35
+ },
36
+ {
37
+ indexes: [
38
+ { name: "by_user", fields: ["userId"], unique: false },
39
+ { name: "by_created", fields: ["createdAt"], unique: false },
40
+ ],
41
+ },
42
+ );
43
+
44
+ const Message = entity(
45
+ "Message",
46
+ {
47
+ conversationId: field.string(),
48
+ userId: field.string().owner(),
49
+ role: field.string(), // "user" | "assistant"
50
+ content: field.string(),
51
+ createdAt: field.datetime().defaultNow(),
52
+ },
53
+ {
54
+ indexes: [
55
+ { name: "by_conversation", fields: ["conversationId"], unique: false },
56
+ { name: "by_user", fields: ["userId"], unique: false },
57
+ ],
58
+ },
59
+ );
60
+
61
+ const User = entity(
62
+ "User",
63
+ {
64
+ email: field.string(),
65
+ displayName: field.string().optional(),
66
+ passwordHash: field.string().serverOnly().optional(),
67
+ avatarColor: field.string().optional(),
68
+ emailVerified: field.datetime().optional(),
69
+ createdAt: field.datetime().defaultNow(),
70
+ },
71
+ { indexes: [{ name: "by_email", fields: ["email"], unique: true }] },
72
+ );
73
+
74
+ // Conversations + messages are PRIVATE: a signed-in (or guest) user can only
75
+ // read, create, and modify their OWN rows. `field.owner()` stamps userId from
76
+ // the session on insert, so "create your own" is enforced at write time and
77
+ // reads are scoped by the same id — your chats never leak to anyone else, and
78
+ // the sync engine only ever ships you yours.
79
+ const conversationPolicy = policy({
80
+ name: "conversation_owner",
81
+ entity: "Conversation",
82
+ allowRead: "auth.userId == data.userId",
83
+ allowInsert: "auth.userId != null",
84
+ allowUpdate: "auth.userId == data.userId",
85
+ allowDelete: "auth.userId == data.userId",
86
+ });
87
+
88
+ const messagePolicy = policy({
89
+ name: "message_owner",
90
+ entity: "Message",
91
+ allowRead: "auth.userId == data.userId",
92
+ allowInsert: "auth.userId != null",
93
+ allowUpdate: "auth.userId == data.userId",
94
+ allowDelete: "auth.userId == data.userId",
95
+ });
96
+
97
+ const userPolicy = policy({
98
+ name: "user_self",
99
+ entity: "User",
100
+ allowRead: "auth.userId == data.id",
101
+ allowInsert: "false",
102
+ allowUpdate: "false",
103
+ allowDelete: "false",
104
+ });
105
+
106
+ const manifest = buildManifest({
107
+ name: "__APP_NAME__",
108
+ version: "0.1.0",
109
+ entities: [Conversation, Message, User],
110
+ // No custom functions needed: messages are written with optimistic db.insert
111
+ // (owner-stamped), and streaming uses the built-in POST /api/ai/stream.
112
+ queries: [],
113
+ actions: [],
114
+ policies: [conversationPolicy, messagePolicy, userPolicy],
115
+ auth: auth(),
116
+ routes: await discoverAppRoutes(),
117
+ });
118
+
119
+ console.log(JSON.stringify(manifest, null, 2));
120
+
121
+ export default manifest;
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "app/globals.css",
9
+ "baseColor": "zinc",
10
+ "cssVariables": true
11
+ },
12
+ "aliases": {
13
+ "components": "@/components",
14
+ "utils": "@/lib/utils",
15
+ "ui": "@/components/ui",
16
+ "lib": "@/lib",
17
+ "hooks": "@/hooks"
18
+ },
19
+ "iconLibrary": "lucide"
20
+ }
@@ -0,0 +1,10 @@
1
+ node_modules/
2
+ .pylon/
3
+ pylon.manifest.json
4
+ pylon.client.ts
5
+ web/dist/
6
+ *.db
7
+ *.db-*
8
+ .env
9
+ .env.local
10
+ .DS_Store
@@ -0,0 +1,103 @@
1
+ // THE single source of truth for everything brand-specific on this AI chat app.
2
+ // Rebrand the whole thing by editing this ONE file — the layout + chat UI read
3
+ // from here. The create-pylon scaffolder and Mast target this file.
4
+ //
5
+ // Colors live here (applied as CSS variables on <html> in app/layout.tsx).
6
+ // Fictional demo copy — replace the values, keep the shape.
7
+
8
+ /* ----------------------------- types ----------------------------- */
9
+
10
+ export type Social = { label: string; href: string; path: string };
11
+
12
+ export type BaseConfig = {
13
+ brand: {
14
+ name: string;
15
+ letter: string;
16
+ domain: string;
17
+ email: string;
18
+ footerBlurb: string;
19
+ copyrightName: string;
20
+ socials: Social[];
21
+ };
22
+ colors: { brand: string; brandSoft: string; paper: string };
23
+ seo: { title: string; description: string };
24
+ };
25
+
26
+ // A selectable model. `id` is what's sent to /api/ai/stream; `provider` is just
27
+ // a label for the picker. Which models are actually accepted is gated server-side
28
+ // by PYLON_AI_MODELS_ALLOWED (+ the configured provider / gateway) — see README.
29
+ export type ChatModel = { id: string; label: string; provider: string };
30
+
31
+ export type ChatConfig = BaseConfig & {
32
+ chat: {
33
+ assistantName: string;
34
+ // Prepended as a system message on every request — sets the assistant's
35
+ // persona. Edit to retune the assistant without touching code.
36
+ systemPrompt: string;
37
+ emptyHeadline: string;
38
+ emptySubcopy: string;
39
+ inputPlaceholder: string;
40
+ // Starter prompts shown on the empty state; clicking one sends it.
41
+ suggestions: string[];
42
+ // The model picker. Curate to your setup: a single provider's models, or —
43
+ // with an OpenRouter-style gateway (PYLON_AI_PROVIDER=custom) — models across
44
+ // providers in one list. The selected id is sent as `model` per request.
45
+ models: ChatModel[];
46
+ defaultModel: string;
47
+ };
48
+ };
49
+
50
+ /* ----------------------------- config ---------------------------- */
51
+
52
+ export const siteConfig: ChatConfig = {
53
+ brand: {
54
+ name: "Lumen",
55
+ letter: "L",
56
+ domain: "lumen.chat",
57
+ email: "hello@lumen.example",
58
+ footerBlurb: "A fast, streaming AI assistant built on Pylon.",
59
+ copyrightName: "Lumen",
60
+ socials: [
61
+ {
62
+ label: "X",
63
+ href: "https://x.com",
64
+ path: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z",
65
+ },
66
+ ],
67
+ },
68
+
69
+ colors: { brand: "#6d28d9", brandSoft: "#ede9fe", paper: "#faf9fc" },
70
+
71
+ seo: {
72
+ title: "Lumen — a fast streaming AI assistant.",
73
+ description:
74
+ "A streaming AI chat app built on Pylon. Conversations sync across your tabs and devices in realtime; your API key never leaves the server.",
75
+ },
76
+
77
+ chat: {
78
+ assistantName: "Lumen",
79
+ systemPrompt:
80
+ "You are Lumen, a friendly, concise AI assistant. Answer clearly and get to the point. Use Markdown when it helps.",
81
+ emptyHeadline: "How can I help?",
82
+ emptySubcopy: "Ask anything. Your conversations are saved and stay in sync across your tabs.",
83
+ inputPlaceholder: "Message Lumen…",
84
+ suggestions: [
85
+ "Explain WebSockets like I'm five.",
86
+ "Draft a friendly out-of-office reply.",
87
+ "Give me 5 dinner ideas using chicken and rice.",
88
+ "What's the difference between SSR and SSG?",
89
+ ],
90
+ // Current Claude models (verify the ids for your provider before shipping —
91
+ // model names move fast). Whichever you keep here must be in
92
+ // PYLON_AI_MODELS_ALLOWED for switching to work (see .env.example). To offer
93
+ // models from OTHER providers (OpenAI, Google, …) in the same list, route
94
+ // through an OpenRouter-style gateway (PYLON_AI_PROVIDER=custom +
95
+ // PYLON_AI_BASE_URL) and use that gateway's slugs here.
96
+ models: [
97
+ { id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", provider: "Anthropic" },
98
+ { id: "claude-opus-4-8", label: "Claude Opus 4.8", provider: "Anthropic" },
99
+ { id: "claude-haiku-4-5", label: "Claude Haiku 4.5", provider: "Anthropic" },
100
+ ],
101
+ defaultModel: "claude-sonnet-4-6",
102
+ },
103
+ };
@@ -0,0 +1,10 @@
1
+ import { type ClassValue, clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ // `cn` — the shadcn class merger. clsx resolves conditional/array class
5
+ // inputs; tailwind-merge then dedupes conflicting Tailwind utilities so
6
+ // the last one wins (e.g. `cn("px-2", "px-4")` → "px-4"). Every shadcn
7
+ // component routes its className through this.
8
+ export function cn(...inputs: ClassValue[]) {
9
+ return twMerge(clsx(inputs));
10
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "__APP_NAME_KEBAB__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "pylon dev",
8
+ "deploy": "pylon deploy",
9
+ "check": "tsc --noEmit"
10
+ },
11
+ "dependencies": {
12
+ "@pylonsync/react": "^__PYLON_VERSION__",
13
+ "@pylonsync/sdk": "^__PYLON_VERSION__",
14
+ "@pylonsync/functions": "^__PYLON_VERSION__",
15
+ "@pylonsync/client": "^__PYLON_VERSION__",
16
+ "react": "^19.0.0",
17
+ "react-dom": "^19.0.0",
18
+ "tailwindcss": "^4.3.0",
19
+ "@tailwindcss/cli": "^4.3.0",
20
+ "tw-animate-css": "^1.2.0",
21
+ "class-variance-authority": "^0.7.1",
22
+ "clsx": "^2.1.1",
23
+ "tailwind-merge": "^2.5.0",
24
+ "lucide-react": "^0.460.0",
25
+ "@radix-ui/react-slot": "^1.1.0"
26
+ },
27
+ "devDependencies": {
28
+ "@pylonsync/cli": "^__PYLON_VERSION__",
29
+ "@types/node": "^22.0.0",
30
+ "@types/react": "^19.0.0",
31
+ "@types/react-dom": "^19.0.0",
32
+ "typescript": "^5.6.0"
33
+ }
34
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "jsx": "react",
7
+ "esModuleInterop": true,
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "lib": ["ES2022", "DOM"],
11
+ "types": ["react", "react-dom", "node"],
12
+ "baseUrl": ".",
13
+ "paths": {
14
+ "@/*": ["./*"]
15
+ }
16
+ },
17
+ "include": ["app.ts", "app/**/*", "components/**/*", "lib/**/*", "functions/**/*"]
18
+ }
@@ -0,0 +1,19 @@
1
+ # Copy to `.env` and fill in. `pylon dev` loads `.env` automatically.
2
+ #
3
+ # The studio boots with NO config — generations return a clearly-labeled
4
+ # placeholder so the flow + live gallery work out of the box. Add a Replicate
5
+ # token to generate real image / audio / video. The token + provider call stay
6
+ # on the server (read only inside the pollGeneration background job).
7
+
8
+ # ── Replicate (optional — enables real generation) ───────────────────────────
9
+ # Get a token at https://replicate.com/account/api-tokens
10
+ # REPLICATE_API_TOKEN=r8_...
11
+
12
+ # Override the model for any medium (defaults shown). They run the model's
13
+ # latest version via Replicate's model-name endpoint, so no version hashes.
14
+ # REPLICATE_IMAGE_MODEL=black-forest-labs/flux-schnell
15
+ # REPLICATE_AUDIO_MODEL=meta/musicgen
16
+ # REPLICATE_VIDEO_MODEL=minimax/video-01
17
+
18
+ # ── Site URL (optional) ──────────────────────────────────────────────────────
19
+ # SITE_URL=https://yourstudio.com
@@ -0,0 +1,61 @@
1
+ # AGENTS.md — working in a Pylon project
2
+
3
+ Operating rules for a coding agent in this Pylon app. Pylon is a Rails-like framework for realtime apps: you declare entities, policies, and server functions in TypeScript, and a single Rust binary (`pylon`) serves the API, auth, sync, WebSocket, SSE, and native React 19 SSR — one process, one port. The full API reference is at **/llms-full.txt** (served at `/llms-full.txt`; in the repo at `apps/web/public/llms-full.txt`). Read it before guessing an API name.
4
+
5
+ ## Directory conventions
6
+
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))`.
9
+ - `app/` — file-based SSR routes. `app/page.tsx` → `/`, `app/about/page.tsx` → `/about`, `app/blog/[slug]/page.tsx` → `/blog/:slug`. `app/layout.tsx` is the shell; `app/error.tsx` / `app/not-found.tsx` are boundaries.
10
+ - `app/globals.css` — Tailwind v4 entrypoint (auto-compiled and injected).
11
+ - `functions/` — server functions, one per file, `default`-exported.
12
+ - `.pylon/` — local dev state (sqlite, jobs, sessions, uploads). Created by `pylon dev`. Do not commit.
13
+
14
+ **Monorepo app:** backend is `apps/api/` (entry `apps/api/schema.ts`, handlers in `apps/api/functions/`); frontend in `apps/web/`. `pylon.manifest.json` / `pylon.client.ts` are generated — do not hand-edit.
15
+
16
+ ## The core authoring loop
17
+
18
+ 1. **Define an entity** — `entity("Thing", { name: field.string(), done: field.boolean().default(false) })`. Modifiers: `.optional()`, `.unique()`, `.readonly()` (settable on insert, rejected on client update — use for `authorId`/`orgId`), `.serverOnly()` (never in HTTP responses), `.encrypted()` (AEAD at rest, needs `PYLON_ENCRYPTION_KEY`), `.crdt("text")` (collaborative).
19
+ 2. **Write a policy** — `policy({ entity: "Thing", allowRead, allowInsert, allowUpdate, allowDelete })` with CEL-like expressions over `auth.*` / `data.*` (e.g. `"auth.userId == data.authorId"`). **Omitted actions DENY by default.** Wide-open dev policies (`allow*: "true"`) are flagged by `pylon lint` — tighten before shipping.
20
+ 3. **Author a function** in `functions/<name>.ts` — `query` (read-only), `mutation` (transactional read+write), or `action` (external I/O, no direct `ctx.db`). Import `{ query, mutation, action, v }` from `@pylonsync/functions`. `auth` defaults to `"user"` (secure-by-default); set `"public"` explicitly for unauthenticated access. Use `ctx.db.*`, `ctx.auth.userId`, `ctx.error(code, msg)`.
21
+ 4. **Read it on the client** — `db.useQuery("Thing")` (live, re-renders on any write) or `db.useQueryOne("Thing", id)`. Call functions with `db.fn(name, args)` / `callFn`. On SSR pages, read via `use(serverData.list("Thing"))` inside `<Suspense>`.
22
+
23
+ ## Key gotchas
24
+
25
+ - **Policies deny by default; server functions BYPASS them.** Direct client CRUD (`/api/entities/*`) and sync are policy-checked. Functions run with full DB access — enforce trust with `ctx.auth` checks inside the handler, not policies.
26
+ - **Type page props from the SDK, don't hand-roll them.** `import type { PageProps, Metadata } from "@pylonsync/react"`. Every page/layout gets `{ url, params, searchParams, auth, response, serverData }`; `PageProps<{ slug: string }>` types a `[slug]` route's params. Request headers/cookies are intentionally NOT on `PageProps` — they're server-only and stripped from hydration, so reading them in the render would mismatch.
27
+ - **Anonymous output caching is opt-in + earned.** `export const revalidate = 60` (seconds) on a page makes it CDN-cacheable (`public, s-maxage=60`) — but ONLY if the render is auth-INDEPENDENT: it must NOT read `props.auth` (reading it at all opts out, even for anonymous), set no cookie, and the app must not run strict per-caller policies (`PYLON_STRICT_FN_POLICIES`). `export const dynamic = "force-static"` caches until the next deploy; `"force-dynamic"` never caches. Fail-closed: without the opt-in (or if any condition fails) the page is `no-cache`. A page that reads `auth` or sets a cookie is never shared. The SAME earned render is also kept in an **origin disk cache** (`.pylon/.cache/ssr`): a cookie-less GET with no query string is served straight off disk for the TTL — skipping the render entirely — then re-rendered live when stale. The disk cache is namespaced per deploy (wiped on each new build) and OFF in `pylon dev` (so an edit is never masked by a stale entry); invalidation is by the `revalidate` TTL or the next deploy.
28
+ - **No-JS forms use `route.ts` + `<Form>`.** Drop `app/.../route.ts` exporting `export const POST: RouteHandler = async ({ form, db, response, auth }) => { await db.insert("X", {...}); response.redirect("/x?ok=1"); }` (303 POST-redirect-GET by default). Render `<Form action="/x">` (from @pylonsync/react) with plain `<input name=...>` — works with JS off (native POST→handler→redirect) and is enhanced to no-reload when JS is on. The handler's `db` is read+write (mutation trust model — gate on `auth`); CSRF is automatic (Origin gate + SameSite=Lax). Multipart/file uploads aren't supported yet — use urlencoded forms + `/api/files`.
29
+ - **`loading.tsx` streams a skeleton while the page's data resolves.** Drop `app/.../loading.tsx` (default export, page props) and the nearest one becomes a route-level Suspense fallback: Pylon flushes the shell + skeleton immediately, then reveals the real page when its top-level `use(serverData…)` resolves (no blank page). It only shows when the PAGE suspends — a page that wraps its own `<Suspense>` around a child (like `/dashboard` in this template) handles that itself. The skeleton is SERVER-ONLY: don't read `serverData` in it. A page with no `loading.tsx` is buffered (unchanged).
30
+ - **`export const streaming = true` streams a page's OWN inner `<Suspense>` boundaries.** Without it (and without a `loading.tsx`), a page is BUFFERED — the whole document, including suspended children, resolves before the first byte. Opt in and the shell + each inner `<Suspense>` fallback flush immediately, then each boundary's real content streams in as its data resolves (multi-boundary progressive streaming). It's opt-in because it changes the response timing contract: a streaming render commits its HTTP head BEFORE suspended subtrees finish, so (a) it's never CDN/disk cacheable — don't combine with `export const revalidate`; (b) `response.setStatus/setCookie/redirect/notFound` only take effect from the SYNCHRONOUS shell render — a call from inside a suspended subtree is dropped (the runtime logs a loud warning naming what was lost); (c) a `throw` from a deep `<Suspense>` child resolves via its nearest `error.tsx` at HTTP 200, not a 5xx. Hydration is clean for any number of boundaries (the data blob ships before hydration runs). Type the config with `import type { RouteSegmentConfig } from "@pylonsync/react"`.
31
+ - **`error.tsx` / `not-found.tsx` boundaries are HYDRATED (interactive).** `app/.../error.tsx` catches a throw below it (HTTP 500) and receives `{ error: { message, digest }, reset }` (`import type { ErrorBoundaryProps }`) — `reset()` re-attempts the route; the stack NEVER reaches the client (dev overlay + logs only). `app/.../not-found.tsx` renders at 404 (also for `response.notFound()`) and gets the page props (`NotFoundProps`), no `reset`. Both run useState/onClick/hooks.
32
+ - **Client navigation hooks live in @pylonsync/react.** `useRouter()` → `{ push, replace, back, forward, refresh, prefetch }`; `useSearchParams()` → reactive `URLSearchParams`; `usePathname()` → reactive pathname. The hooks are CLIENT-reactive — during SSR they return defaults (empty params / "/"); for server-side URL values read the `url` / `searchParams` page props.
33
+ - **Dynamic + catch-all routes follow Next conventions.** `app/blog/[slug]/page.tsx` → `params.slug`. `app/docs/[...path]/page.tsx` is a catch-all (matches `/docs/a/b/c`; `params.path === "a/b/c"` — `.split("/")` for segments). `app/shop/[[...filters]]/page.tsx` is an optional catch-all (also matches the bare `/shop`, with `params.filters === ""`). A catch-all must be the last segment; static beats dynamic beats catch-all on overlap.
34
+ - **`serverData` (SSR) is READ-ONLY.** No write methods; the runtime rejects write frames (`SSR_WRITE_FORBIDDEN`). Mutations belong in actions/functions, never in a page render.
35
+ - **`response.*` / `response.redirect()` / `response.notFound()` must fire in the synchronous shell render**, before any `await` / `<Suspense>`. The HTTP head commits when the shell is ready — status/headers/cookies set from a suspended subtree are lost, and `redirect`/`notFound` thrown below a Suspense boundary are swallowed.
36
+ - **`ctx.llm` and `ctx.connections` are on mutation + action only, NOT query** (reactive purity). `action` has no direct `ctx.db` — use `ctx.runQuery` / `ctx.runMutation`.
37
+ - **It's `db.useQueryOne`, not `useOne`.** Validators and field types have aliases: `v.bool`/`v.boolean`, `v.float`/`v.number`.
38
+ - **There is no `ctx.files` or `defineWorkflow`/`defineJob`.** Files go through `<FileUpload>` + `/api/files/*`; deferred execution is `ctx.scheduler.runAfter/runAt/cancel`.
39
+
40
+ ## Use the CLI — don't guess
41
+
42
+ | Need | Command |
43
+ |---|---|
44
+ | Run the app (SSR + API, hot reload, one port `:4321`) | `pylon dev` (or `npm run dev`) |
45
+ | Regenerate manifest + typed client | `pylon codegen` (Swift client: `pylon codegen client --target swift`) |
46
+ | Validate / diff / push schema | `pylon schema check` \| `diff` \| `push` |
47
+ | Migrations | `pylon migrate create <name>` \| `plan` \| `apply` |
48
+ | Lint policies (PYL001–PYL004) | `pylon lint --strict` |
49
+ | Tests | `pylon test` |
50
+ | Adversarial security probe | `pylon test:security` |
51
+ | Inspect cloud request logs (agent-safe) | `pylon logs --json --limit 50` |
52
+ | Inspect data / entities | `pylon data entities` \| `pylon data list <Entity>` |
53
+ | Call a function | `pylon fn <name> key=value` |
54
+ | Health snapshot | `pylon status` |
55
+ | Build for prod | `pylon build` |
56
+ | Deploy (Pylon Cloud by default) | `pylon deploy` |
57
+ | Look up an error code | `pylon explain <CODE>` |
58
+
59
+ `--json` works on every command for machine-readable output. Prefer one-shot/agent-safe flags (`pylon logs --limit N`, not a blocking `--follow`).
60
+
61
+ For full signatures, env vars, the complete CLI, and SSR/client/server-primitive details: **/llms-full.txt**.
@@ -0,0 +1,83 @@
1
+ # __APP_NAME__
2
+
3
+ A generative **AI media studio** (image / audio / video) built with
4
+ [Pylon](https://pylonsync.com) — a live gallery that fills in as each generation
5
+ finishes, from one binary on one port. No Next.js, no separate job service.
6
+
7
+ Kick off a generation and a card appears instantly, then flips to the finished
8
+ result the moment the generation completes — live, across every open tab. The
9
+ generation runs in a **background job** (so even a minutes-long video never
10
+ blocks your request), and the provider call + API token stay on the server.
11
+
12
+ ## Develop
13
+
14
+ ```bash
15
+ __RUN_DEV__
16
+ ```
17
+
18
+ Open http://localhost:4321 and generate something — it works with **no config**
19
+ (a clearly-labeled placeholder). Add a Replicate token for real media (below).
20
+ Then **open a second tab** — your gallery stays in sync.
21
+
22
+ ## Enable real generation (Replicate)
23
+
24
+ ```bash
25
+ # .env — get a token at https://replicate.com/account/api-tokens
26
+ REPLICATE_API_TOKEN=r8_...
27
+ # optional model overrides (defaults shown):
28
+ REPLICATE_IMAGE_MODEL=black-forest-labs/flux-schnell
29
+ REPLICATE_AUDIO_MODEL=meta/musicgen
30
+ REPLICATE_VIDEO_MODEL=minimax/video-01
31
+ ```
32
+
33
+ One provider, all three media. Models run via Replicate's model-name endpoint
34
+ (latest version — no version hashes to maintain). Results are the provider's
35
+ hosted URLs.
36
+
37
+ ## How it works (background jobs + realtime)
38
+
39
+ - `Generation` is an **owner-scoped** entity read with `db.useQuery` — private
40
+ per user. Clients can't write it; only the server-side pipeline does.
41
+ - `functions/generate.ts` (a `mutation`) inserts a `pending` row and enqueues a
42
+ job with `ctx.scheduler.runAfter` — then returns. No network I/O on the
43
+ request path, so slow models can't time it out.
44
+ - `functions/pollGeneration.ts` (a scheduled `action`) starts the Replicate
45
+ prediction, then **reschedules itself** every few seconds until it settles,
46
+ writing the result via the internal `_updateGeneration` mutation. Each write
47
+ syncs to the owner's gallery — `pending → processing → done` — live.
48
+
49
+ ## Notes
50
+
51
+ - Without a token, image generations return an SVG placeholder; audio/video
52
+ cards show a "add a token" note. The whole flow (and the background job) still
53
+ runs, so you can see the realtime gallery with zero config.
54
+ - Results are Replicate's hosted URLs (fine for a live studio). For permanent
55
+ storage, download the asset in the job and persist via `/api/files`.
56
+ - Video is genuinely wired (Replicate has text-to-video models) — it just takes
57
+ longer, which is exactly why the work runs in a background job.
58
+
59
+ ## Rebrand it
60
+
61
+ Brand, colors, the generation kinds, and the starter prompts all live in
62
+ **`lib/site.config.ts`**.
63
+
64
+ ## Layout
65
+
66
+ ```
67
+ app.ts Generation (owner-scoped) + User
68
+ lib/site.config.ts brand + kinds + example prompts (edit this)
69
+ lib/studio.ts types + the no-token placeholder generator
70
+ functions/generate.ts mutation: insert pending + enqueue the job
71
+ functions/pollGeneration.ts scheduled action: Replicate call + self-poll
72
+ functions/_getGeneration.ts, _updateGeneration.ts internal read/write
73
+ app/page.tsx header + studio island
74
+ app/studio-client.tsx prompt bar + kind selector + live gallery
75
+ ```
76
+
77
+ ## Deploy
78
+
79
+ ```bash
80
+ pylon deploy
81
+ ```
82
+
83
+ Docs: https://docs.pylonsync.com