@helloleo/plugins 0.1.14 → 0.1.16
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.
- package/dist/index.js +197 -469
- package/dist/templates/tanstack-router-2026-06-15/components.json +22 -0
- package/dist/templates/tanstack-router-2026-06-15/index.html +13 -0
- package/dist/templates/tanstack-router-2026-06-15/package.json +94 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/accordion.tsx +51 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/alert-dialog.tsx +115 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/alert.tsx +49 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/aspect-ratio.tsx +5 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/avatar.tsx +47 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/badge.tsx +32 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/breadcrumb.tsx +101 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/button.tsx +49 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/calendar.tsx +177 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/card.tsx +55 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/carousel.tsx +240 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/chart.tsx +331 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/checkbox.tsx +26 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/collapsible.tsx +11 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/command.tsx +143 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/context-menu.tsx +187 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/dialog.tsx +104 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/drawer.tsx +98 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/dropdown-menu.tsx +188 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/form.tsx +171 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/hover-card.tsx +27 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/input-otp.tsx +69 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/input.tsx +22 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/label.tsx +21 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/menubar.tsx +229 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/navigation-menu.tsx +120 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/pagination.tsx +98 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/popover.tsx +31 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/progress.tsx +25 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/radio-group.tsx +36 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/resizable.tsx +37 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/scroll-area.tsx +44 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/select.tsx +152 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/separator.tsx +24 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/sheet.tsx +122 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/sidebar.tsx +744 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/skeleton.tsx +7 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/slider.tsx +23 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/sonner.tsx +23 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/switch.tsx +27 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/table.tsx +94 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/tabs.tsx +53 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/textarea.tsx +21 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/toggle-group.tsx +57 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/toggle.tsx +42 -0
- package/dist/templates/tanstack-router-2026-06-15/src/components/ui/tooltip.tsx +32 -0
- package/dist/templates/tanstack-router-2026-06-15/src/hooks/use-mobile.tsx +19 -0
- package/dist/templates/tanstack-router-2026-06-15/src/lib/proxy.ts +83 -0
- package/dist/templates/tanstack-router-2026-06-15/src/lib/utils.ts +6 -0
- package/dist/templates/tanstack-router-2026-06-15/src/main.tsx +33 -0
- package/dist/templates/tanstack-router-2026-06-15/src/routeTree.gen.ts +59 -0
- package/dist/templates/tanstack-router-2026-06-15/src/router.tsx +23 -0
- package/dist/templates/tanstack-router-2026-06-15/src/routes/__root.tsx +16 -0
- package/dist/templates/tanstack-router-2026-06-15/src/routes/index.tsx +14 -0
- package/dist/templates/tanstack-router-2026-06-15/src/styles.css +214 -0
- package/dist/templates/tanstack-router-2026-06-15/tsconfig.json +27 -0
- package/dist/templates/tanstack-router-2026-06-15/vite.config.ts +24 -0
- package/package.json +3 -2
- package/src/template-render.ts +48 -0
- package/src/templates/tanstack-router-2026-06-15/components.json +22 -0
- package/src/templates/tanstack-router-2026-06-15/index.html +13 -0
- package/src/templates/tanstack-router-2026-06-15/package.json +94 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/accordion.tsx +51 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/alert-dialog.tsx +115 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/alert.tsx +49 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/aspect-ratio.tsx +5 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/avatar.tsx +47 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/badge.tsx +32 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/breadcrumb.tsx +101 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/button.tsx +49 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/calendar.tsx +177 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/card.tsx +55 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/carousel.tsx +240 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/chart.tsx +331 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/checkbox.tsx +26 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/collapsible.tsx +11 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/command.tsx +143 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/context-menu.tsx +187 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/dialog.tsx +104 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/drawer.tsx +98 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/dropdown-menu.tsx +188 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/form.tsx +171 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/hover-card.tsx +27 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/input-otp.tsx +69 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/input.tsx +22 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/label.tsx +21 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/menubar.tsx +229 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/navigation-menu.tsx +120 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/pagination.tsx +98 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/popover.tsx +31 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/progress.tsx +25 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/radio-group.tsx +36 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/resizable.tsx +37 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/scroll-area.tsx +44 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/select.tsx +152 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/separator.tsx +24 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/sheet.tsx +122 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/sidebar.tsx +744 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/skeleton.tsx +7 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/slider.tsx +23 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/sonner.tsx +23 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/switch.tsx +27 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/table.tsx +94 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/tabs.tsx +53 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/textarea.tsx +21 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/toggle-group.tsx +57 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/toggle.tsx +42 -0
- package/src/templates/tanstack-router-2026-06-15/src/components/ui/tooltip.tsx +32 -0
- package/src/templates/tanstack-router-2026-06-15/src/hooks/use-mobile.tsx +19 -0
- package/src/templates/tanstack-router-2026-06-15/src/lib/proxy.ts +83 -0
- package/src/templates/tanstack-router-2026-06-15/src/lib/utils.ts +6 -0
- package/src/templates/tanstack-router-2026-06-15/src/main.tsx +33 -0
- package/src/templates/tanstack-router-2026-06-15/src/routeTree.gen.ts +59 -0
- package/src/templates/tanstack-router-2026-06-15/src/router.tsx +23 -0
- package/src/templates/tanstack-router-2026-06-15/src/routes/__root.tsx +16 -0
- package/src/templates/tanstack-router-2026-06-15/src/routes/index.tsx +14 -0
- package/src/templates/tanstack-router-2026-06-15/src/styles.css +214 -0
- package/src/templates/tanstack-router-2026-06-15/tsconfig.json +27 -0
- package/src/templates/tanstack-router-2026-06-15/vite.config.ts +24 -0
- package/src/tools/scaffold.ts +51 -444
- package/src/tools/setup-netsuite.ts +161 -80
package/src/tools/scaffold.ts
CHANGED
|
@@ -2,245 +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 { renderTemplate } from "../template-render.ts";
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
scripts: {
|
|
14
|
-
dev: "vite",
|
|
15
|
-
start: "vite",
|
|
16
|
-
build: "vite build",
|
|
17
|
-
preview: "vite preview",
|
|
18
|
-
},
|
|
19
|
-
dependencies: {
|
|
20
|
-
react: "^18.3.1",
|
|
21
|
-
"react-dom": "^18.3.1",
|
|
22
|
-
clsx: "^2.1.1",
|
|
23
|
-
"tailwind-merge": "^2.6.0",
|
|
24
|
-
"lucide-react": "^1.14.0",
|
|
25
|
-
},
|
|
26
|
-
devDependencies: {
|
|
27
|
-
"@types/react": "^18.3.12",
|
|
28
|
-
"@types/react-dom": "^18.3.1",
|
|
29
|
-
"@vitejs/plugin-react": "^4.3.4",
|
|
30
|
-
autoprefixer: "^10.4.20",
|
|
31
|
-
postcss: "^8.4.49",
|
|
32
|
-
tailwindcss: "^3.4.17",
|
|
33
|
-
typescript: "^5.6.3",
|
|
34
|
-
vite: "^6.0.5",
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
null,
|
|
38
|
-
2,
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function indexHtml(fontFamily: string) {
|
|
43
|
-
const fontParam = fontFamily.replace(/\s+/g, "+");
|
|
44
|
-
return `<!DOCTYPE html>
|
|
45
|
-
<html lang="en">
|
|
46
|
-
<head>
|
|
47
|
-
<meta charset="UTF-8" />
|
|
48
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
49
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
50
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
51
|
-
<link href="https://fonts.googleapis.com/css2?family=${fontParam}:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
52
|
-
<title>App</title>
|
|
53
|
-
</head>
|
|
54
|
-
<body>
|
|
55
|
-
<div id="root"></div>
|
|
56
|
-
<script type="module" src="/src/main.tsx"></script>
|
|
57
|
-
</body>
|
|
58
|
-
</html>`;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function viteConfig() {
|
|
62
|
-
return `import { defineConfig } from 'vite'
|
|
63
|
-
import react from '@vitejs/plugin-react'
|
|
64
|
-
import path from 'path'
|
|
65
|
-
|
|
66
|
-
export default defineConfig({
|
|
67
|
-
plugins: [react()],
|
|
68
|
-
server: {
|
|
69
|
-
allowedHosts: true,
|
|
70
|
-
},
|
|
71
|
-
resolve: {
|
|
72
|
-
alias: {
|
|
73
|
-
'@': path.resolve(__dirname, './src'),
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
})`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function tailwindConfig() {
|
|
80
|
-
return `export default {
|
|
81
|
-
darkMode: ["class"],
|
|
82
|
-
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
|
83
|
-
theme: {
|
|
84
|
-
extend: {
|
|
85
|
-
colors: {
|
|
86
|
-
background: "hsl(var(--background))",
|
|
87
|
-
foreground: "hsl(var(--foreground))",
|
|
88
|
-
card: { DEFAULT: "hsl(var(--card))", foreground: "hsl(var(--card-foreground))" },
|
|
89
|
-
primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))" },
|
|
90
|
-
secondary: { DEFAULT: "hsl(var(--secondary))", foreground: "hsl(var(--secondary-foreground))" },
|
|
91
|
-
muted: { DEFAULT: "hsl(var(--muted))", foreground: "hsl(var(--muted-foreground))" },
|
|
92
|
-
accent: { DEFAULT: "hsl(var(--accent))", foreground: "hsl(var(--accent-foreground))" },
|
|
93
|
-
destructive: { DEFAULT: "hsl(var(--destructive))", foreground: "hsl(var(--destructive-foreground))" },
|
|
94
|
-
success: { DEFAULT: "hsl(var(--success))", foreground: "hsl(var(--success-foreground))" },
|
|
95
|
-
warning: { DEFAULT: "hsl(var(--warning))", foreground: "hsl(var(--warning-foreground))" },
|
|
96
|
-
border: "hsl(var(--border))",
|
|
97
|
-
input: "hsl(var(--input))",
|
|
98
|
-
ring: "hsl(var(--ring))",
|
|
99
|
-
},
|
|
100
|
-
fontFamily: {
|
|
101
|
-
sans: "var(--font-sans)",
|
|
102
|
-
heading: "var(--font-heading)",
|
|
103
|
-
},
|
|
104
|
-
borderRadius: {
|
|
105
|
-
lg: "var(--radius)",
|
|
106
|
-
md: "calc(var(--radius) - 2px)",
|
|
107
|
-
sm: "calc(var(--radius) - 4px)",
|
|
108
|
-
},
|
|
109
|
-
borderWidth: {
|
|
110
|
-
DEFAULT: "var(--border-width)",
|
|
111
|
-
},
|
|
112
|
-
boxShadow: {
|
|
113
|
-
DEFAULT: "var(--shadow)",
|
|
114
|
-
lg: "var(--shadow-lg)",
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
plugins: [],
|
|
119
|
-
}`;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function postcssConfig() {
|
|
123
|
-
return `export default {
|
|
124
|
-
plugins: {
|
|
125
|
-
tailwindcss: {},
|
|
126
|
-
autoprefixer: {},
|
|
127
|
-
},
|
|
128
|
-
}`;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function indexCss(
|
|
132
|
-
primaryColor: string,
|
|
133
|
-
fontFamily: string,
|
|
134
|
-
borderRadius: string,
|
|
135
|
-
) {
|
|
136
|
-
const fontValue = `'${fontFamily}', ui-sans-serif, system-ui, sans-serif`;
|
|
137
|
-
return `/*
|
|
138
|
-
* HelloLeo Design System -- READ BEFORE STYLING.
|
|
139
|
-
*
|
|
140
|
-
* The CSS variables below are the single source of truth for the project's
|
|
141
|
-
* design tokens. They power the no-code Design panel in the HelloLeo webapp:
|
|
142
|
-
* the user edits colors / fonts / radius / shadows visually, and the panel
|
|
143
|
-
* writes back to THIS file. Keeping the conventions intact is what unlocks
|
|
144
|
-
* that round-trip.
|
|
145
|
-
*
|
|
146
|
-
* Conventions (do not break):
|
|
147
|
-
* - All colors stored as raw HSL triplets (no hsl() wrapper, no #hex).
|
|
148
|
-
* Tailwind classes wrap them via hsl(var(--token)). Never inline #hex
|
|
149
|
-
* in components -- always use design tokens (bg-primary, text-foreground,
|
|
150
|
-
* border-border, etc.).
|
|
151
|
-
* - --primary / --secondary / --accent / --destructive / --success /
|
|
152
|
-
* --warning each have a paired -foreground for text on that surface.
|
|
153
|
-
* - --radius drives lg/md/sm via calc() in tailwind.config.js. Edit here,
|
|
154
|
-
* not in component classes.
|
|
155
|
-
* - --font-sans for body, --font-heading for headings. Both load from the
|
|
156
|
-
* Google Fonts <link> in index.html.
|
|
157
|
-
* - .dark variants mirror :root one-for-one. When adding a token, add it
|
|
158
|
-
* to BOTH blocks or dark mode breaks.
|
|
159
|
-
*
|
|
160
|
-
* UI primitives (Button, Card, Input, Modal, Table, Badge, Alert, Spinner)
|
|
161
|
-
* MUST live in src/components/ui/ and consume these tokens via Tailwind
|
|
162
|
-
* classes -- never hardcoded colors. One responsibility per file, <50 lines
|
|
163
|
-
* per component.
|
|
7
|
+
/**
|
|
8
|
+
* Identifier for the scaffold template a project was generated from. It is
|
|
9
|
+
* also the directory name under src/templates/. Written to .leo/project.json
|
|
10
|
+
* so the webapp / leo-core can tell which template (and therefore which
|
|
11
|
+
* conventions: TanStack Router file routing, Tailwind v4, shadcn/ui) a
|
|
12
|
+
* workspace was bootstrapped with. Bump this (and add a new templates/<id>/
|
|
13
|
+
* dir) when the scaffold's shape changes.
|
|
164
14
|
*/
|
|
165
|
-
|
|
166
|
-
@tailwind base;
|
|
167
|
-
@tailwind components;
|
|
168
|
-
@tailwind utilities;
|
|
169
|
-
|
|
170
|
-
@layer base {
|
|
171
|
-
:root {
|
|
172
|
-
--background: 0 0% 100%;
|
|
173
|
-
--foreground: 222.2 84% 4.9%;
|
|
174
|
-
--card: 0 0% 100%;
|
|
175
|
-
--card-foreground: 222.2 84% 4.9%;
|
|
176
|
-
--primary: ${primaryColor};
|
|
177
|
-
--primary-foreground: 210 40% 98%;
|
|
178
|
-
--secondary: 210 40% 96.1%;
|
|
179
|
-
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
180
|
-
--muted: 210 40% 96.1%;
|
|
181
|
-
--muted-foreground: 215.4 16.3% 46.9%;
|
|
182
|
-
--accent: 210 40% 96.1%;
|
|
183
|
-
--accent-foreground: 222.2 47.4% 11.2%;
|
|
184
|
-
--destructive: 0 84.2% 60.2%;
|
|
185
|
-
--destructive-foreground: 210 40% 98%;
|
|
186
|
-
--success: 142 76% 36%;
|
|
187
|
-
--success-foreground: 210 40% 98%;
|
|
188
|
-
--warning: 38 92% 50%;
|
|
189
|
-
--warning-foreground: 222.2 47.4% 11.2%;
|
|
190
|
-
--border: 214.3 31.8% 91.4%;
|
|
191
|
-
--input: 214.3 31.8% 91.4%;
|
|
192
|
-
--ring: 221.2 83.2% 53.3%;
|
|
193
|
-
--radius: ${borderRadius};
|
|
194
|
-
--border-width: 1px;
|
|
195
|
-
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
196
|
-
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
197
|
-
--font-sans: ${fontValue};
|
|
198
|
-
--font-heading: ${fontValue};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
.dark {
|
|
202
|
-
--background: 222.2 84% 4.9%;
|
|
203
|
-
--foreground: 210 40% 98%;
|
|
204
|
-
--card: 222.2 84% 4.9%;
|
|
205
|
-
--card-foreground: 210 40% 98%;
|
|
206
|
-
--primary: 217.2 91.2% 59.8%;
|
|
207
|
-
--primary-foreground: 222.2 47.4% 11.2%;
|
|
208
|
-
--secondary: 217.2 32.6% 17.5%;
|
|
209
|
-
--secondary-foreground: 210 40% 98%;
|
|
210
|
-
--muted: 217.2 32.6% 17.5%;
|
|
211
|
-
--muted-foreground: 215 20.2% 65.1%;
|
|
212
|
-
--accent: 217.2 32.6% 17.5%;
|
|
213
|
-
--accent-foreground: 210 40% 98%;
|
|
214
|
-
--destructive: 0 62.8% 30.6%;
|
|
215
|
-
--destructive-foreground: 210 40% 98%;
|
|
216
|
-
--success: 142 76% 36%;
|
|
217
|
-
--success-foreground: 210 40% 98%;
|
|
218
|
-
--warning: 38 92% 50%;
|
|
219
|
-
--warning-foreground: 222.2 47.4% 11.2%;
|
|
220
|
-
--border: 217.2 32.6% 17.5%;
|
|
221
|
-
--input: 217.2 32.6% 17.5%;
|
|
222
|
-
--ring: 224.3 76.3% 48%;
|
|
223
|
-
--border-width: 1px;
|
|
224
|
-
--shadow: 0 1px 3px 0 rgb(0 0 0 / 0.3), 0 1px 2px -1px rgb(0 0 0 / 0.3);
|
|
225
|
-
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.3), 0 4px 6px -4px rgb(0 0 0 / 0.3);
|
|
226
|
-
--font-sans: ${fontValue};
|
|
227
|
-
--font-heading: ${fontValue};
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
body {
|
|
232
|
-
@apply bg-background text-foreground font-sans;
|
|
233
|
-
}`;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function utilsTs() {
|
|
237
|
-
return `import { clsx, type ClassValue } from 'clsx'
|
|
238
|
-
import { twMerge } from 'tailwind-merge'
|
|
239
|
-
|
|
240
|
-
export function cn(...inputs: ClassValue[]) {
|
|
241
|
-
return twMerge(clsx(inputs))
|
|
242
|
-
}`;
|
|
243
|
-
}
|
|
15
|
+
export const SCAFFOLD_VERSION = "tanstack-router-2026-06-15";
|
|
244
16
|
|
|
245
17
|
/**
|
|
246
18
|
* The project workspace directory is named after the project UUID -- the
|
|
@@ -253,229 +25,56 @@ export function projectUuidFromDir(dir: string): string {
|
|
|
253
25
|
return path.basename(dir);
|
|
254
26
|
}
|
|
255
27
|
|
|
256
|
-
function proxyTs(projectUuid: string) {
|
|
257
|
-
return `/*
|
|
258
|
-
* HelloLeo Integration Proxy -- runtime helper for connected integrations.
|
|
259
|
-
*
|
|
260
|
-
* All third-party APIs (Odoo, Airtable, HubSpot, Stripe, Notion, Gladia,
|
|
261
|
-
* Microsoft 365, Klaviyo, Supabase, DimoMaint, etc.) are reachable through
|
|
262
|
-
* this single proxy. The proxy handles auth and CORS using credentials the
|
|
263
|
-
* user already configured in Project Settings -- you NEVER pass an API key
|
|
264
|
-
* from frontend code, and you NEVER call third-party hosts directly.
|
|
265
|
-
*
|
|
266
|
-
* Two modes:
|
|
267
|
-
* A) Frontend-only project -> import callIntegration() from this file.
|
|
268
|
-
* B) HelloLeo Cloud project -> route via Worker fetch(env.INTEGRATION_PROXY).
|
|
269
|
-
* Use Cloud whenever the project needs backend logic, automations,
|
|
270
|
-
* webhooks, scheduled jobs, or AI agents. Keep secrets server-side.
|
|
271
|
-
*
|
|
272
|
-
* Integration slugs:
|
|
273
|
-
* Use the exact slug from the Enabled MCP list with the "pipedream-"
|
|
274
|
-
* prefix removed. e.g. "pipedream-gorgias_oauth" -> "gorgias_oauth".
|
|
275
|
-
* Native integrations (odoo, airtable) keep their plain name.
|
|
276
|
-
*
|
|
277
|
-
* Response shape (always wrapped):
|
|
278
|
-
* { success: boolean, status: number, data: <provider payload> }
|
|
279
|
-
*
|
|
280
|
-
* Unwrap "data" by provider:
|
|
281
|
-
* odoo -> data.result
|
|
282
|
-
* airtable -> data.records
|
|
283
|
-
* notion -> data.results
|
|
284
|
-
* microsoft 365 -> data.value
|
|
285
|
-
* klaviyo -> data.data
|
|
286
|
-
* supabase / dimomaint -> data
|
|
287
|
-
*
|
|
288
|
-
* Pagination: put query params directly in the endpoint string,
|
|
289
|
-
* e.g. endpoint: '/api/tickets?cursor=xyz&per_page=100'.
|
|
290
|
-
* Read pagination metadata from the response to build the next call.
|
|
291
|
-
* Never .filter() large lists in the frontend -- paginate server-side.
|
|
292
|
-
*
|
|
293
|
-
* Secrets for non-integration APIs (e.g. raw OpenAI): store them in
|
|
294
|
-
* HelloLeo Cloud -> Settings -> Cloud -> Secrets, and read via env.SECRET_NAME
|
|
295
|
-
* from a Worker. Never embed secrets in frontend code.
|
|
296
|
-
*/
|
|
297
|
-
|
|
298
|
-
export const INTEGRATION_PROXY_URL =
|
|
299
|
-
'https://api.app.helloleo.dev/api/integration-proxy/${projectUuid}';
|
|
300
|
-
|
|
301
|
-
export interface ProxyRequest {
|
|
302
|
-
integration: string;
|
|
303
|
-
endpoint: string;
|
|
304
|
-
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
305
|
-
body?: unknown;
|
|
306
|
-
headers?: Record<string, string>;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
export interface ProxyResponse<T = unknown> {
|
|
310
|
-
success: boolean;
|
|
311
|
-
status: number;
|
|
312
|
-
data: T;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
export async function callIntegration<T = unknown>(
|
|
316
|
-
req: ProxyRequest,
|
|
317
|
-
): Promise<ProxyResponse<T>> {
|
|
318
|
-
const res = await fetch(INTEGRATION_PROXY_URL, {
|
|
319
|
-
method: 'POST',
|
|
320
|
-
headers: { 'Content-Type': 'application/json' },
|
|
321
|
-
body: JSON.stringify({ method: 'POST', ...req }),
|
|
322
|
-
});
|
|
323
|
-
return res.json();
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/* Example -- Odoo search_read:
|
|
327
|
-
*
|
|
328
|
-
* const r = await callIntegration({
|
|
329
|
-
* integration: 'odoo',
|
|
330
|
-
* endpoint: '/jsonrpc',
|
|
331
|
-
* body: {
|
|
332
|
-
* model: 'product.template',
|
|
333
|
-
* method: 'search_read',
|
|
334
|
-
* args: [[['type', '=', 'consu']]],
|
|
335
|
-
* kwargs: { fields: ['name', 'list_price'], limit: 50 },
|
|
336
|
-
* },
|
|
337
|
-
* });
|
|
338
|
-
* const products = r.data?.result ?? [];
|
|
339
|
-
*/
|
|
340
|
-
`;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function hexToHslTriplet(hex: string): string {
|
|
344
|
-
let h = hex.trim().replace(/^#/, "");
|
|
345
|
-
if (h.length === 3) {
|
|
346
|
-
h = h
|
|
347
|
-
.split("")
|
|
348
|
-
.map((c) => c + c)
|
|
349
|
-
.join("");
|
|
350
|
-
}
|
|
351
|
-
if (!/^[0-9a-fA-F]{6}$/.test(h)) return "";
|
|
352
|
-
const r = Number.parseInt(h.slice(0, 2), 16) / 255;
|
|
353
|
-
const g = Number.parseInt(h.slice(2, 4), 16) / 255;
|
|
354
|
-
const b = Number.parseInt(h.slice(4, 6), 16) / 255;
|
|
355
|
-
const max = Math.max(r, g, b);
|
|
356
|
-
const min = Math.min(r, g, b);
|
|
357
|
-
const l = (max + min) / 2;
|
|
358
|
-
let s = 0;
|
|
359
|
-
let hue = 0;
|
|
360
|
-
if (max !== min) {
|
|
361
|
-
const d = max - min;
|
|
362
|
-
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
363
|
-
switch (max) {
|
|
364
|
-
case r:
|
|
365
|
-
hue = (g - b) / d + (g < b ? 6 : 0);
|
|
366
|
-
break;
|
|
367
|
-
case g:
|
|
368
|
-
hue = (b - r) / d + 2;
|
|
369
|
-
break;
|
|
370
|
-
default:
|
|
371
|
-
hue = (r - g) / d + 4;
|
|
372
|
-
}
|
|
373
|
-
hue *= 60;
|
|
374
|
-
}
|
|
375
|
-
return `${Math.round(hue)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
function normalizePrimaryColor(value: string): string {
|
|
379
|
-
const trimmed = value.trim();
|
|
380
|
-
if (/^(#|[0-9a-fA-F]{3}$|[0-9a-fA-F]{6}$)/.test(trimmed)) {
|
|
381
|
-
const hsl = hexToHslTriplet(trimmed);
|
|
382
|
-
if (hsl) return hsl;
|
|
383
|
-
}
|
|
384
|
-
return trimmed;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function mainTsx() {
|
|
388
|
-
return `import React from 'react'
|
|
389
|
-
import ReactDOM from 'react-dom/client'
|
|
390
|
-
import App from './App'
|
|
391
|
-
import './index.css'
|
|
392
|
-
|
|
393
|
-
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
394
|
-
<React.StrictMode>
|
|
395
|
-
<App />
|
|
396
|
-
</React.StrictMode>,
|
|
397
|
-
)`;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
28
|
export interface BaseScaffoldOptions {
|
|
401
29
|
name?: string;
|
|
402
|
-
primaryColor?: string;
|
|
403
|
-
fontFamily?: string;
|
|
404
|
-
borderRadius?: string;
|
|
405
30
|
}
|
|
406
31
|
|
|
407
32
|
/**
|
|
408
|
-
* Write the
|
|
409
|
-
*
|
|
410
|
-
*
|
|
33
|
+
* Write the base scaffold by rendering the TanStack Router template into dir,
|
|
34
|
+
* 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.
|
|
411
37
|
*/
|
|
412
38
|
export async function writeBaseScaffold(
|
|
413
39
|
opts: BaseScaffoldOptions,
|
|
414
40
|
dir: string,
|
|
415
41
|
): Promise<string[]> {
|
|
416
42
|
const name = opts.name ?? "leo-project";
|
|
417
|
-
const primary = normalizePrimaryColor(
|
|
418
|
-
opts.primaryColor ?? "221.2 83.2% 53.3%",
|
|
419
|
-
);
|
|
420
|
-
const font = opts.fontFamily ?? "Inter";
|
|
421
|
-
const radius = opts.borderRadius ?? "0.5rem";
|
|
422
43
|
|
|
423
|
-
|
|
44
|
+
const created = await renderTemplate(
|
|
45
|
+
SCAFFOLD_VERSION,
|
|
46
|
+
{ LEO_NAME: name, LEO_PROJECT_UUID: projectUuidFromDir(dir) },
|
|
47
|
+
dir,
|
|
48
|
+
);
|
|
424
49
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
[path.join("src", "index.css"), indexCss(primary, font, radius)],
|
|
432
|
-
[path.join("src", "lib", "utils.ts"), utilsTs()],
|
|
433
|
-
[path.join("src", "lib", "proxy.ts"), proxyTs(projectUuidFromDir(dir))],
|
|
434
|
-
[path.join("src", "main.tsx"), mainTsx()],
|
|
435
|
-
];
|
|
50
|
+
await mkdir(path.join(dir, ".leo"), { recursive: true });
|
|
51
|
+
const leoPath = path.join(".leo", "project.json");
|
|
52
|
+
await Bun.write(
|
|
53
|
+
path.join(dir, leoPath),
|
|
54
|
+
JSON.stringify({ $schema: 1, scaffold: SCAFFOLD_VERSION }, null, 2),
|
|
55
|
+
);
|
|
436
56
|
|
|
437
|
-
|
|
438
|
-
for (const [filePath, content] of files) {
|
|
439
|
-
await Bun.write(path.join(dir, filePath), content);
|
|
440
|
-
created.push(filePath);
|
|
441
|
-
}
|
|
442
|
-
return created;
|
|
57
|
+
return [...created, leoPath];
|
|
443
58
|
}
|
|
444
59
|
|
|
445
60
|
export const scaffold = tool({
|
|
446
61
|
description:
|
|
447
|
-
"Scaffold a new React + Vite + Tailwind CSS
|
|
448
|
-
"
|
|
449
|
-
"
|
|
450
|
-
"
|
|
451
|
-
"
|
|
452
|
-
"
|
|
453
|
-
"
|
|
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.",
|
|
454
73
|
args: {
|
|
455
74
|
name: tool.schema
|
|
456
75
|
.string()
|
|
457
76
|
.optional()
|
|
458
77
|
.describe("Project name for package.json (default: leo-project)"),
|
|
459
|
-
primaryColor: tool.schema
|
|
460
|
-
.string()
|
|
461
|
-
.optional()
|
|
462
|
-
.describe(
|
|
463
|
-
"Primary brand color. Prefer raw HSL triplet (no hsl() wrapper, no # prefix), " +
|
|
464
|
-
'e.g. "221.2 83.2% 53.3%". Hex like "#3b82f6" is also accepted and will ' +
|
|
465
|
-
"be auto-converted. Default: blue.",
|
|
466
|
-
),
|
|
467
|
-
fontFamily: tool.schema
|
|
468
|
-
.string()
|
|
469
|
-
.optional()
|
|
470
|
-
.describe(
|
|
471
|
-
'Google Font family name, e.g. "Inter", "Poppins" (default: Inter)',
|
|
472
|
-
),
|
|
473
|
-
borderRadius: tool.schema
|
|
474
|
-
.string()
|
|
475
|
-
.optional()
|
|
476
|
-
.describe(
|
|
477
|
-
'Base border radius, e.g. "0.5rem", "0.75rem" (default: 0.5rem)',
|
|
478
|
-
),
|
|
479
78
|
},
|
|
480
79
|
async execute(args, context) {
|
|
481
80
|
const created = await writeBaseScaffold(args, context.directory);
|
|
@@ -483,13 +82,21 @@ export const scaffold = tool({
|
|
|
483
82
|
`Scaffolded ${created.length} files:`,
|
|
484
83
|
...created.map((f) => ` - ${f}`),
|
|
485
84
|
"",
|
|
486
|
-
"Next:
|
|
487
|
-
"
|
|
488
|
-
"
|
|
489
|
-
"
|
|
490
|
-
"
|
|
491
|
-
"
|
|
492
|
-
"
|
|
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.",
|
|
493
100
|
].join("\n");
|
|
494
101
|
},
|
|
495
102
|
});
|