luxlabs 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/commands/interface/boilerplate.js +16 -2
  2. package/package.json +1 -1
  3. package/templates/interface-boilerplate/app/api/auth/[...all]/route.ts +52 -0
  4. package/templates/interface-boilerplate/app/auth/callback/page.tsx +34 -0
  5. package/templates/interface-boilerplate/app/dashboard/page.tsx +53 -0
  6. package/templates/interface-boilerplate/app/favicon.ico +0 -0
  7. package/templates/interface-boilerplate/app/globals.css +26 -0
  8. package/templates/interface-boilerplate/app/layout.tsx +34 -0
  9. package/templates/interface-boilerplate/app/page.tsx +50 -0
  10. package/templates/interface-boilerplate/app/settings/page.tsx +71 -0
  11. package/templates/interface-boilerplate/app/sign-in/page.tsx +19 -0
  12. package/templates/interface-boilerplate/app/sign-up/page.tsx +19 -0
  13. package/templates/interface-boilerplate/components/auth/sign-in-form.tsx +137 -0
  14. package/templates/interface-boilerplate/components/auth/sign-up-form.tsx +225 -0
  15. package/templates/interface-boilerplate/components/ui/badge.tsx +36 -0
  16. package/templates/interface-boilerplate/components/ui/button.tsx +57 -0
  17. package/templates/interface-boilerplate/components/ui/card.tsx +76 -0
  18. package/templates/interface-boilerplate/components/ui/input.tsx +22 -0
  19. package/templates/interface-boilerplate/eslint.config.mjs +18 -0
  20. package/templates/interface-boilerplate/lib/auth-client.ts +18 -0
  21. package/templates/interface-boilerplate/lib/auth.config.ts +111 -0
  22. package/templates/interface-boilerplate/lib/utils.ts +6 -0
  23. package/templates/interface-boilerplate/middleware.ts +60 -0
  24. package/templates/interface-boilerplate/next.config.ts +7 -0
  25. package/templates/interface-boilerplate/package-lock.json +6855 -0
  26. package/templates/interface-boilerplate/package.json +32 -0
  27. package/templates/interface-boilerplate/postcss.config.mjs +7 -0
  28. package/templates/interface-boilerplate/tsconfig.json +34 -0
@@ -3,10 +3,24 @@ const path = require('path');
3
3
 
4
4
  /**
5
5
  * Get the templates directory path
6
- * Uses the canonical templates directory at project root (single source of truth)
6
+ * Tries multiple paths to support both:
7
+ * - Electron app: electron/bin/lux-cli/commands/interface/ → 4 levels up
8
+ * - npm package: luxlabs/commands/interface/ → 2 levels up
7
9
  */
8
10
  function getTemplatesDir() {
9
- return path.join(__dirname, '../../../../templates/interface-boilerplate');
11
+ const candidates = [
12
+ path.join(__dirname, '../../../../templates/interface-boilerplate'), // Electron app
13
+ path.join(__dirname, '../../templates/interface-boilerplate'), // npm package
14
+ ];
15
+
16
+ for (const candidate of candidates) {
17
+ if (fs.existsSync(candidate)) {
18
+ return candidate;
19
+ }
20
+ }
21
+
22
+ // Return first candidate for error message
23
+ return candidates[0];
10
24
  }
11
25
 
12
26
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "luxlabs",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "CLI tool for Lux - Upload and deploy interfaces from your terminal",
5
5
  "author": "Jason Henkel <jason@uselux.ai>",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Auth API Proxy
3
+ *
4
+ * Proxies all auth requests to the Lux Studio API worker.
5
+ * The worker handles Better Auth with the org's database.
6
+ */
7
+
8
+ const API_URL = process.env.NEXT_PUBLIC_LUX_API_URL || "https://v2.uselux.ai";
9
+ const ORG_ID = process.env.NEXT_PUBLIC_LUX_ORG_ID || "";
10
+
11
+ async function proxyRequest(request: Request): Promise<Response> {
12
+ const url = new URL(request.url);
13
+ const path = url.pathname.replace("/api/auth", "");
14
+ const targetUrl = `${API_URL}/api/auth${path}${url.search}`;
15
+
16
+ // Forward the request to the worker
17
+ const headers = new Headers(request.headers);
18
+ headers.set("X-Org-Id", ORG_ID);
19
+
20
+ const response = await fetch(targetUrl, {
21
+ method: request.method,
22
+ headers,
23
+ body: request.method !== "GET" && request.method !== "HEAD" ? request.body : undefined,
24
+ // @ts-ignore - duplex is needed for streaming body
25
+ duplex: "half",
26
+ });
27
+
28
+ // Forward response back with CORS headers
29
+ const responseHeaders = new Headers(response.headers);
30
+
31
+ return new Response(response.body, {
32
+ status: response.status,
33
+ statusText: response.statusText,
34
+ headers: responseHeaders,
35
+ });
36
+ }
37
+
38
+ export async function GET(request: Request) {
39
+ return proxyRequest(request);
40
+ }
41
+
42
+ export async function POST(request: Request) {
43
+ return proxyRequest(request);
44
+ }
45
+
46
+ export async function PUT(request: Request) {
47
+ return proxyRequest(request);
48
+ }
49
+
50
+ export async function DELETE(request: Request) {
51
+ return proxyRequest(request);
52
+ }
@@ -0,0 +1,34 @@
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { useSession } from "@/lib/auth-client";
6
+
7
+ /**
8
+ * Auth Callback Page
9
+ *
10
+ * Handles redirect after OAuth sign-in.
11
+ * Simply redirects authenticated users to dashboard.
12
+ */
13
+ export default function AuthCallbackPage() {
14
+ const router = useRouter();
15
+ const { data: session, isPending } = useSession();
16
+
17
+ useEffect(() => {
18
+ if (!isPending) {
19
+ if (session) {
20
+ // Authenticated - go to dashboard
21
+ router.push("/dashboard");
22
+ } else {
23
+ // Not authenticated - go to sign in
24
+ router.push("/sign-in");
25
+ }
26
+ }
27
+ }, [session, isPending, router]);
28
+
29
+ return (
30
+ <div className="min-h-screen flex items-center justify-center bg-gray-50">
31
+ <div className="text-gray-600">Completing sign in...</div>
32
+ </div>
33
+ );
34
+ }
@@ -0,0 +1,53 @@
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+ import { useSession, signOut } from "@/lib/auth-client";
5
+ import { useRouter } from "next/navigation";
6
+
7
+ export default function DashboardPage() {
8
+ const { data: session, isPending } = useSession();
9
+ const router = useRouter();
10
+
11
+ useEffect(() => {
12
+ if (!isPending && !session) {
13
+ router.push("/sign-in");
14
+ }
15
+ }, [isPending, session, router]);
16
+
17
+ if (isPending) {
18
+ return (
19
+ <div className="min-h-screen flex items-center justify-center">
20
+ <p>Loading...</p>
21
+ </div>
22
+ );
23
+ }
24
+
25
+ if (!session) {
26
+ return null;
27
+ }
28
+
29
+ async function handleSignOut() {
30
+ await signOut();
31
+ router.push("/");
32
+ }
33
+
34
+ return (
35
+ <div className="min-h-screen bg-zinc-50 p-8">
36
+ <div className="max-w-2xl mx-auto bg-white p-8 rounded-lg shadow-md">
37
+ <h1 className="text-2xl font-semibold mb-4">Dashboard</h1>
38
+ <p className="text-zinc-600 mb-2">
39
+ Welcome, <span className="font-medium">{session.user.name}</span>!
40
+ </p>
41
+ <p className="text-zinc-600 mb-6">
42
+ Email: {session.user.email}
43
+ </p>
44
+ <button
45
+ onClick={handleSignOut}
46
+ className="px-4 py-2 bg-black text-white rounded-md hover:bg-zinc-800"
47
+ >
48
+ Sign Out
49
+ </button>
50
+ </div>
51
+ </div>
52
+ );
53
+ }
@@ -0,0 +1,26 @@
1
+ @import "tailwindcss";
2
+
3
+ :root {
4
+ --background: #ffffff;
5
+ --foreground: #171717;
6
+ }
7
+
8
+ @theme inline {
9
+ --color-background: var(--background);
10
+ --color-foreground: var(--foreground);
11
+ --font-sans: var(--font-geist-sans);
12
+ --font-mono: var(--font-geist-mono);
13
+ }
14
+
15
+ @media (prefers-color-scheme: dark) {
16
+ :root {
17
+ --background: #0a0a0a;
18
+ --foreground: #ededed;
19
+ }
20
+ }
21
+
22
+ body {
23
+ background: var(--background);
24
+ color: var(--foreground);
25
+ font-family: Arial, Helvetica, sans-serif;
26
+ }
@@ -0,0 +1,34 @@
1
+ import type { Metadata } from "next";
2
+ import { Geist, Geist_Mono } from "next/font/google";
3
+ import "./globals.css";
4
+
5
+ const geistSans = Geist({
6
+ variable: "--font-geist-sans",
7
+ subsets: ["latin"],
8
+ });
9
+
10
+ const geistMono = Geist_Mono({
11
+ variable: "--font-geist-mono",
12
+ subsets: ["latin"],
13
+ });
14
+
15
+ export const metadata: Metadata = {
16
+ title: "Create Next App",
17
+ description: "Generated by create next app",
18
+ };
19
+
20
+ export default function RootLayout({
21
+ children,
22
+ }: Readonly<{
23
+ children: React.ReactNode;
24
+ }>) {
25
+ return (
26
+ <html lang="en">
27
+ <body
28
+ className={`${geistSans.variable} ${geistMono.variable} antialiased`}
29
+ >
30
+ {children}
31
+ </body>
32
+ </html>
33
+ );
34
+ }
@@ -0,0 +1,50 @@
1
+ import Link from "next/link";
2
+
3
+ export default function Home() {
4
+ return (
5
+ <div className="min-h-screen flex flex-col bg-white">
6
+ <main className="flex-1 flex flex-col items-center justify-center px-6">
7
+ <div className="max-w-2xl w-full text-center">
8
+ <p className="text-sm font-medium tracking-widest text-zinc-400 uppercase mb-4">
9
+ Boilerplate
10
+ </p>
11
+ <h1 className="text-6xl font-semibold tracking-tight text-black mb-6">
12
+ Lux
13
+ </h1>
14
+ <p className="text-lg text-zinc-500 mb-12 max-w-md mx-auto">
15
+ Authentication, database, and UI components. Everything you need to start building.
16
+ </p>
17
+
18
+ <div className="flex gap-4 justify-center mb-16">
19
+ <Link
20
+ href="/sign-in"
21
+ className="px-8 py-3 bg-black text-white text-sm font-medium rounded-full hover:bg-zinc-800 transition-colors"
22
+ >
23
+ Sign In
24
+ </Link>
25
+ <Link
26
+ href="/sign-up"
27
+ className="px-8 py-3 border border-zinc-200 text-black text-sm font-medium rounded-full hover:bg-zinc-50 transition-colors"
28
+ >
29
+ Sign Up
30
+ </Link>
31
+ </div>
32
+
33
+ <div className="flex items-center justify-center gap-8 text-sm text-zinc-400">
34
+ <span>Next.js</span>
35
+ <span className="w-1 h-1 bg-zinc-300 rounded-full" />
36
+ <span>Auth</span>
37
+ <span className="w-1 h-1 bg-zinc-300 rounded-full" />
38
+ <span>Database</span>
39
+ <span className="w-1 h-1 bg-zinc-300 rounded-full" />
40
+ <span>Tailwind</span>
41
+ </div>
42
+ </div>
43
+ </main>
44
+
45
+ <footer className="py-6 text-center text-sm text-zinc-400">
46
+ <p>Ready to ship.</p>
47
+ </footer>
48
+ </div>
49
+ );
50
+ }
@@ -0,0 +1,71 @@
1
+ "use client";
2
+
3
+ import { useSession, signOut } from "@/lib/auth-client";
4
+ import { useRouter } from "next/navigation";
5
+ import { useEffect } from "react";
6
+
7
+ export default function SettingsPage() {
8
+ const { data: session, isPending } = useSession();
9
+ const router = useRouter();
10
+
11
+ useEffect(() => {
12
+ if (!isPending && !session) {
13
+ router.push("/sign-in");
14
+ }
15
+ }, [session, isPending, router]);
16
+
17
+ if (isPending) {
18
+ return (
19
+ <div className="min-h-screen flex items-center justify-center">
20
+ <div className="text-gray-500">Loading...</div>
21
+ </div>
22
+ );
23
+ }
24
+
25
+ if (!session) {
26
+ return null;
27
+ }
28
+
29
+ const handleSignOut = async () => {
30
+ await signOut();
31
+ router.push("/sign-in");
32
+ };
33
+
34
+ return (
35
+ <div className="min-h-screen bg-gray-50">
36
+ <div className="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
37
+ <div className="mb-8">
38
+ <h1 className="text-3xl font-bold text-gray-900">Settings</h1>
39
+ <p className="mt-2 text-sm text-gray-600">
40
+ Manage your account settings
41
+ </p>
42
+ </div>
43
+
44
+ <div className="bg-white shadow rounded-lg p-6">
45
+ <h2 className="text-lg font-medium text-gray-900 mb-4">Account</h2>
46
+
47
+ <div className="space-y-4">
48
+ <div>
49
+ <label className="block text-sm font-medium text-gray-700">Name</label>
50
+ <p className="mt-1 text-sm text-gray-900">{session.user.name}</p>
51
+ </div>
52
+
53
+ <div>
54
+ <label className="block text-sm font-medium text-gray-700">Email</label>
55
+ <p className="mt-1 text-sm text-gray-900">{session.user.email}</p>
56
+ </div>
57
+ </div>
58
+
59
+ <div className="mt-6 pt-6 border-t border-gray-200">
60
+ <button
61
+ onClick={handleSignOut}
62
+ className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors"
63
+ >
64
+ Sign Out
65
+ </button>
66
+ </div>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ );
71
+ }
@@ -0,0 +1,19 @@
1
+ import { SignInForm } from "@/components/auth/sign-in-form";
2
+
3
+ export default function SignInPage() {
4
+ return (
5
+ <div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
6
+ <div className="max-w-md w-full space-y-8">
7
+ <div className="text-center">
8
+ <h2 className="text-3xl font-bold text-gray-900">Sign in</h2>
9
+ <p className="mt-2 text-sm text-gray-600">
10
+ Welcome back! Please sign in to continue.
11
+ </p>
12
+ </div>
13
+ <div className="mt-8 bg-white py-8 px-6 shadow rounded-lg">
14
+ <SignInForm />
15
+ </div>
16
+ </div>
17
+ </div>
18
+ );
19
+ }
@@ -0,0 +1,19 @@
1
+ import { SignUpForm } from "@/components/auth/sign-up-form";
2
+
3
+ export default function SignUpPage() {
4
+ return (
5
+ <div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
6
+ <div className="max-w-md w-full space-y-8">
7
+ <div className="text-center">
8
+ <h2 className="text-3xl font-bold text-gray-900">Create account</h2>
9
+ <p className="mt-2 text-sm text-gray-600">
10
+ Get started by creating your account
11
+ </p>
12
+ </div>
13
+ <div className="mt-8 bg-white py-8 px-6 shadow rounded-lg">
14
+ <SignUpForm />
15
+ </div>
16
+ </div>
17
+ </div>
18
+ );
19
+ }
@@ -0,0 +1,137 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { authClient } from "@/lib/auth-client";
5
+ import { useRouter } from "next/navigation";
6
+
7
+ export function SignInForm() {
8
+ const router = useRouter();
9
+ const [email, setEmail] = useState("");
10
+ const [password, setPassword] = useState("");
11
+ const [isLoading, setIsLoading] = useState(false);
12
+ const [error, setError] = useState("");
13
+
14
+ const handleEmailPassword = async (e: React.FormEvent) => {
15
+ e.preventDefault();
16
+ setError("");
17
+ setIsLoading(true);
18
+
19
+ try {
20
+ await authClient.signIn.email({
21
+ email,
22
+ password,
23
+ callbackURL: "/dashboard",
24
+ });
25
+
26
+ router.push("/dashboard");
27
+ } catch (err) {
28
+ setError("Invalid email or password");
29
+ } finally {
30
+ setIsLoading(false);
31
+ }
32
+ };
33
+
34
+ const handleGoogleSignIn = async () => {
35
+ setError("");
36
+ try {
37
+ await authClient.signIn.social({
38
+ provider: "google",
39
+ callbackURL: "/dashboard",
40
+ });
41
+ } catch (err) {
42
+ setError("Failed to sign in with Google");
43
+ }
44
+ };
45
+
46
+ return (
47
+ <div className="space-y-6">
48
+ {/* Google Sign In */}
49
+ <button
50
+ onClick={handleGoogleSignIn}
51
+ className="w-full flex items-center justify-center gap-3 px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"
52
+ >
53
+ <svg className="w-5 h-5" viewBox="0 0 24 24">
54
+ <path
55
+ fill="#4285F4"
56
+ d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
57
+ />
58
+ <path
59
+ fill="#34A853"
60
+ d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
61
+ />
62
+ <path
63
+ fill="#FBBC05"
64
+ d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
65
+ />
66
+ <path
67
+ fill="#EA4335"
68
+ d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
69
+ />
70
+ </svg>
71
+ Continue with Google
72
+ </button>
73
+
74
+ <div className="relative">
75
+ <div className="absolute inset-0 flex items-center">
76
+ <div className="w-full border-t border-gray-300" />
77
+ </div>
78
+ <div className="relative flex justify-center text-sm">
79
+ <span className="bg-white px-2 text-gray-500">Or continue with</span>
80
+ </div>
81
+ </div>
82
+
83
+ {/* Email/Password Form */}
84
+ <form onSubmit={handleEmailPassword} className="space-y-4">
85
+ <div>
86
+ <label className="block text-sm font-medium text-gray-700 mb-1">
87
+ Email
88
+ </label>
89
+ <input
90
+ type="email"
91
+ value={email}
92
+ onChange={(e) => setEmail(e.target.value)}
93
+ required
94
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-gray-900 focus:border-gray-900"
95
+ placeholder="you@example.com"
96
+ />
97
+ </div>
98
+
99
+ <div>
100
+ <label className="block text-sm font-medium text-gray-700 mb-1">
101
+ Password
102
+ </label>
103
+ <input
104
+ type="password"
105
+ value={password}
106
+ onChange={(e) => setPassword(e.target.value)}
107
+ required
108
+ className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-gray-900 focus:border-gray-900"
109
+ placeholder="••••••••"
110
+ />
111
+ </div>
112
+
113
+ {error && (
114
+ <div className="text-sm text-red-600 bg-red-50 border border-red-200 rounded px-3 py-2">
115
+ {error}
116
+ </div>
117
+ )}
118
+
119
+ <button
120
+ type="submit"
121
+ disabled={isLoading}
122
+ className="w-full px-4 py-2 bg-gray-900 text-white rounded-lg hover:bg-gray-800 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
123
+ >
124
+ {isLoading ? "Signing in..." : "Sign in"}
125
+ </button>
126
+ </form>
127
+
128
+ {/* Sign Up Link */}
129
+ <div className="text-center text-sm text-gray-600">
130
+ Don&apos;t have an account?{" "}
131
+ <a href="/sign-up" className="text-gray-900 hover:underline font-medium">
132
+ Sign up
133
+ </a>
134
+ </div>
135
+ </div>
136
+ );
137
+ }