From c4f7de9049934836c0668901a1e59d515d1b646f Mon Sep 17 00:00:00 2001 From: Harshith Mullapudi Date: Sat, 19 Jul 2025 16:42:38 +0530 Subject: [PATCH] Fix: OAuth screen --- .../conversation/conversation-list.tsx | 38 +-- apps/webapp/app/components/icons/arrows.tsx | 19 ++ apps/webapp/app/components/icons/index.ts | 1 + .../integrations/integration-grid.tsx | 6 +- .../app/components/logs/logs-filters.tsx | 8 +- .../app/components/sidebar/nav-main.tsx | 1 - .../app/components/sidebar/nav-user.tsx | 13 +- apps/webapp/app/routes/api.oauth.clients.tsx | 43 ++- apps/webapp/app/routes/api.v1.mcp.memory.tsx | 43 ++- apps/webapp/app/routes/home.logs.activity.tsx | 2 +- apps/webapp/app/routes/home.logs.all.tsx | 2 +- apps/webapp/app/routes/login.magic.tsx | 4 +- apps/webapp/app/routes/oauth.authorize.tsx | 246 ++++++++++++------ apps/webapp/app/tailwind.css | 2 +- apps/webapp/app/trigger/chat/memory-utils.ts | 3 +- packages/core-cli/README.md | 24 +- packages/core-cli/src/commands/init.ts | 46 +--- packages/core-cli/src/commands/start.ts | 38 +-- packages/core-cli/src/commands/stop.ts | 38 +-- 19 files changed, 314 insertions(+), 263 deletions(-) create mode 100644 apps/webapp/app/components/icons/arrows.tsx diff --git a/apps/webapp/app/components/conversation/conversation-list.tsx b/apps/webapp/app/components/conversation/conversation-list.tsx index 0c81a1b..8760bd4 100644 --- a/apps/webapp/app/components/conversation/conversation-list.tsx +++ b/apps/webapp/app/components/conversation/conversation-list.tsx @@ -3,6 +3,7 @@ import { useEffect, useState, useCallback, useRef } from "react"; import { AutoSizer, List, type ListRowRenderer } from "react-virtualized"; import { cn } from "~/lib/utils"; import { Button } from "../ui"; +import { LoaderCircle } from "lucide-react"; type ConversationItem = { id: string; @@ -179,28 +180,27 @@ export const ConversationList = ({ return (
-
- - {({ height, width }) => ( - - )} - -
+ {!isLoading && conversations.length > 0 && ( +
+ + {({ height, width }) => ( + + )} + +
+ )} {isLoading && conversations.length === 0 && (
-
-
- - Loading conversations... - +
+
)} diff --git a/apps/webapp/app/components/icons/arrows.tsx b/apps/webapp/app/components/icons/arrows.tsx new file mode 100644 index 0000000..2dbe269 --- /dev/null +++ b/apps/webapp/app/components/icons/arrows.tsx @@ -0,0 +1,19 @@ +import type { IconProps } from "./types"; + +export function Arrows({ size = 18, className }: IconProps) { + return ( + + ); +} diff --git a/apps/webapp/app/components/icons/index.ts b/apps/webapp/app/components/icons/index.ts index b5a7e4c..e43963c 100644 --- a/apps/webapp/app/components/icons/index.ts +++ b/apps/webapp/app/components/icons/index.ts @@ -1,2 +1,3 @@ export * from "./slack-icon"; export * from "./linear-icon"; +export * from "./arrows"; diff --git a/apps/webapp/app/components/integrations/integration-grid.tsx b/apps/webapp/app/components/integrations/integration-grid.tsx index f196af9..92107cb 100644 --- a/apps/webapp/app/components/integrations/integration-grid.tsx +++ b/apps/webapp/app/components/integrations/integration-grid.tsx @@ -1,5 +1,5 @@ import React, { useMemo } from "react"; -import { Search } from "lucide-react"; +import { Layout, LayoutGrid, Search } from "lucide-react"; import { IntegrationCard } from "./integration-card"; interface IntegrationGridProps { @@ -25,8 +25,8 @@ export function IntegrationGrid({ if (integrations.length === 0) { return (
- -

No integrations found

+ +

No integrations found

); } diff --git a/apps/webapp/app/components/logs/logs-filters.tsx b/apps/webapp/app/components/logs/logs-filters.tsx index 5930e44..f64dd13 100644 --- a/apps/webapp/app/components/logs/logs-filters.tsx +++ b/apps/webapp/app/components/logs/logs-filters.tsx @@ -1,11 +1,5 @@ import { useState } from "react"; -import { - ChevronsUpDown, - Filter, - FilterIcon, - ListFilter, - X, -} from "lucide-react"; +import { ListFilter, X } from "lucide-react"; import { Button } from "~/components/ui/button"; import { Popover, diff --git a/apps/webapp/app/components/sidebar/nav-main.tsx b/apps/webapp/app/components/sidebar/nav-main.tsx index 6874338..18a1eb6 100644 --- a/apps/webapp/app/components/sidebar/nav-main.tsx +++ b/apps/webapp/app/components/sidebar/nav-main.tsx @@ -3,7 +3,6 @@ import { SidebarGroup, SidebarGroupContent, SidebarMenu, - SidebarMenuButton, SidebarMenuItem, } from "../ui/sidebar"; import { useLocation, useNavigate } from "@remix-run/react"; diff --git a/apps/webapp/app/components/sidebar/nav-user.tsx b/apps/webapp/app/components/sidebar/nav-user.tsx index faaf0b0..8a14d7f 100644 --- a/apps/webapp/app/components/sidebar/nav-user.tsx +++ b/apps/webapp/app/components/sidebar/nav-user.tsx @@ -8,20 +8,13 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "../ui/dropdown-menu"; -import { - SidebarMenu, - SidebarMenuButton, - SidebarMenuItem, - useSidebar, -} from "../ui/sidebar"; +import { SidebarMenu, SidebarMenuItem, useSidebar } from "../ui/sidebar"; import type { User } from "~/models/user.server"; import { Button } from "../ui"; -import { cn } from "~/lib/utils"; -import { useLocation, useNavigate } from "@remix-run/react"; +import { useNavigate } from "@remix-run/react"; export function NavUser({ user }: { user: User }) { const { isMobile } = useSidebar(); - const location = useLocation(); const navigate = useNavigate(); return ( @@ -55,7 +48,7 @@ export function NavUser({ user }: { user: User }) { navigate("/settings")} + onClick={() => navigate("/settings/api")} > Settings diff --git a/apps/webapp/app/routes/api.oauth.clients.tsx b/apps/webapp/app/routes/api.oauth.clients.tsx index 11615f5..d38151a 100644 --- a/apps/webapp/app/routes/api.oauth.clients.tsx +++ b/apps/webapp/app/routes/api.oauth.clients.tsx @@ -1,4 +1,8 @@ -import { type ActionFunctionArgs, type LoaderFunctionArgs, json } from "@remix-run/node"; +import { + type ActionFunctionArgs, + type LoaderFunctionArgs, + json, +} from "@remix-run/node"; import { PrismaClient } from "@prisma/client"; import { requireAuth } from "~/utils/auth-helper"; import crypto from "crypto"; @@ -63,12 +67,23 @@ export const action = async ({ request }: ActionFunctionArgs) => { try { const body = await request.json(); - - const { name, description, redirectUris, allowedScopes, requirePkce, logoUrl, homepageUrl } = body; + + const { + name, + description, + redirectUris, + allowedScopes, + requirePkce, + logoUrl, + homepageUrl, + } = body; // Validate required fields if (!name || !redirectUris) { - return json({ error: "Name and redirectUris are required" }, { status: 400 }); + return json( + { error: "Name and redirectUris are required" }, + { status: 400 }, + ); } // Get user's workspace @@ -83,7 +98,7 @@ export const action = async ({ request }: ActionFunctionArgs) => { // Generate client credentials const clientId = crypto.randomUUID(); - const clientSecret = crypto.randomBytes(32).toString('hex'); + const clientSecret = crypto.randomBytes(32).toString("hex"); // Create OAuth client const client = await prisma.oAuthClient.create({ @@ -92,8 +107,12 @@ export const action = async ({ request }: ActionFunctionArgs) => { clientSecret, name, description: description || null, - redirectUris: Array.isArray(redirectUris) ? redirectUris.join(',') : redirectUris, - allowedScopes: Array.isArray(allowedScopes) ? allowedScopes.join(',') : allowedScopes || "read", + redirectUris: Array.isArray(redirectUris) + ? redirectUris.join(",") + : redirectUris, + allowedScopes: Array.isArray(allowedScopes) + ? allowedScopes.join(",") + : allowedScopes || "read", requirePkce: requirePkce || false, logoUrl: logoUrl || null, homepageUrl: homepageUrl || null, @@ -116,14 +135,14 @@ export const action = async ({ request }: ActionFunctionArgs) => { }, }); - return json({ - success: true, + return json({ + success: true, client, - message: "OAuth client created successfully. Save the client_secret securely - it won't be shown again." + message: + "OAuth client created successfully. Save the client_secret securely - it won't be shown again.", }); - } catch (error) { console.error("Error creating OAuth client:", error); return json({ error: "Internal server error" }, { status: 500 }); } -}; \ No newline at end of file +}; diff --git a/apps/webapp/app/routes/api.v1.mcp.memory.tsx b/apps/webapp/app/routes/api.v1.mcp.memory.tsx index 42ad0ff..83ebb70 100644 --- a/apps/webapp/app/routes/api.v1.mcp.memory.tsx +++ b/apps/webapp/app/routes/api.v1.mcp.memory.tsx @@ -8,7 +8,6 @@ import { createHybridActionApiRoute } from "~/services/routeBuilders/apiBuilder. import { addToQueue } from "~/lib/ingest.server"; import { SearchService } from "~/services/search.server"; import { handleTransport } from "~/utils/mcp"; -import { IngestBodyRequest } from "~/trigger/ingest/ingest"; // Map to store transports by session ID with cleanup tracking const transports: { @@ -39,16 +38,14 @@ const MCPRequestSchema = z.object({}).passthrough(); // Search parameters schema for MCP tool const SearchParamsSchema = z.object({ - query: z.string(), - startTime: z.string().optional(), - endTime: z.string().optional(), - spaceId: z.string().optional(), - limit: z.number().optional(), - maxBfsDepth: z.number().optional(), - includeInvalidated: z.boolean().optional(), - entityTypes: z.array(z.string()).optional(), - scoreThreshold: z.number().optional(), - minResults: z.number().optional(), + query: z.string().describe("The search query in third person perspective"), + validAt: z.string().optional().describe("The valid at time in ISO format"), + startTime: z.string().optional().describe("The start time in ISO format"), + endTime: z.string().optional().describe("The end time in ISO format"), +}); + +const IngestSchema = z.object({ + message: z.string().describe("The data to ingest in text format"), }); const searchService = new SearchService(); @@ -60,6 +57,22 @@ const handleMCPRequest = async ( authentication: any, ) => { const sessionId = request.headers.get("mcp-session-id") as string | undefined; + const source = request.headers.get("source") as string | undefined; + + if (!source) { + return json( + { + jsonrpc: "2.0", + error: { + code: -32601, + message: "No source found", + }, + id: null, + }, + { status: 400 }, + ); + } + let transport: StreamableHTTPServerTransport; try { @@ -104,14 +117,18 @@ const handleMCPRequest = async ( { title: "Ingest Data", description: "Ingest data into the memory system", - inputSchema: IngestBodyRequest.shape, + inputSchema: IngestSchema.shape, }, async (args) => { try { const userId = authentication.userId; const response = addToQueue( - args as z.infer, + { + episodeBody: args.message, + referenceTime: new Date().toISOString(), + source, + }, userId, ); return { diff --git a/apps/webapp/app/routes/home.logs.activity.tsx b/apps/webapp/app/routes/home.logs.activity.tsx index 0570a80..c2e17e8 100644 --- a/apps/webapp/app/routes/home.logs.activity.tsx +++ b/apps/webapp/app/routes/home.logs.activity.tsx @@ -57,7 +57,7 @@ export default function LogsActivity() { }, ]} /> -
+
{isInitialLoad ? ( <> {" "} diff --git a/apps/webapp/app/routes/home.logs.all.tsx b/apps/webapp/app/routes/home.logs.all.tsx index 2797967..2074ef9 100644 --- a/apps/webapp/app/routes/home.logs.all.tsx +++ b/apps/webapp/app/routes/home.logs.all.tsx @@ -45,7 +45,7 @@ export default function LogsAll() { }, ]} /> -
+
{isInitialLoad ? ( <> {" "} diff --git a/apps/webapp/app/routes/login.magic.tsx b/apps/webapp/app/routes/login.magic.tsx index 9ec386d..1c836c0 100644 --- a/apps/webapp/app/routes/login.magic.tsx +++ b/apps/webapp/app/routes/login.magic.tsx @@ -13,7 +13,7 @@ import { CardTitle, } from "~/components/ui/card"; import { Form, useNavigation } from "@remix-run/react"; -import { Inbox, Loader, Mail } from "lucide-react"; +import { Inbox, Loader, LoaderCircle, Mail } from "lucide-react"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { z } from "zod"; import { LoginPageLayout } from "~/components/layout/login-page-layout"; @@ -203,7 +203,7 @@ export default function LoginMagicLinkPage() { data-action="send a magic link" > {isLoading ? ( - + ) : ( )} diff --git a/apps/webapp/app/routes/oauth.authorize.tsx b/apps/webapp/app/routes/oauth.authorize.tsx index 3e4447c..4a95168 100644 --- a/apps/webapp/app/routes/oauth.authorize.tsx +++ b/apps/webapp/app/routes/oauth.authorize.tsx @@ -1,14 +1,25 @@ -import { type ActionFunctionArgs, type LoaderFunctionArgs, redirect } from "@remix-run/node"; -import { Form, useLoaderData, useSearchParams } from "@remix-run/react"; +import { + type ActionFunctionArgs, + type LoaderFunctionArgs, + redirect, +} from "@remix-run/node"; +import { Form, useLoaderData } from "@remix-run/react"; import { getUser } from "~/services/session.server"; -import { oauth2Service, OAuth2Errors, type OAuth2AuthorizeRequest } from "~/services/oauth2.server"; +import { + oauth2Service, + OAuth2Errors, + type OAuth2AuthorizeRequest, +} from "~/services/oauth2.server"; import { Button } from "~/components/ui/button"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card"; +import { Card, CardContent } from "~/components/ui/card"; +import { Arrows } from "~/components/icons"; +import Logo from "~/components/logo/logo"; +import { AlignLeft, LayoutGrid, Pen } from "lucide-react"; export const loader = async ({ request }: LoaderFunctionArgs) => { // Check if user is authenticated const user = await getUser(request); - + if (!user) { // Redirect to login with return URL const url = new URL(request.url); @@ -18,33 +29,52 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { } const url = new URL(request.url); + let scopeParam = url.searchParams.get("scope") || undefined; + + // If scope is present, remove spaces after commas (e.g., "read, write" -> "read,write") + if (scopeParam) { + scopeParam = scopeParam + .split(",") + .map((s) => s.trim()) + .join(","); + } else { + throw new Error("Scope is not found"); + } + const params: OAuth2AuthorizeRequest = { client_id: url.searchParams.get("client_id") || "", redirect_uri: url.searchParams.get("redirect_uri") || "", response_type: url.searchParams.get("response_type") || "", - scope: url.searchParams.get("scope") || undefined, + scope: scopeParam, state: url.searchParams.get("state") || undefined, code_challenge: url.searchParams.get("code_challenge") || undefined, - code_challenge_method: url.searchParams.get("code_challenge_method") || undefined, + code_challenge_method: + url.searchParams.get("code_challenge_method") || undefined, }; // Validate required parameters if (!params.client_id || !params.redirect_uri || !params.response_type) { - return redirect(`${params.redirect_uri}?error=${OAuth2Errors.INVALID_REQUEST}&error_description=Missing required parameters${params.state ? `&state=${params.state}` : ""}`); + return redirect( + `${params.redirect_uri}?error=${OAuth2Errors.INVALID_REQUEST}&error_description=Missing required parameters${params.state ? `&state=${params.state}` : ""}`, + ); } // Only support authorization code flow if (params.response_type !== "code") { - return redirect(`${params.redirect_uri}?error=${OAuth2Errors.UNSUPPORTED_RESPONSE_TYPE}&error_description=Only authorization code flow is supported${params.state ? `&state=${params.state}` : ""}`); + return redirect( + `${params.redirect_uri}?error=${OAuth2Errors.UNSUPPORTED_RESPONSE_TYPE}&error_description=Only authorization code flow is supported${params.state ? `&state=${params.state}` : ""}`, + ); } try { // Validate client const client = await oauth2Service.validateClient(params.client_id); - + // Validate redirect URI if (!oauth2Service.validateRedirectUri(client, params.redirect_uri)) { - return redirect(`${params.redirect_uri}?error=${OAuth2Errors.INVALID_REQUEST}&error_description=Invalid redirect URI${params.state ? `&state=${params.state}` : ""}`); + return redirect( + `${params.redirect_uri}?error=${OAuth2Errors.INVALID_REQUEST}&error_description=Invalid redirect URI${params.state ? `&state=${params.state}` : ""}`, + ); } return { @@ -53,41 +83,48 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { params, }; } catch (error) { - return redirect(`${params.redirect_uri}?error=${OAuth2Errors.INVALID_CLIENT}&error_description=Invalid client${params.state ? `&state=${params.state}` : ""}`); + return redirect( + `${params.redirect_uri}?error=${OAuth2Errors.INVALID_CLIENT}&error_description=Invalid client${params.state ? `&state=${params.state}` : ""}`, + ); } }; export const action = async ({ request }: ActionFunctionArgs) => { const user = await getUser(request); - + if (!user) { return redirect("/login"); } const formData = await request.formData(); const action = formData.get("action"); - + const params: OAuth2AuthorizeRequest = { client_id: formData.get("client_id") as string, redirect_uri: formData.get("redirect_uri") as string, response_type: formData.get("response_type") as string, - scope: formData.get("scope") as string || undefined, - state: formData.get("state") as string || undefined, - code_challenge: formData.get("code_challenge") as string || undefined, - code_challenge_method: formData.get("code_challenge_method") as string || undefined, + scope: (formData.get("scope") as string) || undefined, + state: (formData.get("state") as string) || undefined, + code_challenge: (formData.get("code_challenge") as string) || undefined, + code_challenge_method: + (formData.get("code_challenge_method") as string) || undefined, }; if (action === "deny") { - return redirect(`${params.redirect_uri}?error=${OAuth2Errors.ACCESS_DENIED}&error_description=User denied access${params.state ? `&state=${params.state}` : ""}`); + return redirect( + `${params.redirect_uri}?error=${OAuth2Errors.ACCESS_DENIED}&error_description=User denied access${params.state ? `&state=${params.state}` : ""}`, + ); } if (action === "allow") { try { // Validate client again const client = await oauth2Service.validateClient(params.client_id); - + if (!oauth2Service.validateRedirectUri(client, params.redirect_uri)) { - return redirect(`${params.redirect_uri}?error=${OAuth2Errors.INVALID_REQUEST}&error_description=Invalid redirect URI${params.state ? `&state=${params.state}` : ""}`); + return redirect( + `${params.redirect_uri}?error=${OAuth2Errors.INVALID_REQUEST}&error_description=Invalid redirect URI${params.state ? `&state=${params.state}` : ""}`, + ); } // Create authorization code @@ -109,90 +146,129 @@ export const action = async ({ request }: ActionFunctionArgs) => { return redirect(redirectUrl.toString()); } catch (error) { - return redirect(`${params.redirect_uri}?error=${OAuth2Errors.SERVER_ERROR}&error_description=Failed to create authorization code${params.state ? `&state=${params.state}` : ""}`); + return redirect( + `${params.redirect_uri}?error=${OAuth2Errors.SERVER_ERROR}&error_description=Failed to create authorization code${params.state ? `&state=${params.state}` : ""}`, + ); } } - return redirect(`${params.redirect_uri}?error=${OAuth2Errors.INVALID_REQUEST}&error_description=Invalid action${params.state ? `&state=${params.state}` : ""}`); + return redirect( + `${params.redirect_uri}?error=${OAuth2Errors.INVALID_REQUEST}&error_description=Invalid action${params.state ? `&state=${params.state}` : ""}`, + ); }; export default function OAuthAuthorize() { const { user, client, params } = useLoaderData(); - const [searchParams] = useSearchParams(); + + const getIcon = (scope: string) => { + if (scope === "read") { + return ; + } + + return ; + }; return ( -
- - - Authorize Application - - {client.name} wants to access your Echo account - - +
+ -
-
- {client.logoUrl && ( - {client.name} - )} +
+ {client.logoUrl ? ( + {client.name} + ) : ( + + )} + + +
+
+
-

{client.name}

- {client.description && ( -

{client.description}

- )} +

+ {client.name} is requesting access +

+

+ Authenticating with your {user.name} workspace +

-
-

This application will be able to:

-
    - {params.scope ? ( - params.scope.split(' ').map((scope, index) => ( -
  • • {scope === 'read' ? 'Read your profile information' : scope}
  • - )) - ) : ( -
  • • Read your profile information
  • - )} -
-
- -
-

- Signed in as: {user.email} -

-
+

Permissions

+
    + {params.scope?.split(",").map((scope, index, arr) => { + const isFirst = index === 0; + const isLast = index === arr.length - 1; + return ( +
  • +
    {getIcon(scope)}
    +
    + {scope.charAt(0).toUpperCase() + scope.slice(1)} access to + your workspace +
    +
  • + ); + })} +
- - - {params.scope && } - {params.state && } - {params.code_challenge && } - {params.code_challenge_method && } - -
- - +
@@ -200,4 +276,4 @@ export default function OAuthAuthorize() {
); -} \ No newline at end of file +} diff --git a/apps/webapp/app/tailwind.css b/apps/webapp/app/tailwind.css index 85ebd48..d14eb46 100644 --- a/apps/webapp/app/tailwind.css +++ b/apps/webapp/app/tailwind.css @@ -305,7 +305,7 @@ --radius-full: 9999px; --shadow: 0px 6px 20px 0px rgba(0, 0, 0, 0.15), 0px 0px 2px 0px rgba(0, 0, 0, 0.2); - --shadow-1: 0px 6px 20px 0px rgba(0, 0, 0, 0.15), 0px 0px 2px 0px rgba(0, 0, 0, 0.2); + --shadow-1: lch(0 0 0 / 0.022) 0px 3px 6px -2px, lch(0 0 0 / 0.044) 0px 1px 1px; --font-sans: "Geist Variable", "Helvetica Neue", "Helvetica", "Arial", sans-serif; --font-mono: "Geist Mono Variable", monaco, Consolas, "Lucida Console", monospace; diff --git a/apps/webapp/app/trigger/chat/memory-utils.ts b/apps/webapp/app/trigger/chat/memory-utils.ts index afb2791..613cf21 100644 --- a/apps/webapp/app/trigger/chat/memory-utils.ts +++ b/apps/webapp/app/trigger/chat/memory-utils.ts @@ -12,7 +12,6 @@ export interface SearchMemoryParams { export interface AddMemoryParams { message: string; referenceTime?: string; - source?: string; spaceId?: string; sessionId?: string; metadata?: any; @@ -38,7 +37,7 @@ export const addMemory = async (params: AddMemoryParams) => { ...params, episodeBody: params.message, referenceTime: params.referenceTime || new Date().toISOString(), - source: params.source || "CORE", + source: "CORE", }; const response = await axios.post( diff --git a/packages/core-cli/README.md b/packages/core-cli/README.md index 21451fd..9d6d85d 100644 --- a/packages/core-cli/README.md +++ b/packages/core-cli/README.md @@ -35,19 +35,23 @@ npm install -g @redplanethq/core ### Initial Setup -1. **Run the initialization command:** +1. **Clone the Core repository:** + ```bash + git clone https://github.com/redplanethq/core.git + cd core + ``` +2. **Run the initialization command:** ```bash core init ``` -2. **The CLI will guide you through the complete setup process:** +3. **The CLI will guide you through the complete setup process:** -#### Step 1: Repository Validation - -- The CLI checks if you're in the Core repository -- If not, it offers to clone the repository for you -- Choose **Yes** to clone automatically, or **No** to clone manually +#### Step 1: Prerequisites Check +- The CLI shows a checklist of required tools +- Confirms you're in the Core repository directory +- Exits with instructions if prerequisites aren't met #### Step 2: Environment Configuration @@ -130,9 +134,9 @@ After setup, these services will be available: If you run commands outside the Core repository: -- The CLI will offer to clone the repository automatically -- Choose **Yes** to clone in the current directory -- Or navigate to the Core repository manually +- The CLI will ask you to confirm you're in the Core repository +- If not, it provides instructions to clone the repository +- Navigate to the Core repository directory before running commands again ### Docker Issues diff --git a/packages/core-cli/src/commands/init.ts b/packages/core-cli/src/commands/init.ts index 5c790b3..87b34e8 100644 --- a/packages/core-cli/src/commands/init.ts +++ b/packages/core-cli/src/commands/init.ts @@ -1,5 +1,4 @@ import { intro, outro, text, confirm, spinner, note, log } from "@clack/prompts"; -import { isValidCoreRepo } from "../utils/git.js"; import { fileExists, updateEnvFile } from "../utils/file.js"; import { checkPostgresHealth } from "../utils/docker.js"; import { executeDockerCommandInteractive } from "../utils/docker-interactive.js"; @@ -14,42 +13,17 @@ export async function initCommand() { intro("šŸš€ Core Development Environment Setup"); - // Step 1: Validate repository - if (!isValidCoreRepo()) { - log.warning("This directory is not a Core repository"); - note( - "The Core repository is required to run the development environment.\nWould you like to clone it in the current directory?", - "šŸ” Repository Not Found" - ); + // Step 1: Confirm this is the Core repository + note("Please ensure you have:\n• Docker and Docker Compose installed\n• Git installed\n• pnpm package manager installed\n• You are in the Core repository directory", "šŸ“‹ Prerequisites"); + + const isCoreRepo = await confirm({ + message: "Are you currently in the Core repository directory?", + }); - const shouldClone = await confirm({ - message: "Clone the Core repository here?", - }); - - if (!shouldClone) { - outro("āŒ Setup cancelled. Please navigate to the Core repository or clone it first."); - process.exit(1); - } - - // Clone the repository - try { - await executeDockerCommandInteractive("git clone https://github.com/redplanethq/core.git .", { - cwd: process.cwd(), - message: "Cloning Core repository...", - showOutput: true, - }); - - log.success("Core repository cloned successfully!"); - note( - 'Please run "core init" again to initialize the development environment.', - "āœ… Repository Ready" - ); - outro("šŸŽ‰ Core repository is now available!"); - process.exit(0); - } catch (error: any) { - outro(`āŒ Failed to clone repository: ${error.message}`); - process.exit(1); - } + if (!isCoreRepo) { + note("Please clone the Core repository first:\n\ngit clone https://github.com/redplanethq/core.git\ncd core\n\nThen run 'core init' again.", "šŸ“„ Clone Repository"); + outro("āŒ Setup cancelled. Please navigate to the Core repository first."); + process.exit(1); } const rootDir = process.cwd(); diff --git a/packages/core-cli/src/commands/start.ts b/packages/core-cli/src/commands/start.ts index f1378d8..2d061ae 100644 --- a/packages/core-cli/src/commands/start.ts +++ b/packages/core-cli/src/commands/start.ts @@ -1,5 +1,4 @@ import { intro, outro, note, log, confirm } from '@clack/prompts'; -import { isValidCoreRepo } from '../utils/git.js'; import { executeDockerCommandInteractive } from '../utils/docker-interactive.js'; import { printCoreBrainLogo } from '../utils/ascii.js'; import path from 'path'; @@ -10,36 +9,15 @@ export async function startCommand() { intro('šŸš€ Starting Core Development Environment'); - // Step 1: Validate repository - if (!isValidCoreRepo()) { - log.warning('This directory is not a Core repository'); - note('The Core repository is required to run the development environment.\nWould you like to clone it in the current directory?', 'šŸ” Repository Not Found'); - - const shouldClone = await confirm({ - message: 'Clone the Core repository here?', - }); + // Step 1: Confirm this is the Core repository + const isCoreRepo = await confirm({ + message: 'Are you currently in the Core repository directory?', + }); - if (!shouldClone) { - outro('āŒ Setup cancelled. Please navigate to the Core repository or clone it first.'); - process.exit(1); - } - - // Clone the repository - try { - await executeDockerCommandInteractive('git clone https://github.com/redplanethq/core.git .', { - cwd: process.cwd(), - message: 'Cloning Core repository...', - showOutput: true - }); - - log.success('Core repository cloned successfully!'); - note('You can now run "core start" to start the development environment.', 'āœ… Repository Ready'); - outro('šŸŽ‰ Core repository is now available!'); - process.exit(0); - } catch (error: any) { - outro(`āŒ Failed to clone repository: ${error.message}`); - process.exit(1); - } + if (!isCoreRepo) { + note('Please navigate to the Core repository first:\n\ngit clone https://github.com/redplanethq/core.git\ncd core\n\nThen run "core start" again.', 'šŸ“„ Core Repository Required'); + outro('āŒ Please navigate to the Core repository first.'); + process.exit(1); } const rootDir = process.cwd(); diff --git a/packages/core-cli/src/commands/stop.ts b/packages/core-cli/src/commands/stop.ts index 5fef1d1..aa1b2a6 100644 --- a/packages/core-cli/src/commands/stop.ts +++ b/packages/core-cli/src/commands/stop.ts @@ -1,5 +1,4 @@ import { intro, outro, log, confirm, note } from '@clack/prompts'; -import { isValidCoreRepo } from '../utils/git.js'; import { executeDockerCommandInteractive } from '../utils/docker-interactive.js'; import { printCoreBrainLogo } from '../utils/ascii.js'; import path from 'path'; @@ -10,36 +9,15 @@ export async function stopCommand() { intro('šŸ›‘ Stopping Core Development Environment'); - // Step 1: Validate repository - if (!isValidCoreRepo()) { - log.warning('This directory is not a Core repository'); - note('The Core repository is required to stop the development environment.\nWould you like to clone it in the current directory?', 'šŸ” Repository Not Found'); - - const shouldClone = await confirm({ - message: 'Clone the Core repository here?', - }); + // Step 1: Confirm this is the Core repository + const isCoreRepo = await confirm({ + message: 'Are you currently in the Core repository directory?', + }); - if (!shouldClone) { - outro('āŒ Setup cancelled. Please navigate to the Core repository or clone it first.'); - process.exit(1); - } - - // Clone the repository - try { - await executeDockerCommandInteractive('git clone https://github.com/redplanethq/core.git .', { - cwd: process.cwd(), - message: 'Cloning Core repository...', - showOutput: true - }); - - log.success('Core repository cloned successfully!'); - note('You can now run "core stop" to stop the development environment.', 'āœ… Repository Ready'); - outro('šŸŽ‰ Core repository is now available!'); - process.exit(0); - } catch (error: any) { - outro(`āŒ Failed to clone repository: ${error.message}`); - process.exit(1); - } + if (!isCoreRepo) { + note('Please navigate to the Core repository first:\n\ngit clone https://github.com/redplanethq/core.git\ncd core\n\nThen run "core stop" again.', 'šŸ“„ Core Repository Required'); + outro('āŒ Please navigate to the Core repository first.'); + process.exit(1); } const rootDir = process.cwd();