@helloleo/plugins 0.1.20 → 0.2.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/dist/index.js +12364 -65
  2. package/dist/templates/tanstack-start-2026-06-18/components.json +22 -0
  3. package/dist/templates/tanstack-start-2026-06-18/drizzle.config.ts +15 -0
  4. package/dist/templates/tanstack-start-2026-06-18/package.json +103 -0
  5. package/dist/templates/tanstack-start-2026-06-18/src/api/health.ts +66 -0
  6. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/accordion.tsx +51 -0
  7. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/alert-dialog.tsx +115 -0
  8. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/alert.tsx +49 -0
  9. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/aspect-ratio.tsx +5 -0
  10. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/avatar.tsx +47 -0
  11. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/badge.tsx +32 -0
  12. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/breadcrumb.tsx +101 -0
  13. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/button.tsx +49 -0
  14. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/calendar.tsx +177 -0
  15. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/card.tsx +55 -0
  16. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/carousel.tsx +240 -0
  17. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/chart.tsx +331 -0
  18. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/checkbox.tsx +26 -0
  19. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/collapsible.tsx +11 -0
  20. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/command.tsx +143 -0
  21. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/context-menu.tsx +187 -0
  22. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/dialog.tsx +104 -0
  23. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/drawer.tsx +98 -0
  24. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/dropdown-menu.tsx +188 -0
  25. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/form.tsx +171 -0
  26. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/hover-card.tsx +27 -0
  27. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/input-otp.tsx +69 -0
  28. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/input.tsx +22 -0
  29. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/label.tsx +21 -0
  30. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/menubar.tsx +229 -0
  31. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/navigation-menu.tsx +120 -0
  32. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/pagination.tsx +98 -0
  33. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/popover.tsx +31 -0
  34. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/progress.tsx +25 -0
  35. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/radio-group.tsx +36 -0
  36. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/resizable.tsx +37 -0
  37. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/scroll-area.tsx +44 -0
  38. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/select.tsx +152 -0
  39. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/separator.tsx +24 -0
  40. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/sheet.tsx +122 -0
  41. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/sidebar.tsx +744 -0
  42. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/skeleton.tsx +7 -0
  43. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/slider.tsx +23 -0
  44. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/sonner.tsx +23 -0
  45. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/switch.tsx +27 -0
  46. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/table.tsx +94 -0
  47. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/tabs.tsx +53 -0
  48. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/textarea.tsx +21 -0
  49. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/toggle-group.tsx +57 -0
  50. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/toggle.tsx +42 -0
  51. package/dist/templates/tanstack-start-2026-06-18/src/components/ui/tooltip.tsx +32 -0
  52. package/dist/templates/tanstack-start-2026-06-18/src/db/index.ts +9 -0
  53. package/dist/templates/tanstack-start-2026-06-18/src/db/schema.ts +18 -0
  54. package/dist/templates/tanstack-start-2026-06-18/src/global.d.ts +3 -0
  55. package/dist/templates/tanstack-start-2026-06-18/src/hooks/use-mobile.tsx +19 -0
  56. package/dist/templates/tanstack-start-2026-06-18/src/lib/proxy.ts +83 -0
  57. package/dist/templates/tanstack-start-2026-06-18/src/lib/utils.ts +6 -0
  58. package/dist/templates/tanstack-start-2026-06-18/src/routeTree.gen.ts +59 -0
  59. package/dist/templates/tanstack-start-2026-06-18/src/router.tsx +33 -0
  60. package/dist/templates/tanstack-start-2026-06-18/src/routes/__root.tsx +60 -0
  61. package/dist/templates/tanstack-start-2026-06-18/src/routes/index.tsx +33 -0
  62. package/dist/templates/tanstack-start-2026-06-18/src/server.ts +32 -0
  63. package/dist/templates/tanstack-start-2026-06-18/src/styles.css +214 -0
  64. package/dist/templates/tanstack-start-2026-06-18/tsconfig.json +27 -0
  65. package/dist/templates/tanstack-start-2026-06-18/vite.config.ts +17 -0
  66. package/package.json +1 -1
  67. package/src/template-render.ts +5 -0
  68. package/src/templates/tanstack-start-2026-06-18/components.json +22 -0
  69. package/src/templates/tanstack-start-2026-06-18/drizzle.config.ts +15 -0
  70. package/src/templates/tanstack-start-2026-06-18/package.json +103 -0
  71. package/src/templates/tanstack-start-2026-06-18/src/api/health.ts +66 -0
  72. package/src/templates/tanstack-start-2026-06-18/src/components/ui/accordion.tsx +51 -0
  73. package/src/templates/tanstack-start-2026-06-18/src/components/ui/alert-dialog.tsx +115 -0
  74. package/src/templates/tanstack-start-2026-06-18/src/components/ui/alert.tsx +49 -0
  75. package/src/templates/tanstack-start-2026-06-18/src/components/ui/aspect-ratio.tsx +5 -0
  76. package/src/templates/tanstack-start-2026-06-18/src/components/ui/avatar.tsx +47 -0
  77. package/src/templates/tanstack-start-2026-06-18/src/components/ui/badge.tsx +32 -0
  78. package/src/templates/tanstack-start-2026-06-18/src/components/ui/breadcrumb.tsx +101 -0
  79. package/src/templates/tanstack-start-2026-06-18/src/components/ui/button.tsx +49 -0
  80. package/src/templates/tanstack-start-2026-06-18/src/components/ui/calendar.tsx +177 -0
  81. package/src/templates/tanstack-start-2026-06-18/src/components/ui/card.tsx +55 -0
  82. package/src/templates/tanstack-start-2026-06-18/src/components/ui/carousel.tsx +240 -0
  83. package/src/templates/tanstack-start-2026-06-18/src/components/ui/chart.tsx +331 -0
  84. package/src/templates/tanstack-start-2026-06-18/src/components/ui/checkbox.tsx +26 -0
  85. package/src/templates/tanstack-start-2026-06-18/src/components/ui/collapsible.tsx +11 -0
  86. package/src/templates/tanstack-start-2026-06-18/src/components/ui/command.tsx +143 -0
  87. package/src/templates/tanstack-start-2026-06-18/src/components/ui/context-menu.tsx +187 -0
  88. package/src/templates/tanstack-start-2026-06-18/src/components/ui/dialog.tsx +104 -0
  89. package/src/templates/tanstack-start-2026-06-18/src/components/ui/drawer.tsx +98 -0
  90. package/src/templates/tanstack-start-2026-06-18/src/components/ui/dropdown-menu.tsx +188 -0
  91. package/src/templates/tanstack-start-2026-06-18/src/components/ui/form.tsx +171 -0
  92. package/src/templates/tanstack-start-2026-06-18/src/components/ui/hover-card.tsx +27 -0
  93. package/src/templates/tanstack-start-2026-06-18/src/components/ui/input-otp.tsx +69 -0
  94. package/src/templates/tanstack-start-2026-06-18/src/components/ui/input.tsx +22 -0
  95. package/src/templates/tanstack-start-2026-06-18/src/components/ui/label.tsx +21 -0
  96. package/src/templates/tanstack-start-2026-06-18/src/components/ui/menubar.tsx +229 -0
  97. package/src/templates/tanstack-start-2026-06-18/src/components/ui/navigation-menu.tsx +120 -0
  98. package/src/templates/tanstack-start-2026-06-18/src/components/ui/pagination.tsx +98 -0
  99. package/src/templates/tanstack-start-2026-06-18/src/components/ui/popover.tsx +31 -0
  100. package/src/templates/tanstack-start-2026-06-18/src/components/ui/progress.tsx +25 -0
  101. package/src/templates/tanstack-start-2026-06-18/src/components/ui/radio-group.tsx +36 -0
  102. package/src/templates/tanstack-start-2026-06-18/src/components/ui/resizable.tsx +37 -0
  103. package/src/templates/tanstack-start-2026-06-18/src/components/ui/scroll-area.tsx +44 -0
  104. package/src/templates/tanstack-start-2026-06-18/src/components/ui/select.tsx +152 -0
  105. package/src/templates/tanstack-start-2026-06-18/src/components/ui/separator.tsx +24 -0
  106. package/src/templates/tanstack-start-2026-06-18/src/components/ui/sheet.tsx +122 -0
  107. package/src/templates/tanstack-start-2026-06-18/src/components/ui/sidebar.tsx +744 -0
  108. package/src/templates/tanstack-start-2026-06-18/src/components/ui/skeleton.tsx +7 -0
  109. package/src/templates/tanstack-start-2026-06-18/src/components/ui/slider.tsx +23 -0
  110. package/src/templates/tanstack-start-2026-06-18/src/components/ui/sonner.tsx +23 -0
  111. package/src/templates/tanstack-start-2026-06-18/src/components/ui/switch.tsx +27 -0
  112. package/src/templates/tanstack-start-2026-06-18/src/components/ui/table.tsx +94 -0
  113. package/src/templates/tanstack-start-2026-06-18/src/components/ui/tabs.tsx +53 -0
  114. package/src/templates/tanstack-start-2026-06-18/src/components/ui/textarea.tsx +21 -0
  115. package/src/templates/tanstack-start-2026-06-18/src/components/ui/toggle-group.tsx +57 -0
  116. package/src/templates/tanstack-start-2026-06-18/src/components/ui/toggle.tsx +42 -0
  117. package/src/templates/tanstack-start-2026-06-18/src/components/ui/tooltip.tsx +32 -0
  118. package/src/templates/tanstack-start-2026-06-18/src/db/index.ts +9 -0
  119. package/src/templates/tanstack-start-2026-06-18/src/db/schema.ts +18 -0
  120. package/src/templates/tanstack-start-2026-06-18/src/global.d.ts +3 -0
  121. package/src/templates/tanstack-start-2026-06-18/src/hooks/use-mobile.tsx +19 -0
  122. package/src/templates/tanstack-start-2026-06-18/src/lib/proxy.ts +83 -0
  123. package/src/templates/tanstack-start-2026-06-18/src/lib/utils.ts +6 -0
  124. package/src/templates/tanstack-start-2026-06-18/src/routeTree.gen.ts +59 -0
  125. package/src/templates/tanstack-start-2026-06-18/src/router.tsx +33 -0
  126. package/src/templates/tanstack-start-2026-06-18/src/routes/__root.tsx +60 -0
  127. package/src/templates/tanstack-start-2026-06-18/src/routes/index.tsx +33 -0
  128. package/src/templates/tanstack-start-2026-06-18/src/server.ts +32 -0
  129. package/src/templates/tanstack-start-2026-06-18/src/styles.css +214 -0
  130. package/src/templates/tanstack-start-2026-06-18/tsconfig.json +27 -0
  131. package/src/templates/tanstack-start-2026-06-18/vite.config.ts +17 -0
  132. package/src/tools/scaffold.ts +41 -43
  133. package/src/tools/setup-netsuite.ts +32 -65
@@ -0,0 +1,60 @@
1
+ import {
2
+ HeadContent,
3
+ Outlet,
4
+ Scripts,
5
+ createRootRouteWithContext,
6
+ } from '@tanstack/react-router'
7
+ import type { QueryClient } from '@tanstack/react-query'
8
+ import type { ReactNode } from 'react'
9
+
10
+ // Side-effect order matters: Tailwind needs to be linked into the SSR'd
11
+ // document. Importing the stylesheet as a URL and listing it in `head.links`
12
+ // is the Start-native way (a bare `import './styles.css'` only works client-side).
13
+ import appCss from '../styles.css?url'
14
+
15
+ export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
16
+ head: () => ({
17
+ meta: [
18
+ { charSet: 'utf-8' },
19
+ { name: 'viewport', content: 'width=device-width, initial-scale=1.0' },
20
+ { title: '{{LEO_NAME}}' },
21
+ { name: 'description', content: '{{LEO_DESCRIPTION}}' },
22
+ ],
23
+ links: [
24
+ { rel: 'stylesheet', href: appCss },
25
+ { rel: 'preconnect', href: 'https://fonts.googleapis.com' },
26
+ {
27
+ rel: 'preconnect',
28
+ href: 'https://fonts.gstatic.com',
29
+ crossOrigin: 'anonymous',
30
+ },
31
+ {
32
+ rel: 'stylesheet',
33
+ href: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap',
34
+ },
35
+ ],
36
+ }),
37
+ component: RootComponent,
38
+ })
39
+
40
+ function RootComponent() {
41
+ return (
42
+ <RootDocument>
43
+ <Outlet />
44
+ </RootDocument>
45
+ )
46
+ }
47
+
48
+ function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
49
+ return (
50
+ <html lang="en">
51
+ <head>
52
+ <HeadContent />
53
+ </head>
54
+ <body>
55
+ {children}
56
+ <Scripts />
57
+ </body>
58
+ </html>
59
+ )
60
+ }
@@ -0,0 +1,33 @@
1
+ import { createFileRoute } from '@tanstack/react-router'
2
+ import { createServerFn } from '@tanstack/react-start'
3
+
4
+ // Server functions run only on the server (dev: node + cf-shim; prod: the
5
+ // worker). This is the seam where you read your bindings — import { db } from
6
+ // '#/db' and query Drizzle, or import { kv, storage } from '@helloleo/runtime'.
7
+ // Keep binding access INSIDE the handler, never at module scope.
8
+ const getMessage = createServerFn({ method: 'GET' }).handler(() => {
9
+ return { message: 'Hello from the server' }
10
+ })
11
+
12
+ export const Route = createFileRoute('/')({
13
+ component: Home,
14
+ loader: () => getMessage(),
15
+ })
16
+
17
+ /**
18
+ * Do not `export default function Home()`. This breaks the default
19
+ * codesplitting by Tanstack Router.
20
+ */
21
+ function Home() {
22
+ const { message } = Route.useLoaderData()
23
+
24
+ return (
25
+ <div className="p-8">
26
+ <h1 className="text-4xl font-bold">Welcome to HelloLeo</h1>
27
+ <p className="mt-4 text-lg">{message}</p>
28
+ <p className="mt-2 text-sm text-muted-foreground">
29
+ Edit <code>src/routes/index.tsx</code> to get started.
30
+ </p>
31
+ </div>
32
+ )
33
+ }
@@ -0,0 +1,32 @@
1
+ // Cloudflare Worker entry (the Worker `main`, configured by @helloleo/vite-config).
2
+ // Wraps the TanStack Start server entry so an SSR failure renders a clean 500
3
+ // instead of a raw stack.
4
+ type ServerEntry = {
5
+ fetch: (request: Request, env: unknown, ctx: unknown) => Promise<Response> | Response
6
+ }
7
+
8
+ let serverEntryPromise: Promise<ServerEntry> | undefined
9
+
10
+ async function getServerEntry(): Promise<ServerEntry> {
11
+ if (!serverEntryPromise) {
12
+ serverEntryPromise = import('@tanstack/react-start/server-entry').then(
13
+ (m) => ((m as { default?: ServerEntry }).default ?? m) as ServerEntry,
14
+ )
15
+ }
16
+ return serverEntryPromise
17
+ }
18
+
19
+ export default {
20
+ async fetch(request: Request, env: unknown, ctx: unknown) {
21
+ try {
22
+ const handler = await getServerEntry()
23
+ return await handler.fetch(request, env, ctx)
24
+ } catch (error) {
25
+ console.error(error)
26
+ return new Response('<h1>Something went wrong</h1>', {
27
+ status: 500,
28
+ headers: { 'content-type': 'text/html; charset=utf-8' },
29
+ })
30
+ }
31
+ },
32
+ }
@@ -0,0 +1,214 @@
1
+ /*
2
+ * HelloLeo Design System -- READ BEFORE STYLING.
3
+ *
4
+ * Design tokens are the single source of truth and power the no-code Design
5
+ * panel (the user edits colors / fonts / radius visually). Conventions:
6
+ * - Raw tokens live in :root (light) and .dark (dark). The @media
7
+ * (prefers-color-scheme: dark) block mirrors .dark so apps follow the OS
8
+ * by default; adding the `dark` class still forces dark explicitly.
9
+ * - @theme inline maps each raw token to a Tailwind utility
10
+ * (bg-background, text-foreground, rounded-lg, ...). Edit the raw token,
11
+ * not the component class. Never hardcode #hex in components.
12
+ * - When you add a color token, add it to :root, .dark, AND the media block,
13
+ * or one theme breaks.
14
+ *
15
+ * UI primitives live in src/components/ui (shadcn/ui) and consume these
16
+ * tokens. One responsibility per file.
17
+ */
18
+
19
+ @import "tailwindcss" source(none);
20
+ @source "../src";
21
+ @import "tw-animate-css";
22
+
23
+ @custom-variant dark (&:is(.dark *));
24
+
25
+ @theme inline {
26
+ --radius-sm: calc(var(--radius) - 4px);
27
+ --radius-md: calc(var(--radius) - 2px);
28
+ --radius-lg: var(--radius);
29
+ --radius-xl: calc(var(--radius) + 4px);
30
+ --color-background: var(--background);
31
+ --color-foreground: var(--foreground);
32
+ --color-card: var(--card);
33
+ --color-card-foreground: var(--card-foreground);
34
+ --color-popover: var(--popover);
35
+ --color-popover-foreground: var(--popover-foreground);
36
+ --color-primary: var(--primary);
37
+ --color-primary-foreground: var(--primary-foreground);
38
+ --color-secondary: var(--secondary);
39
+ --color-secondary-foreground: var(--secondary-foreground);
40
+ --color-muted: var(--muted);
41
+ --color-muted-foreground: var(--muted-foreground);
42
+ --color-accent: var(--accent);
43
+ --color-accent-foreground: var(--accent-foreground);
44
+ --color-destructive: var(--destructive);
45
+ --color-destructive-foreground: var(--destructive-foreground);
46
+ --color-border: var(--border);
47
+ --color-input: var(--input);
48
+ --color-ring: var(--ring);
49
+ --color-chart-1: var(--chart-1);
50
+ --color-chart-2: var(--chart-2);
51
+ --color-chart-3: var(--chart-3);
52
+ --color-chart-4: var(--chart-4);
53
+ --color-chart-5: var(--chart-5);
54
+ --color-sidebar: var(--sidebar);
55
+ --color-sidebar-foreground: var(--sidebar-foreground);
56
+ --color-sidebar-primary: var(--sidebar-primary);
57
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
58
+ --color-sidebar-accent: var(--sidebar-accent);
59
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
60
+ --color-sidebar-border: var(--sidebar-border);
61
+ --color-sidebar-ring: var(--sidebar-ring);
62
+ --font-sans: "Inter", system-ui, sans-serif;
63
+ --font-display: "Space Grotesk", "Inter", sans-serif;
64
+ }
65
+
66
+ :root {
67
+ --radius: 0.625rem;
68
+ --background: oklch(1 0 0);
69
+ --foreground: oklch(0.145 0 0);
70
+ --card: oklch(1 0 0);
71
+ --card-foreground: oklch(0.145 0 0);
72
+ --popover: oklch(1 0 0);
73
+ --popover-foreground: oklch(0.145 0 0);
74
+ --primary: oklch(0.205 0 0);
75
+ --primary-foreground: oklch(0.985 0 0);
76
+ --secondary: oklch(0.97 0 0);
77
+ --secondary-foreground: oklch(0.205 0 0);
78
+ --muted: oklch(0.97 0 0);
79
+ --muted-foreground: oklch(0.556 0 0);
80
+ --accent: oklch(0.97 0 0);
81
+ --accent-foreground: oklch(0.205 0 0);
82
+ --destructive: oklch(0.577 0.245 27.325);
83
+ --destructive-foreground: oklch(0.985 0 0);
84
+ --border: oklch(0.922 0 0);
85
+ --input: oklch(0.922 0 0);
86
+ --ring: oklch(0.708 0 0);
87
+ --chart-1: oklch(0.646 0.222 41.116);
88
+ --chart-2: oklch(0.6 0.118 184.704);
89
+ --chart-3: oklch(0.398 0.07 227.392);
90
+ --chart-4: oklch(0.828 0.189 84.429);
91
+ --chart-5: oklch(0.769 0.188 70.08);
92
+ --sidebar: oklch(0.985 0 0);
93
+ --sidebar-foreground: oklch(0.145 0 0);
94
+ --sidebar-primary: oklch(0.205 0 0);
95
+ --sidebar-primary-foreground: oklch(0.985 0 0);
96
+ --sidebar-accent: oklch(0.97 0 0);
97
+ --sidebar-accent-foreground: oklch(0.205 0 0);
98
+ --sidebar-border: oklch(0.922 0 0);
99
+ --sidebar-ring: oklch(0.708 0 0);
100
+ }
101
+
102
+ /* Dark palette -- kept identical in .dark (explicit) and the media block
103
+ * (system default). Edit BOTH when changing a token. */
104
+ .dark {
105
+ --background: oklch(0.145 0 0);
106
+ --foreground: oklch(0.985 0 0);
107
+ --card: oklch(0.205 0 0);
108
+ --card-foreground: oklch(0.985 0 0);
109
+ --popover: oklch(0.205 0 0);
110
+ --popover-foreground: oklch(0.985 0 0);
111
+ --primary: oklch(0.922 0 0);
112
+ --primary-foreground: oklch(0.205 0 0);
113
+ --secondary: oklch(0.269 0 0);
114
+ --secondary-foreground: oklch(0.985 0 0);
115
+ --muted: oklch(0.269 0 0);
116
+ --muted-foreground: oklch(0.708 0 0);
117
+ --accent: oklch(0.269 0 0);
118
+ --accent-foreground: oklch(0.985 0 0);
119
+ --destructive: oklch(0.704 0.191 22.216);
120
+ --destructive-foreground: oklch(0.985 0 0);
121
+ --border: oklch(1 0 0 / 10%);
122
+ --input: oklch(1 0 0 / 15%);
123
+ --ring: oklch(0.556 0 0);
124
+ --chart-1: oklch(0.488 0.243 264.376);
125
+ --chart-2: oklch(0.696 0.17 162.48);
126
+ --chart-3: oklch(0.769 0.188 70.08);
127
+ --chart-4: oklch(0.627 0.265 303.9);
128
+ --chart-5: oklch(0.645 0.246 16.439);
129
+ --sidebar: oklch(0.205 0 0);
130
+ --sidebar-foreground: oklch(0.985 0 0);
131
+ --sidebar-primary: oklch(0.488 0.243 264.376);
132
+ --sidebar-primary-foreground: oklch(0.985 0 0);
133
+ --sidebar-accent: oklch(0.269 0 0);
134
+ --sidebar-accent-foreground: oklch(0.985 0 0);
135
+ --sidebar-border: oklch(1 0 0 / 10%);
136
+ --sidebar-ring: oklch(0.556 0 0);
137
+ }
138
+
139
+ @media (prefers-color-scheme: dark) {
140
+ :root {
141
+ --background: oklch(0.145 0 0);
142
+ --foreground: oklch(0.985 0 0);
143
+ --card: oklch(0.205 0 0);
144
+ --card-foreground: oklch(0.985 0 0);
145
+ --popover: oklch(0.205 0 0);
146
+ --popover-foreground: oklch(0.985 0 0);
147
+ --primary: oklch(0.922 0 0);
148
+ --primary-foreground: oklch(0.205 0 0);
149
+ --secondary: oklch(0.269 0 0);
150
+ --secondary-foreground: oklch(0.985 0 0);
151
+ --muted: oklch(0.269 0 0);
152
+ --muted-foreground: oklch(0.708 0 0);
153
+ --accent: oklch(0.269 0 0);
154
+ --accent-foreground: oklch(0.985 0 0);
155
+ --destructive: oklch(0.704 0.191 22.216);
156
+ --destructive-foreground: oklch(0.985 0 0);
157
+ --border: oklch(1 0 0 / 10%);
158
+ --input: oklch(1 0 0 / 15%);
159
+ --ring: oklch(0.556 0 0);
160
+ --chart-1: oklch(0.488 0.243 264.376);
161
+ --chart-2: oklch(0.696 0.17 162.48);
162
+ --chart-3: oklch(0.769 0.188 70.08);
163
+ --chart-4: oklch(0.627 0.265 303.9);
164
+ --chart-5: oklch(0.645 0.246 16.439);
165
+ --sidebar: oklch(0.205 0 0);
166
+ --sidebar-foreground: oklch(0.985 0 0);
167
+ --sidebar-primary: oklch(0.488 0.243 264.376);
168
+ --sidebar-primary-foreground: oklch(0.985 0 0);
169
+ --sidebar-accent: oklch(0.269 0 0);
170
+ --sidebar-accent-foreground: oklch(0.985 0 0);
171
+ --sidebar-border: oklch(1 0 0 / 10%);
172
+ --sidebar-ring: oklch(0.556 0 0);
173
+ }
174
+ }
175
+
176
+ @layer base {
177
+ * {
178
+ border-color: var(--color-border);
179
+ }
180
+ html,
181
+ body {
182
+ font-family: var(--font-sans);
183
+ }
184
+ body {
185
+ background-color: var(--color-background);
186
+ color: var(--color-foreground);
187
+ min-height: 100vh;
188
+ }
189
+ h1, h2, h3, h4 {
190
+ font-family: var(--font-display);
191
+ letter-spacing: -0.02em;
192
+ }
193
+ }
194
+
195
+ @layer utilities {
196
+ .prose-note {
197
+ color: var(--color-foreground);
198
+ }
199
+ .prose-note h1 { font-size: 1.875rem; font-weight: 700; margin: 1.25rem 0 0.75rem; font-family: var(--font-display); }
200
+ .prose-note h2 { font-size: 1.5rem; font-weight: 600; margin: 1rem 0 0.5rem; font-family: var(--font-display); }
201
+ .prose-note h3 { font-size: 1.25rem; font-weight: 600; margin: 0.875rem 0 0.5rem; }
202
+ .prose-note p { margin: 0.5rem 0; line-height: 1.7; }
203
+ .prose-note ul { list-style: disc; padding-left: 1.5rem; margin: 0.5rem 0; }
204
+ .prose-note ol { list-style: decimal; padding-left: 1.5rem; margin: 0.5rem 0; }
205
+ .prose-note li { margin: 0.25rem 0; }
206
+ .prose-note a { color: var(--color-primary); text-decoration: underline; }
207
+ .prose-note code { background: var(--color-muted); padding: 0.125rem 0.375rem; border-radius: 0.25rem; font-size: 0.875em; }
208
+ .prose-note pre { background: var(--color-muted); padding: 1rem; border-radius: 0.5rem; overflow-x: auto; margin: 0.75rem 0; }
209
+ .prose-note pre code { background: transparent; padding: 0; }
210
+ .prose-note blockquote { border-left: 3px solid var(--color-primary); padding-left: 1rem; margin: 0.75rem 0; color: var(--color-muted-foreground); }
211
+ .prose-note hr { border-color: var(--color-border); margin: 1.5rem 0; }
212
+ .prose-note table { border-collapse: collapse; margin: 0.75rem 0; }
213
+ .prose-note th, .prose-note td { border: 1px solid var(--color-border); padding: 0.375rem 0.75rem; }
214
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "include": ["**/*.ts", "**/*.tsx"],
3
+ "compilerOptions": {
4
+ "target": "ES2022",
5
+ "jsx": "react-jsx",
6
+ "module": "ESNext",
7
+ "paths": {
8
+ "#/*": ["./src/*"],
9
+ "@/*": ["./src/*"]
10
+ },
11
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
12
+ "types": ["@cloudflare/workers-types", "vite/client"],
13
+
14
+ /* Bundler mode */
15
+ "moduleResolution": "bundler",
16
+ "allowImportingTsExtensions": true,
17
+ "noEmit": true,
18
+
19
+ /* Linting */
20
+ "skipLibCheck": true,
21
+ "strict": true,
22
+ "noUnusedLocals": true,
23
+ "noUnusedParameters": true,
24
+ "noFallthroughCasesInSwitch": true,
25
+ "noUncheckedSideEffectImports": true
26
+ }
27
+ }
@@ -0,0 +1,17 @@
1
+ import { defineConfig } from '@helloleo/vite-config'
2
+
3
+ // @helloleo/vite-config owns the whole build/dev/sandbox pipeline: in dev it
4
+ // aliases `cloudflare:workers` -> @helloleo/runtime/cf-shim (libsql .dev.db for
5
+ // D1+KV, local disk for R2) and runs plain Vite SSR; at build it adds
6
+ // @cloudflare/vite-plugin to emit a workerd Worker bound to wrangler.jsonc. It
7
+ // also binds host:true/port:5173 itself (>=0.1.2).
8
+ //
9
+ // The one thing the wrapper doesn't set: HMR over the HelloLeo preview proxy,
10
+ // which terminates HTTPS on :443 — so point the HMR client at wss://…:443.
11
+ export default defineConfig({
12
+ vite: {
13
+ server: {
14
+ hmr: { clientPort: 443, protocol: 'wss' },
15
+ },
16
+ },
17
+ })
@@ -2,17 +2,18 @@ import { mkdir } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import type { Plugin } from "@opencode-ai/plugin";
4
4
  import { tool } from "@opencode-ai/plugin";
5
+ import { z } from "zod/v4";
5
6
  import { renderTemplate } from "../template-render.ts";
6
7
 
7
8
  /**
8
9
  * Identifier for the scaffold template a project was generated from. It is
9
10
  * also the directory name under src/templates/. Written to .leo/project.json
10
11
  * so the webapp / leo-core can tell which template (and therefore which
11
- * conventions: TanStack Router file routing, Tailwind v4, shadcn/ui) a
12
+ * conventions: TanStack Start file routing + SSR, Tailwind v4, shadcn/ui) a
12
13
  * workspace was bootstrapped with. Bump this (and add a new templates/<id>/
13
14
  * dir) when the scaffold's shape changes.
14
15
  */
15
- export const SCAFFOLD_VERSION = "tanstack-router-2026-06-15";
16
+ export const SCAFFOLD_VERSION = "tanstack-start-2026-06-18";
16
17
 
17
18
  /**
18
19
  * The project workspace directory is named after the project UUID -- the
@@ -25,25 +26,31 @@ export function projectUuidFromDir(dir: string): string {
25
26
  return path.basename(dir);
26
27
  }
27
28
 
28
- export interface BaseScaffoldOptions {
29
- name?: string;
30
- }
29
+ const scaffoldOptionsSchema = z.object({
30
+ name: z
31
+ .string()
32
+ .describe("Short product name. Used as the page title and package name."),
33
+ description: z
34
+ .string()
35
+ .describe("One sentence describing the app. Used as the meta description."),
36
+ });
31
37
 
32
38
  /**
33
- * Write the base scaffold by rendering the TanStack Router template into dir,
39
+ * Write the base scaffold by rendering the TanStack Start template into dir,
34
40
  * then adding .leo/project.json (template metadata the tool owns, not part of
35
- * the template tree). Shared between `scaffold` (frontend-only) and
36
- * `setup_netsuite` (base + OAuth files). Returns the relative paths written.
41
+ * the template tree). Returns the relative paths written.
37
42
  */
38
43
  export async function writeBaseScaffold(
39
- opts: BaseScaffoldOptions,
44
+ { name, description }: z.infer<typeof scaffoldOptionsSchema>,
40
45
  dir: string,
41
46
  ): Promise<string[]> {
42
- const name = opts.name ?? "leo-project";
43
-
44
47
  const created = await renderTemplate(
45
48
  SCAFFOLD_VERSION,
46
- { LEO_NAME: name, LEO_PROJECT_UUID: projectUuidFromDir(dir) },
49
+ {
50
+ LEO_NAME: name,
51
+ LEO_DESCRIPTION: description,
52
+ LEO_PROJECT_UUID: projectUuidFromDir(dir),
53
+ },
47
54
  dir,
48
55
  );
49
56
 
@@ -59,44 +66,35 @@ export async function writeBaseScaffold(
59
66
 
60
67
  export const scaffold = tool({
61
68
  description:
62
- "Scaffold a new React 19 + Vite + Tailwind CSS v4 + TanStack Router project " +
63
- "with shadcn/ui baked in. Renders the full template: TanStack file-based " +
64
- "routing (src/routes/), the complete shadcn/ui component set under " +
65
- "src/components/ui, src/lib/utils.ts (cn helper), src/lib/proxy.ts " +
66
- "(HelloLeo integration proxy), src/styles.css (shadcn design tokens, " +
67
- "light+dark), plus package.json, vite.config.ts, tsconfig.json, " +
68
- "tsr.config.json, components.json, .gitignore and the generated " +
69
- "src/routeTree.gen.ts. Call this FIRST for any new project before adding " +
70
- "routes and pages. For NetSuite projects (NetSuite MCP in the live list), " +
71
- "call `setup_netsuite` INSTEAD -- it bundles base scaffold + OAuth wiring " +
72
- "in one call. Do not call both.",
69
+ "Scaffold a new TanStack Start project with React 19, SSR, file-based " +
70
+ "routing, Tailwind v4, shadcn/ui, react-query, and a database, KV and " +
71
+ "bucket via @helloleo/runtime. Call this first for any new project, " +
72
+ "before writing routes or components.",
73
73
  args: {
74
- name: tool.schema
75
- .string()
76
- .optional()
77
- .describe("Project name for package.json (default: leo-project)"),
74
+ name: scaffoldOptionsSchema.shape.name,
75
+ description: scaffoldOptionsSchema.shape.description,
78
76
  },
79
77
  async execute(args, context) {
80
78
  const created = await writeBaseScaffold(args, context.directory);
79
+ const uiComponents = created.filter((f) =>
80
+ f.startsWith("src/components/ui/"),
81
+ );
82
+ const rest = created.filter((f) => !f.startsWith("src/components/ui/"));
81
83
  return [
82
84
  `Scaffolded ${created.length} files:`,
83
- ...created.map((f) => ` - ${f}`),
85
+ ...rest.map((f) => ` - ${f}`),
86
+ ` - src/components/ui/ (${uiComponents.length} shadcn/ui components)`,
87
+ "",
88
+ "Notes:",
89
+ "- This is a TanStack Start project. The server entry is src/server.ts. There is no index.html or client entrypoint.",
90
+ "- Add routes as files in src/routes/. Do not default export route components.",
91
+ "- shadcn/ui components are already in src/components/ui.",
92
+ "- Design system: edit the tokens in src/styles.css (light and dark blocks).",
93
+ "- The database schema lives in src/db/schema.ts. Migrations run automatically.",
94
+ "- Always fetch data with react-query (useQuery and useMutation).",
84
95
  "",
85
- "Next: add routes as files under src/routes/ (TanStack file-based",
86
- " routing). Edit src/routes/index.tsx for the home page.",
87
- "UI: shadcn/ui components are already in src/components/ui -- import and",
88
- " use them; do not attempt to re-add via the shadcn CLI.",
89
- "Design system: edit tokens in src/styles.css (light + dark blocks).",
90
- "Data fetching: React Query is wired (QueryClient on the router context +",
91
- " QueryClientProvider). ALWAYS fetch with @tanstack/react-query --",
92
- " useQuery / useMutation in components, or a route loader calling",
93
- " context.queryClient.ensureQueryData(). Do NOT hand-roll fetches with",
94
- " useEffect + useState. Wrap the fetch call (usually callIntegration())",
95
- " in the queryFn.",
96
- "Integration calls: use callIntegration() from src/lib/proxy.ts -- as the",
97
- " queryFn body, never on its own in useEffect. Its header comment",
98
- " documents slugs, response unwrapping, pagination, and frontend-only vs",
99
- " HelloLeo Cloud routing.",
96
+ "Next: read src/routes/index.tsx and continue with the user's request.",
97
+ "For NetSuite projects, call setup_netsuite.",
100
98
  ].join("\n");
101
99
  },
102
100
  });
@@ -2,21 +2,17 @@ import { mkdir } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import type { Plugin } from "@opencode-ai/plugin";
4
4
  import { tool } from "@opencode-ai/plugin";
5
- import { writeBaseScaffold } from "./scaffold.ts";
6
5
 
7
6
  /**
8
- * One-shot bootstrap for a NetSuite project. Writes the base HelloLeo
9
- * scaffold (via writeBaseScaffold) plus ADDITIVE NetSuite OAuth wiring:
10
- * the netsuite OAuth client, a NetSuite auth context (auth.tsx), and the
11
- * AuthLayout / LoginPage / SignInWithNetSuite components. It never
12
- * overwrites a scaffold file -- the one manual step is wrapping the root
13
- * route's <Outlet /> with <AuthLayout> (the tool's output shows the
14
- * snippet). All NetSuite-specific templates live in this file -- one place
15
- * to read or change anything NetSuite-related.
7
+ * Adds NetSuite OAuth wiring on top of an existing scaffold: the netsuite
8
+ * OAuth client, a NetSuite auth context (auth.tsx), and the AuthLayout /
9
+ * LoginPage / SignInWithNetSuite components. It never overwrites a
10
+ * scaffold file -- the one manual step is wrapping the root route's
11
+ * <Outlet /> with <AuthLayout> (the tool's output shows the snippet).
12
+ * All NetSuite-specific templates live in this file -- one place to read
13
+ * or change anything NetSuite-related.
16
14
  *
17
- * Use INSTEAD of `scaffold` when the project needs NetSuite. The tool
18
- * name carries the intent so the LLM can't accidentally ship a project
19
- * without the auth files.
15
+ * Called AFTER `scaffold`, never instead of it.
20
16
  */
21
17
 
22
18
  function netsuiteTs(): string {
@@ -86,20 +82,17 @@ export function signInWithNetSuite(returnTo: string = window.location.href) {
86
82
  // refuses to render -- looks like "nothing happens" to the user. Navigate
87
83
  // the top frame instead so the OAuth flow runs at the top level and the
88
84
  // callback redirect lands on the same top frame, which then reads the
89
- // #nssid= fragment. Use \`location.href =\` (NOT \`location.assign()\`):
90
- // reading the \`.assign\` method off a cross-origin \`window.top.location\`
91
- // throws a SecurityError, which would drop us into the same-frame fallback
92
- // and load NetSuite inside the iframe (X-Frame-Options: DENY -> "refused to
93
- // connect"). Assigning \`.href\` is a navigation, allowed cross-origin.
85
+ // #nssid= fragment. Fallback to same-frame navigation if the top window
86
+ // is cross-origin and can't be reassigned.
94
87
  try {
95
88
  if (window.top && window.top !== window) {
96
- window.top.location.href = url.toString();
89
+ window.top.location.assign(url.toString());
97
90
  return;
98
91
  }
99
92
  } catch {
100
93
  /* top window not accessible — fall through to same-frame navigation */
101
94
  }
102
- window.location.href = url.toString();
95
+ window.location.assign(url.toString());
103
96
  }
104
97
 
105
98
  export interface NetSuiteSession {
@@ -345,21 +338,13 @@ function Gate({ children }: { children: ReactNode }) {
345
338
 
346
339
  export const setupNetsuite = tool({
347
340
  description:
348
- "Bootstrap a NetSuite-backed React + Vite + Tailwind v4 + TanStack Router " +
349
- "project in one call. Writes the full HelloLeo base scaffold PLUS the " +
350
- "NetSuite OAuth wiring, all ADDITIVE -- it never overwrites a scaffold " +
351
- "file. Adds src/lib/netsuite.ts (OAuth client), src/lib/auth.tsx " +
352
- "(NetSuiteAuthProvider + useNetSuiteAuth), and " +
353
- "src/components/auth/{AuthLayout,LoginPage,SignInWithNetSuite}.tsx. " +
354
- "Use this INSTEAD of `scaffold` for any project that needs NetSuite. " +
355
- "After it runs there is exactly ONE manual step: wrap the root route's " +
356
- "<Outlet /> with <AuthLayout> in src/routes/__root.tsx (the tool's output " +
357
- "shows the snippet). Real OAuth is wired from day one -- never replace " +
358
- "<AuthLayout> with a password placeholder.",
341
+ "Add NetSuite OAuth wiring to an existing project. Call scaffold first, " +
342
+ "then this. Writes the OAuth client, auth provider and login components. " +
343
+ "All files are additive, nothing is overwritten. Real OAuth is wired " +
344
+ "from day one, never replace <AuthLayout> with a password placeholder.",
359
345
  args: {},
360
346
  async execute(_args, context) {
361
347
  const dir = context.directory;
362
- const base = await writeBaseScaffold({}, dir);
363
348
 
364
349
  await mkdir(path.join(dir, "src", "components", "auth"), {
365
350
  recursive: true,
@@ -387,45 +372,27 @@ export const setupNetsuite = tool({
387
372
  await Bun.write(path.join(dir, filePath), content);
388
373
  }
389
374
 
390
- const all = [...base, ...nsFiles.map(([f]) => f)];
391
375
  return [
392
- `Generated ${all.length} files (base scaffold + NetSuite OAuth, all additive -- nothing overwritten).`,
376
+ `Wrote ${nsFiles.length} NetSuite OAuth files (all additive, nothing overwritten):`,
377
+ " - src/lib/netsuite.ts (OAuth client: signIn, session, workerFetch)",
378
+ " - src/lib/auth.tsx (NetSuiteAuthProvider and useNetSuiteAuth)",
379
+ " - src/components/auth/AuthLayout.tsx (provider and auth gate)",
380
+ " - src/components/auth/LoginPage.tsx (unauthenticated view)",
381
+ " - src/components/auth/SignInWithNetSuite.tsx (sign-in button)",
393
382
  "",
394
- "All you need to do is wrap the root layout with the auth layout.",
395
- "In src/routes/__root.tsx, import AuthLayout and wrap <Outlet />:",
383
+ "Notes:",
384
+ "- AuthLayout shows LoginPage to signed-out users and renders the app's routes once they sign in with NetSuite.",
385
+ "- Read the session anywhere with useNetSuiteAuth() from src/lib/auth.tsx.",
386
+ "- Never replace AuthLayout with a password gate. Real OAuth is the only supported login.",
387
+ "- The auth worker (deployed via the cloudflare-backend MCP) serves /auth/netsuite/start, /auth/netsuite/callback, /api/me and /auth/netsuite/logout. It reads env.NETSUITE_ACCOUNT_ID, _CLIENT_ID and _CLIENT_SECRET. VITE_WORKER_URL must be set (HelloLeo Cloud seeds it).",
396
388
  "",
397
- " import { AuthLayout } from '../components/auth/AuthLayout'",
398
- "",
399
- " function RootComponent() {",
400
- " return (",
401
- " <>",
402
- " <AuthLayout>",
403
- " <Outlet />",
404
- " </AuthLayout>",
405
- " {/* keep the existing <TanStackDevtools ... /> here */}",
406
- " </>",
407
- " )",
408
- " }",
389
+ "Next: in src/routes/__root.tsx, import AuthLayout and wrap <Outlet /> with it. Keep any existing providers.",
409
390
  "",
410
- "That single wrap gates every route: <LoginPage /> until the user signs",
411
- "in with NetSuite, your routes after. Read the session anywhere below it",
412
- "with useNetSuiteAuth() from src/lib/auth.tsx. NEVER swap <AuthLayout>",
413
- "for a password gate -- real OAuth is the only supported login.",
414
- "",
415
- "NetSuite OAuth files written:",
416
- " - src/lib/netsuite.ts OAuth client (signIn / session / workerFetch)",
417
- " - src/lib/auth.tsx NetSuiteAuthProvider + useNetSuiteAuth",
418
- " - src/components/auth/AuthLayout.tsx provider + gate (wrap root with this)",
419
- " - src/components/auth/LoginPage.tsx unauthenticated view",
420
- " - src/components/auth/SignInWithNetSuite.tsx sign-in button",
391
+ " import { AuthLayout } from '../components/auth/AuthLayout'",
421
392
  "",
422
- "Worker (deploy via cloudflare-backend MCP) must serve:",
423
- " GET /auth/netsuite/start -- redirect to NetSuite OAuth",
424
- " GET /auth/netsuite/callback -- code -> token exchange",
425
- " GET /api/me -- current session",
426
- " POST /auth/netsuite/logout -- clear session",
427
- " Reads env.NETSUITE_ACCOUNT_ID / _CLIENT_ID / _CLIENT_SECRET.",
428
- " VITE_WORKER_URL must be set (HelloLeo Cloud provisioner seeds it).",
393
+ " <AuthLayout>",
394
+ " <Outlet />",
395
+ " </AuthLayout>",
429
396
  ].join("\n");
430
397
  },
431
398
  });