luxlabs 1.0.21 → 1.0.24

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 (37) hide show
  1. package/README.md +16 -21
  2. package/commands/ab-tests.js +14 -11
  3. package/commands/agents.js +11 -11
  4. package/commands/data.js +19 -17
  5. package/commands/deploy.js +145 -82
  6. package/commands/flows.js +152 -133
  7. package/commands/interface/init.js +36 -35
  8. package/commands/interface.js +135 -10
  9. package/commands/knowledge.js +3 -3
  10. package/commands/list.js +6 -24
  11. package/commands/login.js +31 -26
  12. package/commands/logout.js +13 -4
  13. package/commands/logs.js +17 -66
  14. package/commands/project.js +74 -47
  15. package/commands/secrets.js +1 -1
  16. package/commands/servers.js +9 -113
  17. package/commands/storage.js +1 -1
  18. package/commands/tools.js +4 -4
  19. package/commands/validate-data-lux.js +5 -2
  20. package/commands/voice-agents.js +22 -18
  21. package/lib/config.js +235 -83
  22. package/lib/helpers.js +6 -4
  23. package/lux.js +4 -94
  24. package/package.json +6 -1
  25. package/templates/interface-boilerplate/components/auth/sign-in-form.tsx +41 -34
  26. package/templates/interface-boilerplate/components/auth/sign-up-form.tsx +41 -34
  27. package/templates/interface-boilerplate/components/providers/posthog-provider.tsx +41 -26
  28. package/templates/interface-boilerplate/gitignore.template +4 -0
  29. package/templates/interface-boilerplate/lib/auth.config.ts +3 -2
  30. package/templates/interface-boilerplate/lib/knowledge.ts +2 -2
  31. package/templates/interface-boilerplate/middleware.ts +14 -3
  32. package/templates/interface-boilerplate/next-env.d.ts +6 -0
  33. package/templates/interface-boilerplate/package-lock.json +432 -8
  34. package/commands/dev.js +0 -578
  35. package/commands/init.js +0 -126
  36. package/commands/link.js +0 -127
  37. package/commands/up.js +0 -211
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "luxlabs",
3
- "version": "1.0.21",
3
+ "version": "1.0.24",
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",
@@ -35,6 +35,10 @@
35
35
  "bugs": {
36
36
  "url": "https://github.com/luxAILabs/lux-studio/issues"
37
37
  },
38
+ "scripts": {
39
+ "start": "node lux.js",
40
+ "test": "echo \"TODO: add tests\" && exit 0"
41
+ },
38
42
  "engines": {
39
43
  "node": ">=18.0.0"
40
44
  },
@@ -44,6 +48,7 @@
44
48
  "chalk": "^4.1.2",
45
49
  "chokidar": "^3.6.0",
46
50
  "commander": "^12.1.0",
51
+ "dotenv": "^17.2.3",
47
52
  "express": "^5.1.0",
48
53
  "form-data": "^4.0.0",
49
54
  "ignore": "^5.3.2",
@@ -7,6 +7,9 @@ import { createLux } from "@/lib/lux";
7
7
 
8
8
  const lux = createLux("SignInForm");
9
9
 
10
+ // Check if Google OAuth is enabled via env var (defaults to true)
11
+ const googleOAuthEnabled = process.env.NEXT_PUBLIC_AUTH_GOOGLE_ENABLED !== "false";
12
+
10
13
  export function SignInForm() {
11
14
  const router = useRouter();
12
15
  const [email, setEmail] = useState("");
@@ -48,41 +51,45 @@ export function SignInForm() {
48
51
 
49
52
  return (
50
53
  <div className="space-y-6" data-lux={lux("container")}>
51
- {/* Google Sign In */}
52
- <button
53
- onClick={handleGoogleSignIn}
54
- data-lux={lux("google-button")}
55
- 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"
56
- >
57
- <svg className="w-5 h-5" viewBox="0 0 24 24">
58
- <path
59
- fill="#4285F4"
60
- 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"
61
- />
62
- <path
63
- fill="#34A853"
64
- 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"
65
- />
66
- <path
67
- fill="#FBBC05"
68
- 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"
69
- />
70
- <path
71
- fill="#EA4335"
72
- 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"
73
- />
74
- </svg>
75
- Continue with Google
76
- </button>
54
+ {/* Google Sign In - only show if enabled */}
55
+ {googleOAuthEnabled && (
56
+ <>
57
+ <button
58
+ onClick={handleGoogleSignIn}
59
+ data-lux={lux("google-button")}
60
+ 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"
61
+ >
62
+ <svg className="w-5 h-5" viewBox="0 0 24 24">
63
+ <path
64
+ fill="#4285F4"
65
+ 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"
66
+ />
67
+ <path
68
+ fill="#34A853"
69
+ 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"
70
+ />
71
+ <path
72
+ fill="#FBBC05"
73
+ 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"
74
+ />
75
+ <path
76
+ fill="#EA4335"
77
+ 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"
78
+ />
79
+ </svg>
80
+ Continue with Google
81
+ </button>
77
82
 
78
- <div className="relative">
79
- <div className="absolute inset-0 flex items-center">
80
- <div className="w-full border-t border-gray-300" />
81
- </div>
82
- <div className="relative flex justify-center text-sm">
83
- <span className="bg-white px-2 text-gray-500">Or continue with</span>
84
- </div>
85
- </div>
83
+ <div className="relative">
84
+ <div className="absolute inset-0 flex items-center">
85
+ <div className="w-full border-t border-gray-300" />
86
+ </div>
87
+ <div className="relative flex justify-center text-sm">
88
+ <span className="bg-white px-2 text-gray-500">Or continue with</span>
89
+ </div>
90
+ </div>
91
+ </>
92
+ )}
86
93
 
87
94
  {/* Email/Password Form */}
88
95
  <form onSubmit={handleEmailPassword} className="space-y-4" data-lux={lux("form")}>
@@ -7,6 +7,9 @@ import { createLux } from "@/lib/lux";
7
7
 
8
8
  const lux = createLux("SignUpForm");
9
9
 
10
+ // Check if Google OAuth is enabled via env var (defaults to true)
11
+ const googleOAuthEnabled = process.env.NEXT_PUBLIC_AUTH_GOOGLE_ENABLED !== "false";
12
+
10
13
  export function SignUpForm() {
11
14
  const router = useRouter();
12
15
  const [name, setName] = useState("");
@@ -67,41 +70,45 @@ export function SignUpForm() {
67
70
 
68
71
  return (
69
72
  <div className="space-y-6" data-lux={lux("container")}>
70
- {/* Google Sign Up */}
71
- <button
72
- onClick={handleGoogleSignUp}
73
- data-lux={lux("google-button")}
74
- 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"
75
- >
76
- <svg className="w-5 h-5" viewBox="0 0 24 24">
77
- <path
78
- fill="#4285F4"
79
- 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"
80
- />
81
- <path
82
- fill="#34A853"
83
- 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"
84
- />
85
- <path
86
- fill="#FBBC05"
87
- 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"
88
- />
89
- <path
90
- fill="#EA4335"
91
- 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"
92
- />
93
- </svg>
94
- Continue with Google
95
- </button>
73
+ {/* Google Sign Up - only show if enabled */}
74
+ {googleOAuthEnabled && (
75
+ <>
76
+ <button
77
+ onClick={handleGoogleSignUp}
78
+ data-lux={lux("google-button")}
79
+ 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"
80
+ >
81
+ <svg className="w-5 h-5" viewBox="0 0 24 24">
82
+ <path
83
+ fill="#4285F4"
84
+ 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"
85
+ />
86
+ <path
87
+ fill="#34A853"
88
+ 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"
89
+ />
90
+ <path
91
+ fill="#FBBC05"
92
+ 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"
93
+ />
94
+ <path
95
+ fill="#EA4335"
96
+ 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"
97
+ />
98
+ </svg>
99
+ Continue with Google
100
+ </button>
96
101
 
97
- <div className="relative">
98
- <div className="absolute inset-0 flex items-center">
99
- <div className="w-full border-t border-gray-300" />
100
- </div>
101
- <div className="relative flex justify-center text-sm">
102
- <span className="bg-white px-2 text-gray-500">Or sign up with email</span>
103
- </div>
104
- </div>
102
+ <div className="relative">
103
+ <div className="absolute inset-0 flex items-center">
104
+ <div className="w-full border-t border-gray-300" />
105
+ </div>
106
+ <div className="relative flex justify-center text-sm">
107
+ <span className="bg-white px-2 text-gray-500">Or sign up with email</span>
108
+ </div>
109
+ </div>
110
+ </>
111
+ )}
105
112
 
106
113
  {/* Sign Up Form */}
107
114
  <form onSubmit={handleAccountSubmit} className="space-y-4" data-lux={lux("form")}>
@@ -2,7 +2,7 @@
2
2
 
3
3
  import posthog from 'posthog-js'
4
4
  import { PostHogProvider as PHProvider } from 'posthog-js/react'
5
- import { useEffect, useRef } from 'react'
5
+ import { Suspense, useEffect, useRef } from 'react'
6
6
  import { usePathname, useSearchParams } from 'next/navigation'
7
7
 
8
8
  const POSTHOG_KEY = process.env.NEXT_PUBLIC_POSTHOG_KEY
@@ -50,9 +50,40 @@ function isLuxPreviewMode(): boolean {
50
50
  return getLuxFlagOverrides() !== undefined
51
51
  }
52
52
 
53
+ /**
54
+ * Inner component that handles flag overrides - uses useSearchParams which requires Suspense
55
+ */
56
+ function PostHogFlagOverrideHandler({ initialized }: { initialized: React.MutableRefObject<boolean> }) {
57
+ const searchParams = useSearchParams()
58
+
59
+ // Apply Lux Studio flag overrides whenever URL changes
60
+ // Uses posthog.featureFlags.overrideFeatureFlags() - the correct API
61
+ useEffect(() => {
62
+ // Only skip if PostHog not initialized at all
63
+ if (!initialized.current && !POSTHOG_KEY && !isLuxPreviewMode()) return
64
+
65
+ const flagOverrides = getLuxFlagOverrides()
66
+
67
+ if (flagOverrides && Object.keys(flagOverrides).length > 0) {
68
+ // IMPORTANT: overrideFeatureFlags expects { flags: { [key]: value } }
69
+ posthog.featureFlags.overrideFeatureFlags({
70
+ flags: flagOverrides,
71
+ })
72
+ console.log('[PostHog] Applied Lux Studio flag overrides:', flagOverrides)
73
+
74
+ // Force reload to make React hooks aware of the change
75
+ posthog.reloadFeatureFlags()
76
+ } else if (initialized.current) {
77
+ // Clear any previous overrides when not in preview mode
78
+ posthog.featureFlags.overrideFeatureFlags({ flags: {} })
79
+ }
80
+ }, [searchParams, initialized])
81
+
82
+ return null
83
+ }
84
+
53
85
  export function PostHogProvider({ children }: { children: React.ReactNode }) {
54
86
  const initialized = useRef(false)
55
- const searchParams = useSearchParams()
56
87
 
57
88
  // Initialize PostHog - use a dummy key for preview mode if no real key
58
89
  useEffect(() => {
@@ -90,29 +121,6 @@ export function PostHogProvider({ children }: { children: React.ReactNode }) {
90
121
  initialized.current = true
91
122
  }, [])
92
123
 
93
- // Apply Lux Studio flag overrides whenever URL changes
94
- // Uses posthog.featureFlags.overrideFeatureFlags() - the correct API
95
- useEffect(() => {
96
- // Only skip if PostHog not initialized at all
97
- if (!initialized.current && !POSTHOG_KEY && !isLuxPreviewMode()) return
98
-
99
- const flagOverrides = getLuxFlagOverrides()
100
-
101
- if (flagOverrides && Object.keys(flagOverrides).length > 0) {
102
- // IMPORTANT: overrideFeatureFlags expects { flags: { [key]: value } }
103
- posthog.featureFlags.overrideFeatureFlags({
104
- flags: flagOverrides,
105
- })
106
- console.log('[PostHog] Applied Lux Studio flag overrides:', flagOverrides)
107
-
108
- // Force reload to make React hooks aware of the change
109
- posthog.reloadFeatureFlags()
110
- } else if (initialized.current) {
111
- // Clear any previous overrides when not in preview mode
112
- posthog.featureFlags.overrideFeatureFlags({ flags: {} })
113
- }
114
- }, [searchParams])
115
-
116
124
  // Always use PHProvider if we're initialized (either with real key or preview mode)
117
125
  const shouldUsePHProvider = POSTHOG_KEY || isLuxPreviewMode()
118
126
 
@@ -120,7 +128,14 @@ export function PostHogProvider({ children }: { children: React.ReactNode }) {
120
128
  return <>{children}</>
121
129
  }
122
130
 
123
- return <PHProvider client={posthog}>{children}</PHProvider>
131
+ return (
132
+ <PHProvider client={posthog}>
133
+ <Suspense fallback={null}>
134
+ <PostHogFlagOverrideHandler initialized={initialized} />
135
+ </Suspense>
136
+ {children}
137
+ </PHProvider>
138
+ )
124
139
  }
125
140
 
126
141
  export function PostHogPageView() {
@@ -40,3 +40,7 @@ Thumbs.db
40
40
 
41
41
  # Testing
42
42
  coverage/
43
+
44
+ # Lux Studio generated files (auto-regenerated locally)
45
+ lib/flows.ts
46
+ CLAUDE.md
@@ -106,6 +106,7 @@ export function getRouteType(pathname: string): "protected" | "auth" | "public"
106
106
  return "public";
107
107
  }
108
108
 
109
- // Default: treat as public (change to "protected" for whitelist approach)
110
- return "public";
109
+ // Default: treat as protected (whitelist approach)
110
+ // Only explicitly listed public routes are accessible without auth
111
+ return "protected";
111
112
  }
@@ -207,7 +207,7 @@ export async function uploadToKnowledge(
207
207
  };
208
208
  }
209
209
 
210
- const { uploadUrl, path } = await urlResponse.json();
210
+ const { uploadUrl, path, fileId } = await urlResponse.json();
211
211
 
212
212
  // Step 2: Upload directly to R2
213
213
  if (options.onProgress) {
@@ -241,7 +241,7 @@ export async function uploadToKnowledge(
241
241
  'Authorization': `Bearer ${apiKey}`,
242
242
  'X-Device-Id': deviceId,
243
243
  },
244
- body: JSON.stringify({ path }),
244
+ body: JSON.stringify({ path, fileId }),
245
245
  });
246
246
 
247
247
  if (!confirmResponse.ok) {
@@ -2,9 +2,16 @@ import { NextResponse } from "next/server";
2
2
  import type { NextRequest } from "next/server";
3
3
  import { authConfig, getRouteType } from "@/lib/auth.config";
4
4
 
5
+ const AUTH_ENABLED = process.env.NEXT_PUBLIC_AUTH_ENABLED === "true";
6
+
5
7
  export async function middleware(request: NextRequest) {
6
8
  const { pathname } = request.nextUrl;
7
9
 
10
+ // If auth is disabled, allow everything through
11
+ if (!AUTH_ENABLED) {
12
+ return NextResponse.next();
13
+ }
14
+
8
15
  // Check if user has a session cookie
9
16
  const sessionToken = request.cookies.get("better-auth.session_token");
10
17
  const isAuthenticated = !!sessionToken;
@@ -34,16 +41,20 @@ export async function middleware(request: NextRequest) {
34
41
  return NextResponse.redirect(new URL(authConfig.redirects.afterAuth, request.url));
35
42
  }
36
43
 
37
- // Handle protected routes
44
+ // Handle public routes - allow access
45
+ if (routeType === "public") {
46
+ return NextResponse.next();
47
+ }
48
+
49
+ // Everything else requires authentication (whitelist approach)
38
50
  // Redirect unauthenticated users to signin
39
- if (routeType === "protected" && !isAuthenticated) {
51
+ if (!isAuthenticated) {
40
52
  const signInUrl = new URL(authConfig.redirects.toSignIn, request.url);
41
53
  // Preserve the original destination for redirect after sign-in
42
54
  signInUrl.searchParams.set("callbackUrl", pathname);
43
55
  return NextResponse.redirect(signInUrl);
44
56
  }
45
57
 
46
- // Allow access to public routes
47
58
  return NextResponse.next();
48
59
  }
49
60
 
@@ -0,0 +1,6 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+ import "./.next/types/routes.d.ts";
4
+
5
+ // NOTE: This file should not be edited
6
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.