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.
- package/README.md +16 -21
- package/commands/ab-tests.js +14 -11
- package/commands/agents.js +11 -11
- package/commands/data.js +19 -17
- package/commands/deploy.js +145 -82
- package/commands/flows.js +152 -133
- package/commands/interface/init.js +36 -35
- package/commands/interface.js +135 -10
- package/commands/knowledge.js +3 -3
- package/commands/list.js +6 -24
- package/commands/login.js +31 -26
- package/commands/logout.js +13 -4
- package/commands/logs.js +17 -66
- package/commands/project.js +74 -47
- package/commands/secrets.js +1 -1
- package/commands/servers.js +9 -113
- package/commands/storage.js +1 -1
- package/commands/tools.js +4 -4
- package/commands/validate-data-lux.js +5 -2
- package/commands/voice-agents.js +22 -18
- package/lib/config.js +235 -83
- package/lib/helpers.js +6 -4
- package/lux.js +4 -94
- package/package.json +6 -1
- package/templates/interface-boilerplate/components/auth/sign-in-form.tsx +41 -34
- package/templates/interface-boilerplate/components/auth/sign-up-form.tsx +41 -34
- package/templates/interface-boilerplate/components/providers/posthog-provider.tsx +41 -26
- package/templates/interface-boilerplate/gitignore.template +4 -0
- package/templates/interface-boilerplate/lib/auth.config.ts +3 -2
- package/templates/interface-boilerplate/lib/knowledge.ts +2 -2
- package/templates/interface-boilerplate/middleware.ts +14 -3
- package/templates/interface-boilerplate/next-env.d.ts +6 -0
- package/templates/interface-boilerplate/package-lock.json +432 -8
- package/commands/dev.js +0 -578
- package/commands/init.js +0 -126
- package/commands/link.js +0 -127
- package/commands/up.js +0 -211
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "luxlabs",
|
|
3
|
-
"version": "1.0.
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|
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() {
|
|
@@ -106,6 +106,7 @@ export function getRouteType(pathname: string): "protected" | "auth" | "public"
|
|
|
106
106
|
return "public";
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
// Default: treat as
|
|
110
|
-
|
|
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
|
|
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 (
|
|
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
|
|