@helloleo/plugins 0.1.20-beta.1 → 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.
@@ -2,14 +2,9 @@ import { defineConfig } from 'drizzle-kit'
2
2
 
3
3
  // src/db/schema.ts is the single source of truth for the D1 / libsql schema.
4
4
  //
5
- // DEV: `drizzle-kit push --force` (the `dev` script) syncs the schema straight
6
- // into the cf-shim's libsql file. The url MUST match the shim's DEV_DB_PATH
7
- // default (.dev.db) so push and the running app open the same file.
8
- //
9
- // PROD: migrations are generated with `drizzle-kit generate` and applied to D1
10
- // with `wrangler d1 migrations apply DB --remote` (reads migrations_dir in
11
- // wrangler.jsonc) — so no d1-http driver or Cloudflare API env vars are needed
12
- // here.
5
+ // In development, your database is always up-to-date. In production, we migrate
6
+ // automatically when you publish.
7
+
13
8
  const devDbPath = process.env.DEV_DB_PATH ?? '.dev.db'
14
9
 
15
10
  export default defineConfig({
@@ -9,10 +9,9 @@
9
9
  "dev": "drizzle-kit push --force && vite dev",
10
10
  "build": "oxlint --fix && vite build",
11
11
  "preview": "vite preview",
12
- "deploy": "bun run db:generate && vite build && wrangler deploy && wrangler d1 migrations apply DB --remote",
13
12
  "db:generate": "drizzle-kit generate",
13
+ "generate-routes": "tsr generate",
14
14
  "db:push": "drizzle-kit push --force",
15
- "db:migrate": "wrangler d1 migrations apply DB --remote",
16
15
  "typecheck": "tsc --noEmit",
17
16
  "lint": "oxlint --fix",
18
17
  "test": "vitest run"
@@ -75,10 +74,11 @@
75
74
  "devDependencies": {
76
75
  "@cloudflare/vite-plugin": "^1.13.0",
77
76
  "@cloudflare/workers-types": "^4.20250906.0",
78
- "@helloleo/vite-config": "^0.1.2",
77
+ "@helloleo/vite-config": "^0.1.4",
79
78
  "@libsql/client": "^0.14.0",
80
79
  "@tailwindcss/typography": "^0.5.16",
81
80
  "drizzle-kit": "^0.31.10",
81
+ "@tanstack/router-cli": "^1.167.0",
82
82
  "@tanstack/router-plugin": "latest",
83
83
  "@testing-library/dom": "^10.4.1",
84
84
  "@testing-library/react": "^16.3.0",
@@ -0,0 +1,66 @@
1
+ import { createFileRoute } from "@tanstack/react-router";
2
+ import { supabaseAdmin } from "@/integrations/supabase/client.server";
3
+
4
+ export const Route = createFileRoute("/api/health")({
5
+ server: {
6
+ handlers: {
7
+ GET: async () => {
8
+ const startedAt = Date.now();
9
+
10
+ // Server health
11
+ const server = {
12
+ status: "ok" as const,
13
+ uptimeSeconds: Math.round(
14
+ typeof process !== "undefined" &&
15
+ typeof process.uptime === "function"
16
+ ? process.uptime()
17
+ : 0,
18
+ ),
19
+ timestamp: new Date().toISOString(),
20
+ runtime: "cloudflare-workers",
21
+ };
22
+
23
+ // Database health — lightweight query against a known table
24
+ let database: {
25
+ status: "ok" | "error";
26
+ latencyMs: number;
27
+ error: string | null;
28
+ };
29
+ const dbStart = Date.now();
30
+ try {
31
+ const { error } = await supabaseAdmin
32
+ .from("notes")
33
+ .select("id", { count: "exact", head: true })
34
+ .limit(1);
35
+ if (error) throw error;
36
+ database = {
37
+ status: "ok",
38
+ latencyMs: Date.now() - dbStart,
39
+ error: null,
40
+ };
41
+ } catch (err) {
42
+ database = {
43
+ status: "error",
44
+ latencyMs: Date.now() - dbStart,
45
+ error: err instanceof Error ? err.message : String(err),
46
+ };
47
+ }
48
+
49
+ const overall =
50
+ server.status === "ok" && database.status === "ok"
51
+ ? "ok"
52
+ : "degraded";
53
+
54
+ return Response.json(
55
+ {
56
+ status: overall,
57
+ server,
58
+ database,
59
+ checkedInMs: Date.now() - startedAt,
60
+ },
61
+ { status: overall === "ok" ? 200 : 503 },
62
+ );
63
+ },
64
+ },
65
+ },
66
+ });
@@ -18,6 +18,7 @@ export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()(
18
18
  { charSet: 'utf-8' },
19
19
  { name: 'viewport', content: 'width=device-width, initial-scale=1.0' },
20
20
  { title: '{{LEO_NAME}}' },
21
+ { name: 'description', content: '{{LEO_DESCRIPTION}}' },
21
22
  ],
22
23
  links: [
23
24
  { rel: 'stylesheet', href: appCss },
@@ -1,5 +1,6 @@
1
- // Cloudflare Worker entry (wrangler.jsonc `main`). Wraps the TanStack Start
2
- // server entry so an SSR failure renders a clean 500 instead of a raw stack.
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.
3
4
  type ServerEntry = {
4
5
  fetch: (request: Request, env: unknown, ctx: unknown) => Promise<Response> | Response
5
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helloleo/plugins",
3
- "version": "0.1.20-beta.1",
3
+ "version": "0.2.0-beta.0",
4
4
  "description": "OpenCode plugins for HelloLeo",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -33,16 +33,21 @@ export async function renderTemplate(
33
33
  const root = path.join(TEMPLATES_ROOT, templateId);
34
34
  const glob = new Glob("**/*");
35
35
  const created: string[] = [];
36
+
36
37
  for await (const rel of glob.scan({
37
38
  cwd: root,
38
39
  dot: true,
39
40
  onlyFiles: true,
40
41
  })) {
41
42
  if (SKIP.test(rel)) continue;
43
+
42
44
  const raw = await Bun.file(path.join(root, rel)).text();
43
45
  const content = raw.replace(TOKEN_RE, (_m, key: string) => vars[key] ?? "");
46
+
44
47
  await Bun.write(path.join(destDir, rel), content);
48
+
45
49
  created.push(rel);
46
50
  }
51
+
47
52
  return created.sort();
48
53
  }
@@ -2,14 +2,9 @@ import { defineConfig } from 'drizzle-kit'
2
2
 
3
3
  // src/db/schema.ts is the single source of truth for the D1 / libsql schema.
4
4
  //
5
- // DEV: `drizzle-kit push --force` (the `dev` script) syncs the schema straight
6
- // into the cf-shim's libsql file. The url MUST match the shim's DEV_DB_PATH
7
- // default (.dev.db) so push and the running app open the same file.
8
- //
9
- // PROD: migrations are generated with `drizzle-kit generate` and applied to D1
10
- // with `wrangler d1 migrations apply DB --remote` (reads migrations_dir in
11
- // wrangler.jsonc) — so no d1-http driver or Cloudflare API env vars are needed
12
- // here.
5
+ // In development, your database is always up-to-date. In production, we migrate
6
+ // automatically when you publish.
7
+
13
8
  const devDbPath = process.env.DEV_DB_PATH ?? '.dev.db'
14
9
 
15
10
  export default defineConfig({
@@ -9,10 +9,9 @@
9
9
  "dev": "drizzle-kit push --force && vite dev",
10
10
  "build": "oxlint --fix && vite build",
11
11
  "preview": "vite preview",
12
- "deploy": "bun run db:generate && vite build && wrangler deploy && wrangler d1 migrations apply DB --remote",
13
12
  "db:generate": "drizzle-kit generate",
13
+ "generate-routes": "tsr generate",
14
14
  "db:push": "drizzle-kit push --force",
15
- "db:migrate": "wrangler d1 migrations apply DB --remote",
16
15
  "typecheck": "tsc --noEmit",
17
16
  "lint": "oxlint --fix",
18
17
  "test": "vitest run"
@@ -75,10 +74,11 @@
75
74
  "devDependencies": {
76
75
  "@cloudflare/vite-plugin": "^1.13.0",
77
76
  "@cloudflare/workers-types": "^4.20250906.0",
78
- "@helloleo/vite-config": "^0.1.2",
77
+ "@helloleo/vite-config": "^0.1.4",
79
78
  "@libsql/client": "^0.14.0",
80
79
  "@tailwindcss/typography": "^0.5.16",
81
80
  "drizzle-kit": "^0.31.10",
81
+ "@tanstack/router-cli": "^1.167.0",
82
82
  "@tanstack/router-plugin": "latest",
83
83
  "@testing-library/dom": "^10.4.1",
84
84
  "@testing-library/react": "^16.3.0",
@@ -0,0 +1,66 @@
1
+ import { createFileRoute } from "@tanstack/react-router";
2
+ import { supabaseAdmin } from "@/integrations/supabase/client.server";
3
+
4
+ export const Route = createFileRoute("/api/health")({
5
+ server: {
6
+ handlers: {
7
+ GET: async () => {
8
+ const startedAt = Date.now();
9
+
10
+ // Server health
11
+ const server = {
12
+ status: "ok" as const,
13
+ uptimeSeconds: Math.round(
14
+ typeof process !== "undefined" &&
15
+ typeof process.uptime === "function"
16
+ ? process.uptime()
17
+ : 0,
18
+ ),
19
+ timestamp: new Date().toISOString(),
20
+ runtime: "cloudflare-workers",
21
+ };
22
+
23
+ // Database health — lightweight query against a known table
24
+ let database: {
25
+ status: "ok" | "error";
26
+ latencyMs: number;
27
+ error: string | null;
28
+ };
29
+ const dbStart = Date.now();
30
+ try {
31
+ const { error } = await supabaseAdmin
32
+ .from("notes")
33
+ .select("id", { count: "exact", head: true })
34
+ .limit(1);
35
+ if (error) throw error;
36
+ database = {
37
+ status: "ok",
38
+ latencyMs: Date.now() - dbStart,
39
+ error: null,
40
+ };
41
+ } catch (err) {
42
+ database = {
43
+ status: "error",
44
+ latencyMs: Date.now() - dbStart,
45
+ error: err instanceof Error ? err.message : String(err),
46
+ };
47
+ }
48
+
49
+ const overall =
50
+ server.status === "ok" && database.status === "ok"
51
+ ? "ok"
52
+ : "degraded";
53
+
54
+ return Response.json(
55
+ {
56
+ status: overall,
57
+ server,
58
+ database,
59
+ checkedInMs: Date.now() - startedAt,
60
+ },
61
+ { status: overall === "ok" ? 200 : 503 },
62
+ );
63
+ },
64
+ },
65
+ },
66
+ });
@@ -18,6 +18,7 @@ export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()(
18
18
  { charSet: 'utf-8' },
19
19
  { name: 'viewport', content: 'width=device-width, initial-scale=1.0' },
20
20
  { title: '{{LEO_NAME}}' },
21
+ { name: 'description', content: '{{LEO_DESCRIPTION}}' },
21
22
  ],
22
23
  links: [
23
24
  { rel: 'stylesheet', href: appCss },
@@ -1,5 +1,6 @@
1
- // Cloudflare Worker entry (wrangler.jsonc `main`). Wraps the TanStack Start
2
- // server entry so an SSR failure renders a clean 500 instead of a raw stack.
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.
3
4
  type ServerEntry = {
4
5
  fetch: (request: Request, env: unknown, ctx: unknown) => Promise<Response> | Response
5
6
  }
@@ -2,6 +2,7 @@ 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
  /**
@@ -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
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,52 +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 Start project " +
63
- "with shadcn/ui baked in, wired to @helloleo/runtime for Cloudflare " +
64
- "bindings (D1 + KV + R2). Renders the full template: TanStack file-based " +
65
- "routing + SSR (src/routes/, src/router.tsx), the Cloudflare Worker entry " +
66
- "(src/server.ts) + wrangler.jsonc (DB/KV/BUCKET bindings), a Drizzle DB " +
67
- "layer (src/db/schema.ts as the single source of truth, src/db/index.ts " +
68
- "typed client, drizzle.config.ts), the complete shadcn/ui component set " +
69
- "under src/components/ui, src/lib/utils.ts (cn helper), src/lib/proxy.ts " +
70
- "(HelloLeo integration proxy), src/styles.css (shadcn design tokens, " +
71
- "light+dark), plus package.json, vite.config.ts, tsconfig.json, " +
72
- "components.json, .gitignore and the generated src/routeTree.gen.ts. Call " +
73
- "this FIRST for any new project before adding routes and pages. For " +
74
- "NetSuite projects (NetSuite MCP in the live list), call `setup_netsuite` " +
75
- "INSTEAD -- it bundles base scaffold + OAuth wiring in one call. Do not " +
76
- "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.",
77
73
  args: {
78
- name: tool.schema
79
- .string()
80
- .optional()
81
- .describe("Project name for package.json (default: leo-project)"),
74
+ name: scaffoldOptionsSchema.shape.name,
75
+ description: scaffoldOptionsSchema.shape.description,
82
76
  },
83
77
  async execute(args, context) {
84
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/"));
85
83
  return [
86
84
  `Scaffolded ${created.length} files:`,
87
- ...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).",
88
95
  "",
89
- "Next: add routes as files under src/routes/ (TanStack file-based",
90
- " routing). Edit src/routes/index.tsx for the home page.",
91
- "UI: shadcn/ui components are already in src/components/ui -- import and",
92
- " use them; do not attempt to re-add via the shadcn CLI.",
93
- "Design system: edit tokens in src/styles.css (light + dark blocks).",
94
- "Database: edit src/db/schema.ts (the single source of truth) -- `bun run",
95
- " db:push` syncs it to the dev DB; the `dev` script does this for you.",
96
- " Query via `import { db } from '#/db'` inside loaders/server functions.",
97
- " KV + R2: `import { kv, storage } from '@helloleo/runtime'`.",
98
- "Data fetching: React Query is wired (QueryClient on the router context +",
99
- " QueryClientProvider). ALWAYS fetch with @tanstack/react-query --",
100
- " useQuery / useMutation in components, or a route loader calling",
101
- " context.queryClient.ensureQueryData(). Do NOT hand-roll fetches with",
102
- " useEffect + useState. Wrap the fetch call (usually callIntegration())",
103
- " in the queryFn.",
104
- "Integration calls: use callIntegration() from src/lib/proxy.ts -- as the",
105
- " queryFn body, never on its own in useEffect. Its header comment",
106
- " documents slugs, response unwrapping, pagination, and frontend-only vs",
107
- " HelloLeo Cloud routing.",
96
+ "Next: read src/routes/index.tsx and continue with the user's request.",
97
+ "For NetSuite projects, call setup_netsuite.",
108
98
  ].join("\n");
109
99
  },
110
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 {
@@ -342,21 +338,13 @@ function Gate({ children }: { children: ReactNode }) {
342
338
 
343
339
  export const setupNetsuite = tool({
344
340
  description:
345
- "Bootstrap a NetSuite-backed React + Vite + Tailwind v4 + TanStack Router " +
346
- "project in one call. Writes the full HelloLeo base scaffold PLUS the " +
347
- "NetSuite OAuth wiring, all ADDITIVE -- it never overwrites a scaffold " +
348
- "file. Adds src/lib/netsuite.ts (OAuth client), src/lib/auth.tsx " +
349
- "(NetSuiteAuthProvider + useNetSuiteAuth), and " +
350
- "src/components/auth/{AuthLayout,LoginPage,SignInWithNetSuite}.tsx. " +
351
- "Use this INSTEAD of `scaffold` for any project that needs NetSuite. " +
352
- "After it runs there is exactly ONE manual step: wrap the root route's " +
353
- "<Outlet /> with <AuthLayout> in src/routes/__root.tsx (the tool's output " +
354
- "shows the snippet). Real OAuth is wired from day one -- never replace " +
355
- "<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.",
356
345
  args: {},
357
346
  async execute(_args, context) {
358
347
  const dir = context.directory;
359
- const base = await writeBaseScaffold({}, dir);
360
348
 
361
349
  await mkdir(path.join(dir, "src", "components", "auth"), {
362
350
  recursive: true,
@@ -384,45 +372,27 @@ export const setupNetsuite = tool({
384
372
  await Bun.write(path.join(dir, filePath), content);
385
373
  }
386
374
 
387
- const all = [...base, ...nsFiles.map(([f]) => f)];
388
375
  return [
389
- `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)",
390
382
  "",
391
- "All you need to do is wrap the root layout with the auth layout.",
392
- "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).",
393
388
  "",
394
- " import { AuthLayout } from '../components/auth/AuthLayout'",
395
- "",
396
- " function RootComponent() {",
397
- " return (",
398
- " <>",
399
- " <AuthLayout>",
400
- " <Outlet />",
401
- " </AuthLayout>",
402
- " {/* keep the existing <TanStackDevtools ... /> here */}",
403
- " </>",
404
- " )",
405
- " }",
389
+ "Next: in src/routes/__root.tsx, import AuthLayout and wrap <Outlet /> with it. Keep any existing providers.",
406
390
  "",
407
- "That single wrap gates every route: <LoginPage /> until the user signs",
408
- "in with NetSuite, your routes after. Read the session anywhere below it",
409
- "with useNetSuiteAuth() from src/lib/auth.tsx. NEVER swap <AuthLayout>",
410
- "for a password gate -- real OAuth is the only supported login.",
411
- "",
412
- "NetSuite OAuth files written:",
413
- " - src/lib/netsuite.ts OAuth client (signIn / session / workerFetch)",
414
- " - src/lib/auth.tsx NetSuiteAuthProvider + useNetSuiteAuth",
415
- " - src/components/auth/AuthLayout.tsx provider + gate (wrap root with this)",
416
- " - src/components/auth/LoginPage.tsx unauthenticated view",
417
- " - src/components/auth/SignInWithNetSuite.tsx sign-in button",
391
+ " import { AuthLayout } from '../components/auth/AuthLayout'",
418
392
  "",
419
- "Worker (deploy via cloudflare-backend MCP) must serve:",
420
- " GET /auth/netsuite/start -- redirect to NetSuite OAuth",
421
- " GET /auth/netsuite/callback -- code -> token exchange",
422
- " GET /api/me -- current session",
423
- " POST /auth/netsuite/logout -- clear session",
424
- " Reads env.NETSUITE_ACCOUNT_ID / _CLIENT_ID / _CLIENT_SECRET.",
425
- " VITE_WORKER_URL must be set (HelloLeo Cloud provisioner seeds it).",
393
+ " <AuthLayout>",
394
+ " <Outlet />",
395
+ " </AuthLayout>",
426
396
  ].join("\n");
427
397
  },
428
398
  });
@@ -1,27 +0,0 @@
1
- {
2
- "$schema": "node_modules/wrangler/config-schema.json",
3
- "name": "{{LEO_NAME}}",
4
- "compatibility_date": "2025-09-02",
5
- "compatibility_flags": ["nodejs_compat"],
6
- // @cloudflare/vite-plugin builds the Worker from the TanStack Start server
7
- // entry; src/server.ts wraps it. These three bindings are the PROD path that
8
- // the dev cf-shim emulates locally — keep the names DB / KV / BUCKET, that is
9
- // the contract @helloleo/runtime expects.
10
- "main": "src/server.ts",
11
- "observability": { "enabled": true },
12
- // Provision real resources before deploy (`wrangler d1/kv/r2 create`) and swap
13
- // the placeholder ids below. In dev none of this matters — the cf-shim backs
14
- // DB with libsql (.dev.db), KV in libsql, and R2 on the local filesystem.
15
- "d1_databases": [
16
- {
17
- "binding": "DB",
18
- "database_name": "{{LEO_NAME}}",
19
- "database_id": "REPLACE_WITH_D1_ID",
20
- // drizzle-kit generate writes migrations here; `wrangler d1 migrations
21
- // apply DB --remote` reads this dir to apply them to prod D1.
22
- "migrations_dir": "drizzle"
23
- }
24
- ],
25
- "kv_namespaces": [{ "binding": "KV", "id": "REPLACE_WITH_KV_ID" }],
26
- "r2_buckets": [{ "binding": "BUCKET", "bucket_name": "{{LEO_NAME}}" }]
27
- }
@@ -1,27 +0,0 @@
1
- {
2
- "$schema": "node_modules/wrangler/config-schema.json",
3
- "name": "{{LEO_NAME}}",
4
- "compatibility_date": "2025-09-02",
5
- "compatibility_flags": ["nodejs_compat"],
6
- // @cloudflare/vite-plugin builds the Worker from the TanStack Start server
7
- // entry; src/server.ts wraps it. These three bindings are the PROD path that
8
- // the dev cf-shim emulates locally — keep the names DB / KV / BUCKET, that is
9
- // the contract @helloleo/runtime expects.
10
- "main": "src/server.ts",
11
- "observability": { "enabled": true },
12
- // Provision real resources before deploy (`wrangler d1/kv/r2 create`) and swap
13
- // the placeholder ids below. In dev none of this matters — the cf-shim backs
14
- // DB with libsql (.dev.db), KV in libsql, and R2 on the local filesystem.
15
- "d1_databases": [
16
- {
17
- "binding": "DB",
18
- "database_name": "{{LEO_NAME}}",
19
- "database_id": "REPLACE_WITH_D1_ID",
20
- // drizzle-kit generate writes migrations here; `wrangler d1 migrations
21
- // apply DB --remote` reads this dir to apply them to prod D1.
22
- "migrations_dir": "drizzle"
23
- }
24
- ],
25
- "kv_namespaces": [{ "binding": "KV", "id": "REPLACE_WITH_KV_ID" }],
26
- "r2_buckets": [{ "binding": "BUCKET", "bucket_name": "{{LEO_NAME}}" }]
27
- }