Skip to content

Commit 179f5ee

Browse files
committed
feat: Add Roo Code Cloud provider support
- Add Roo Code Cloud as a new AI provider with authentication - Implement OAuth flow with Clerk authentication service - Add Grok Code Fast and Code Supernova models - Fix deep link handling to prevent opening multiple app instances - Focus main window after successful authentication - Bump version to 0.23.0
1 parent 5afd44a commit 179f5ee

File tree

9 files changed

+198
-229
lines changed

9 files changed

+198
-229
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "alifullstack",
33
"productName": "AliFullStack",
4-
"version": "0.22.0",
4+
"version": "0.23.0",
55
"description": "Free, local, open-source AI app builder",
66
"main": ".vite/build/main.js",
77
"repository": {

src/components/settings/ApiKeyConfiguration.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from "@/components/ui/accordion";
99
import { AzureConfiguration } from "./AzureConfiguration";
1010
import { VertexConfiguration } from "./VertexConfiguration";
11+
import { RooCodeConfiguration } from "./RooCodeConfiguration";
1112
import { Input } from "@/components/ui/input";
1213
import { Button } from "@/components/ui/button";
1314
import { UserSettings } from "@/lib/schemas";
@@ -57,6 +58,11 @@ export function ApiKeyConfiguration({
5758
return <VertexConfiguration />;
5859
}
5960

61+
// Special handling for Roo Code Cloud which uses OAuth authentication
62+
if (provider === "roo") {
63+
return <RooCodeConfiguration />;
64+
}
65+
6066
const envApiKey = envVarName ? envVars[envVarName] : undefined;
6167
const userApiKey = settings?.providerSettings?.[provider]?.apiKey?.value;
6268

Lines changed: 48 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,190 +1,87 @@
11
import { useState, useEffect } from "react";
2-
import { LogIn, LogOut, User, AlertCircle } from "lucide-react";
3-
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
42
import { Button } from "@/components/ui/button";
3+
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
4+
import { CheckCircle, LogIn } from "lucide-react";
55
import { IpcClient } from "@/ipc/ipc_client";
6-
import { showError } from "@/lib/toast";
7-
import { useDeepLink } from "@/hooks/useDeepLink";
86

9-
interface RooCodeConfigurationProps {
10-
provider: string;
11-
}
12-
13-
interface AuthState {
14-
isAuthenticated: boolean;
15-
userInfo?: {
16-
name?: string;
17-
email?: string;
18-
picture?: string;
19-
};
20-
}
21-
22-
export function RooCodeConfiguration({ provider }: RooCodeConfigurationProps) {
23-
const [authState, setAuthState] = useState<AuthState>({ isAuthenticated: false });
7+
export function RooCodeConfiguration() {
8+
const [isAuthenticated, setIsAuthenticated] = useState(false);
249
const [isLoading, setIsLoading] = useState(false);
25-
const [isInitializing, setIsInitializing] = useState(true);
2610

2711
// Check authentication status on component mount
2812
useEffect(() => {
29-
// Small delay to ensure IPC client is initialized
30-
const timer = setTimeout(() => {
31-
checkAuthStatus();
32-
}, 100);
33-
return () => clearTimeout(timer);
13+
checkAuthStatus();
3414
}, []);
3515

36-
// Listen for authentication callback
37-
useDeepLink("roocode-auth-callback", async (data: { code: string; state: string }) => {
38-
try {
39-
if (!(window as any).electron || !(window as any).electron.ipcRenderer) {
40-
console.error("IPC renderer not available for auth callback");
41-
return;
42-
}
43-
44-
const ipcClient = IpcClient.getInstance();
45-
await ipcClient.roocodeAuthCallback(data.code, data.state);
46-
await checkAuthStatus(); // Refresh auth status
47-
} catch (error) {
48-
console.error("Failed to handle Roo Code auth callback:", error);
49-
showError("Failed to complete Roo Code authentication");
50-
}
51-
});
52-
5316
const checkAuthStatus = async () => {
54-
try {
55-
setIsInitializing(true);
56-
// Check if IPC client is available
57-
if (!(window as any).electron || !(window as any).electron.ipcRenderer) {
58-
console.warn("IPC renderer not available yet, retrying...");
59-
setTimeout(() => checkAuthStatus(), 500);
60-
return;
61-
}
62-
63-
const ipcClient = IpcClient.getInstance();
64-
const status = await ipcClient.roocodeAuthStatus();
65-
setAuthState(status);
66-
} catch (error) {
67-
console.error("Failed to check Roo Code auth status:", error);
68-
setAuthState({ isAuthenticated: false });
69-
} finally {
70-
setIsInitializing(false);
71-
}
72-
};
73-
74-
const handleLogin = async () => {
7517
try {
7618
setIsLoading(true);
77-
if (!(window as any).electron || !(window as any).electron.ipcRenderer) {
78-
showError("IPC renderer not available. Please refresh the page.");
79-
return;
80-
}
81-
8219
const ipcClient = IpcClient.getInstance();
83-
await ipcClient.roocodeLogin();
84-
// The login process will open a browser window and handle the callback
20+
const authStatus = await ipcClient.roocodeAuthStatus();
21+
setIsAuthenticated(authStatus?.isAuthenticated || false);
8522
} catch (error) {
86-
console.error("Failed to initiate Roo Code login:", error);
87-
showError("Failed to initiate Roo Code authentication");
23+
console.error("Failed to check Roo Code authentication status:", error);
24+
setIsAuthenticated(false);
8825
} finally {
8926
setIsLoading(false);
9027
}
9128
};
9229

93-
const handleLogout = async () => {
30+
const handleSignIn = async () => {
9431
try {
9532
setIsLoading(true);
96-
if (!(window as any).electron || !(window as any).electron.ipcRenderer) {
97-
showError("IPC renderer not available. Please refresh the page.");
98-
return;
99-
}
100-
10133
const ipcClient = IpcClient.getInstance();
102-
await ipcClient.roocodeLogout();
103-
await checkAuthStatus(); // Refresh auth status
34+
// This will trigger the OAuth flow
35+
await ipcClient.roocodeLogin();
36+
// After sign in, check status again
37+
setTimeout(checkAuthStatus, 1000);
10438
} catch (error) {
105-
console.error("Failed to logout from Roo Code:", error);
106-
showError("Failed to logout from Roo Code");
39+
console.error("Failed to initiate Roo Code sign in:", error);
10740
} finally {
10841
setIsLoading(false);
10942
}
11043
};
11144

112-
if (isInitializing) {
45+
if (isAuthenticated) {
11346
return (
114-
<div className="space-y-4">
115-
<div className="border rounded-lg px-4 py-4 bg-(--background-lightest)">
116-
<div className="flex items-center justify-center">
117-
<div className="text-sm text-muted-foreground">Checking authentication status...</div>
118-
</div>
119-
</div>
120-
</div>
47+
<Alert variant="default" className="border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950">
48+
<CheckCircle className="h-4 w-4 text-green-600 dark:text-green-400" />
49+
<AlertTitle className="text-green-800 dark:text-green-200">
50+
Connected to Roo Code Cloud
51+
</AlertTitle>
52+
<AlertDescription className="text-green-700 dark:text-green-300">
53+
You are successfully authenticated with Roo Code Cloud. You can now use Roo Code Cloud models.
54+
</AlertDescription>
55+
</Alert>
12156
);
12257
}
12358

12459
return (
12560
<div className="space-y-4">
126-
<div className="border rounded-lg px-4 py-4 bg-(--background-lightest)">
127-
<h3 className="text-lg font-medium mb-4">Roo Code Cloud Authentication</h3>
128-
129-
{authState.isAuthenticated ? (
130-
<div className="space-y-4">
131-
<Alert>
132-
<User className="h-4 w-4" />
133-
<AlertTitle className="flex items-center justify-between">
134-
<span>Authenticated</span>
135-
<Button
136-
variant="destructive"
137-
size="sm"
138-
onClick={handleLogout}
139-
disabled={isLoading}
140-
className="flex items-center gap-1 h-7 px-2"
141-
>
142-
<LogOut className="h-4 w-4" />
143-
{isLoading ? "Logging out..." : "Logout"}
144-
</Button>
145-
</AlertTitle>
146-
<AlertDescription>
147-
<div className="space-y-2">
148-
{authState.userInfo?.name && (
149-
<p><strong>Name:</strong> {authState.userInfo.name}</p>
150-
)}
151-
{authState.userInfo?.email && (
152-
<p><strong>Email:</strong> {authState.userInfo.email}</p>
153-
)}
154-
<p className="text-xs text-green-600 dark:text-green-400 mt-2">
155-
You are successfully authenticated with Roo Code Cloud.
156-
</p>
157-
</div>
158-
</AlertDescription>
159-
</Alert>
160-
</div>
161-
) : (
162-
<div className="space-y-4">
163-
<Alert variant="destructive">
164-
<AlertCircle className="h-4 w-4" />
165-
<AlertTitle>Not Authenticated</AlertTitle>
166-
<AlertDescription>
167-
You need to authenticate with Roo Code Cloud to use Roo Code models.
168-
Click the button below to open your browser and sign in.
169-
</AlertDescription>
170-
</Alert>
171-
172-
<Button
173-
onClick={handleLogin}
174-
disabled={isLoading}
175-
className="flex items-center gap-2"
176-
>
177-
<LogIn className="h-4 w-4" />
178-
{isLoading ? "Opening browser..." : "Authenticate with Roo Code"}
179-
</Button>
180-
181-
<p className="text-xs text-muted-foreground">
182-
This will open your default browser to authenticate with Roo Code Cloud.
183-
After authentication, you'll be redirected back to AliFullStack.
184-
</p>
185-
</div>
186-
)}
61+
<Alert variant="default">
62+
<LogIn className="h-4 w-4" />
63+
<AlertTitle>Roo Code Cloud Authentication Required</AlertTitle>
64+
<AlertDescription>
65+
To use Roo Code Cloud models, you need to authenticate with Roo Code Cloud.
66+
This provides access to premium AI models with enhanced capabilities.
67+
</AlertDescription>
68+
</Alert>
69+
70+
<div className="flex justify-center">
71+
<Button
72+
onClick={handleSignIn}
73+
disabled={isLoading}
74+
className="flex items-center gap-2"
75+
>
76+
<LogIn className="h-4 w-4" />
77+
{isLoading ? "Connecting..." : "Connect to Roo Code Cloud"}
78+
</Button>
18779
</div>
80+
81+
<p className="text-xs text-gray-500 dark:text-gray-400 text-center">
82+
Authentication is secure and only grants access to AI model services.
83+
Your data remains private and is not shared.
84+
</p>
18885
</div>
18986
);
19087
}

src/ipc/handlers/roocode_auth_handlers.ts

Lines changed: 14 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -168,23 +168,7 @@ async function handleRooCodeLogin(): Promise<void> {
168168

169169
logger.info(`Attempting to open authentication URL: ${authUrl}`);
170170

171-
// For development/demo purposes, we'll simulate successful authentication
172-
// In production, this would open the browser for real authentication
173-
if (process.env.NODE_ENV === 'development') {
174-
logger.info("Development mode: Simulating successful authentication");
175-
// Simulate authentication callback after a short delay
176-
setTimeout(() => {
177-
const mockCode = 'dev-auth-code-' + Date.now();
178-
const mockState = state;
179-
// Trigger the callback as if the user completed authentication
180-
handleRooCodeCallback(mockCode, mockState).catch(err => {
181-
logger.error("Mock authentication callback failed:", err);
182-
});
183-
}, 2000);
184-
return;
185-
}
186-
187-
// Open browser for authentication
171+
// Open browser for authentication (always use real authentication, not mock)
188172
try {
189173
await shell.openExternal(authUrl);
190174
logger.info("Opened Roo Code authentication URL in browser");
@@ -198,7 +182,7 @@ async function handleRooCodeLogin(): Promise<void> {
198182
}
199183
}
200184

201-
async function handleRooCodeCallback(code: string, state: string): Promise<void> {
185+
export async function handleRooCodeAuthCallback(code: string, state: string): Promise<void> {
202186
try {
203187
// Verify state parameter
204188
const authData = loadAuthData();
@@ -209,17 +193,7 @@ async function handleRooCodeCallback(code: string, state: string): Promise<void>
209193
}
210194

211195
// Exchange code for credentials
212-
// For development mode with mock codes, create mock credentials
213-
let credentials: AuthCredentials;
214-
if (code.startsWith('dev-auth-code-')) {
215-
logger.info("Using mock credentials for development");
216-
credentials = {
217-
clientToken: 'mock-client-token-' + Date.now(),
218-
sessionId: 'mock-session-id-' + Date.now(),
219-
};
220-
} else {
221-
credentials = await clerkSignIn(code);
222-
}
196+
credentials = await clerkSignIn(code);
223197

224198
// Save credentials
225199
saveAuthData({ credentials });
@@ -282,34 +256,18 @@ async function handleRooCodeAuthStatus(): Promise<AuthState> {
282256
}
283257

284258
// Try to get a fresh session token to verify authentication
285-
// For mock credentials, skip the API calls
286259
let userInfo: any;
287-
if (credentials.clientToken.startsWith('mock-client-token-')) {
288-
logger.info("Using mock user info for development");
289-
userInfo = {
290-
id: 'dev-user-id',
291-
first_name: 'Dev',
292-
last_name: 'User',
293-
email_addresses: [{
294-
id: 'primary-email-id',
295-
email_address: '[email protected]'
296-
}],
297-
primary_email_address_id: 'primary-email-id',
298-
image_url: 'https://via.placeholder.com/150'
299-
};
300-
} else {
301-
try {
302-
await clerkCreateSessionToken(credentials); // Verify credentials are still valid
303-
userInfo = await clerkGetUserInfo(credentials);
304-
} catch (error) {
305-
logger.warn("Failed to refresh session token, treating as unauthenticated:", error);
306-
// Clear invalid credentials
307-
const filePath = getAuthStoragePath();
308-
if (fs.existsSync(filePath)) {
309-
fs.unlinkSync(filePath);
310-
}
311-
return { isAuthenticated: false };
260+
try {
261+
await clerkCreateSessionToken(credentials); // Verify credentials are still valid
262+
userInfo = await clerkGetUserInfo(credentials);
263+
} catch (error) {
264+
logger.warn("Failed to refresh session token, treating as unauthenticated:", error);
265+
// Clear invalid credentials
266+
const filePath = getAuthStoragePath();
267+
if (fs.existsSync(filePath)) {
268+
fs.unlinkSync(filePath);
312269
}
270+
return { isAuthenticated: false };
313271
}
314272

315273
const authState: AuthState = {
@@ -346,6 +304,6 @@ export function registerRooCodeAuthHandlers(): void {
346304

347305
// Handle deep link callback for authentication
348306
ipcMain.handle("roocode:auth-callback", async (_, code: string, state: string) => {
349-
await handleRooCodeCallback(code, state);
307+
await handleRooCodeAuthCallback(code, state);
350308
});
351309
}

src/ipc/ipc_host.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { registerPortalHandlers } from "./handlers/portal_handlers";
3232
import { registerPromptHandlers } from "./handlers/prompt_handlers";
3333
import { registerHelpBotHandlers } from "./handlers/help_bot_handlers";
3434
import { registerTerminalHandlers, addTerminalOutput } from "./handlers/terminal_handlers";
35+
import { registerRooCodeAuthHandlers } from "./handlers/roocode_auth_handlers";
3536
import { AppOutput } from "./ipc_types";
3637

3738
export function registerIpcHandlers() {
@@ -69,4 +70,5 @@ export function registerIpcHandlers() {
6970
registerPromptHandlers();
7071
registerHelpBotHandlers();
7172
registerTerminalHandlers();
73+
registerRooCodeAuthHandlers();
7274
}

0 commit comments

Comments
 (0)