├── worker ├── agents │ ├── services │ │ ├── interfaces │ │ │ ├── IAnalysisManager.ts │ │ │ ├── IServiceOptions.ts │ │ │ ├── IStateManager.ts │ │ │ └── IFileManager.ts │ │ └── implementations │ │ │ └── StateManager.ts │ ├── git │ │ └── index.ts │ ├── utils │ │ ├── idGenerator.ts │ │ ├── operationError.ts │ │ └── codeSerializers.ts │ ├── output-formats │ │ └── diff-formats │ │ │ └── index.ts │ ├── assistants │ │ └── assistant.ts │ ├── tools │ │ ├── types.ts │ │ └── toolkit │ │ │ ├── run-analysis.ts │ │ │ ├── queue-request.ts │ │ │ ├── wait-for-debug.ts │ │ │ ├── wait-for-generation.ts │ │ │ ├── wait.ts │ │ │ ├── rename-project.ts │ │ │ ├── regenerate-file.ts │ │ │ ├── deploy-preview.ts │ │ │ ├── read-files.ts │ │ │ └── exec-commands.ts │ ├── core │ │ ├── smartGeneratorAgent.ts │ │ ├── state.ts │ │ └── types.ts │ ├── domain │ │ └── pure │ │ │ └── DependencyManagement.ts │ └── operations │ │ └── common.ts ├── services │ ├── github │ │ └── index.ts │ ├── rate-limit │ │ └── errors.ts │ ├── sandbox │ │ ├── factory.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── cache │ │ ├── KVCache.ts │ │ └── wrapper.ts │ └── oauth │ │ └── github-exporter.ts ├── api │ ├── controllers │ │ ├── status │ │ │ ├── types.ts │ │ │ └── controller.ts │ │ ├── stats │ │ │ └── types.ts │ │ ├── user │ │ │ └── types.ts │ │ ├── analytics │ │ │ └── types.ts │ │ ├── types.ts │ │ ├── agent │ │ │ └── types.ts │ │ ├── secrets │ │ │ └── types.ts │ │ ├── appView │ │ │ └── types.ts │ │ ├── apps │ │ │ └── types.ts │ │ ├── modelProviders │ │ │ └── types.ts │ │ └── modelConfig │ │ │ └── types.ts │ ├── routes │ │ ├── statusRoutes.ts │ │ ├── sentryRoutes.ts │ │ ├── statsRoutes.ts │ │ ├── userRoutes.ts │ │ ├── analyticsRoutes.ts │ │ ├── imagesRoutes.ts │ │ ├── githubExporterRoutes.ts │ │ ├── modelProviderRoutes.ts │ │ ├── secretsRoutes.ts │ │ ├── codegenRoutes.ts │ │ ├── index.ts │ │ └── modelConfigRoutes.ts │ ├── honoAdapter.ts │ ├── types │ │ └── route-context.ts │ └── responses.ts ├── utils │ ├── envs.ts │ ├── idGenerator.ts │ ├── deployToCf.ts │ ├── dispatcherUtils.ts │ ├── timeFormatter.ts │ ├── urls.ts │ └── githubUtils.ts ├── types │ ├── appenv.ts │ └── image-attachment.ts ├── database │ ├── index.ts │ └── services │ │ └── BaseService.ts ├── middleware │ ├── security │ │ └── websocket.ts │ └── auth │ │ └── auth.ts └── logger │ └── types.ts ├── migrations ├── 0001_married_moondragon.sql ├── 0003_bumpy_albert_cleary.sql ├── meta │ └── _journal.json └── 0002_nebulous_fantastic_four.sql ├── public ├── favicon.ico └── vite.svg ├── src ├── vite-env.d.ts ├── assets │ ├── fonts │ │ └── DepartureMono-Regular.woff │ └── provider-logos │ │ ├── anthropic.svg │ │ ├── google.svg │ │ ├── cloudflare.svg │ │ └── openai.svg ├── components │ ├── ui │ │ ├── aspect-ratio.tsx │ │ ├── skeleton.tsx │ │ ├── sonner.tsx │ │ ├── label.tsx │ │ ├── separator.tsx │ │ ├── textarea.tsx │ │ ├── progress.tsx │ │ ├── collapsible.tsx │ │ ├── input.tsx │ │ ├── switch.tsx │ │ ├── avatar.tsx │ │ ├── checkbox.tsx │ │ ├── radio-group.tsx │ │ ├── hover-card.tsx │ │ ├── toggle.tsx │ │ ├── badge.tsx │ │ ├── popover.tsx │ │ ├── alert.tsx │ │ ├── scroll-area.tsx │ │ ├── tooltip.tsx │ │ ├── card.tsx │ │ ├── tabs.tsx │ │ ├── resizable.tsx │ │ ├── toggle-group.tsx │ │ └── slider.tsx │ ├── monaco-editor │ │ └── monaco-editor.module.css │ ├── primitives │ │ └── button.tsx │ ├── header.tsx │ ├── agent-mode-display.tsx │ ├── layout │ │ └── app-layout.tsx │ ├── theme-toggle.tsx │ ├── shared │ │ ├── TimePeriodSelector.tsx │ │ └── AppFiltersForm.tsx │ ├── image-upload-button.tsx │ ├── analytics │ │ └── cost-display.tsx │ ├── image-attachment-preview.tsx │ └── ErrorBoundary.tsx ├── utils │ ├── id-generator.ts │ ├── string.ts │ └── logger.ts ├── hooks │ ├── use-mobile.ts │ ├── useSentryUser.ts │ ├── use-app.ts │ ├── use-infinite-scroll.ts │ ├── use-platform-status.ts │ ├── use-auto-scroll.ts │ └── use-stats.ts ├── lib │ ├── utils.ts │ └── app-events.ts ├── routes │ ├── chat │ │ ├── utils │ │ │ ├── websocket-helpers.ts │ │ │ └── project-stage-helpers.ts │ │ └── components │ │ │ └── copy.tsx │ └── protected-route.tsx ├── main.tsx ├── App.tsx ├── routes.ts └── contexts │ └── theme-context.tsx ├── .editorconfig ├── .husky ├── pre-commit └── commit-msg ├── tsconfig.json ├── tsconfig.worker.json ├── components.json ├── container ├── package.json ├── bun-sqlite.d.ts └── example-usage.sh ├── vitest.config.ts ├── .github ├── workflows │ ├── ci.yml │ └── pr-labeler.yml └── labeler.yml ├── index.html ├── .gitignore ├── tsconfig.node.json ├── drizzle.config.local.ts ├── drizzle.config.remote.ts ├── tsconfig.app.json ├── LICENSE ├── commitlint.config.js ├── .dev.vars.example ├── eslint.config.js ├── shared └── types │ └── errors.ts ├── vite.config.ts ├── knip.json └── docs └── v1dev-environment.postman_environment.json /worker/agents/services/interfaces/IAnalysisManager.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /migrations/0001_married_moondragon.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `apps` DROP COLUMN `blueprint`; -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/vibesdk/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /src/assets/fonts/DepartureMono-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/vibesdk/HEAD/src/assets/fonts/DepartureMono-Regular.woff -------------------------------------------------------------------------------- /worker/services/github/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * GitHub Service Module Exports 3 | */ 4 | 5 | export { GitHubService } from './GitHubService'; 6 | export * from './types'; -------------------------------------------------------------------------------- /worker/api/controllers/status/types.ts: -------------------------------------------------------------------------------- 1 | export interface PlatformStatusData { 2 | globalUserMessage: string; 3 | changeLogs: string; 4 | hasActiveMessage: boolean; 5 | } 6 | -------------------------------------------------------------------------------- /migrations/0003_bumpy_albert_cleary.sql: -------------------------------------------------------------------------------- 1 | CREATE INDEX `app_views_app_viewed_at_idx` ON `app_views` (`app_id`,`viewed_at`);--> statement-breakpoint 2 | CREATE INDEX `stars_app_starred_at_idx` ON `stars` (`app_id`,`starred_at`); -------------------------------------------------------------------------------- /worker/services/rate-limit/errors.ts: -------------------------------------------------------------------------------- 1 | import { RateLimitType } from "./config"; 2 | export interface RateLimitError { 3 | message: string; 4 | limitType: RateLimitType; 5 | limit?: number; 6 | period?: number; // seconds 7 | suggestions?: string[]; 8 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = tab 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 4 -------------------------------------------------------------------------------- /worker/utils/envs.ts: -------------------------------------------------------------------------------- 1 | export function isProd(env: Env) { 2 | return env.ENVIRONMENT === 'prod' || env.ENVIRONMENT === 'production'; 3 | } 4 | 5 | export function isDev(env: Env) { 6 | return env.ENVIRONMENT === 'dev' || env.ENVIRONMENT === 'development' || env.ENVIRONMENT === 'local'; 7 | } 8 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | # Add common bun installation paths to PATH 2 | export PATH="/opt/homebrew/bin:$HOME/.bun/bin:$PATH" 3 | 4 | # Run tests with bun 5 | if command -v bun >/dev/null 2>&1; then 6 | bun test 7 | else 8 | echo "⚠️ bun not found in PATH, skipping tests" 9 | exit 0 10 | fi 11 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | # Add common bun installation paths to PATH 2 | export PATH="/opt/homebrew/bin:$HOME/.bun/bin:$PATH" 3 | 4 | # Run commitlint if bunx is available 5 | if command -v bunx >/dev/null 2>&1; then 6 | bunx commitlint --edit $1 7 | else 8 | echo "⚠️ bunx not found in PATH, skipping commitlint" 9 | exit 0 10 | fi 11 | -------------------------------------------------------------------------------- /src/components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" 2 | 3 | function AspectRatio({ 4 | ...props 5 | }: React.ComponentProps) { 6 | return 7 | } 8 | 9 | export { AspectRatio } 10 | -------------------------------------------------------------------------------- /src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ className, ...props }: React.ComponentProps<"div">) { 4 | return ( 5 | 10 | ) 11 | } 12 | 13 | export { Skeleton } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.app.json" 6 | }, 7 | { 8 | "path": "./tsconfig.node.json" 9 | }, 10 | { 11 | "path": "./tsconfig.worker.json" 12 | } 13 | ], 14 | "compilerOptions": { 15 | "composite": true, 16 | "types": [ 17 | "./worker-configuration.d.ts" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /worker/utils/idGenerator.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ID Generation Utility 3 | * Simple wrapper around crypto.randomUUID() for consistent ID generation 4 | */ 5 | 6 | import { nanoid } from "nanoid"; 7 | 8 | export function generateId(): string { 9 | return crypto.randomUUID(); 10 | } 11 | 12 | export function generateNanoId(): string { 13 | return nanoid(); 14 | } -------------------------------------------------------------------------------- /src/utils/id-generator.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ID Generation Utility for Frontend 3 | * Simple wrapper around crypto.randomUUID() for consistent ID generation 4 | */ 5 | 6 | export function generateId(): string { 7 | return crypto.randomUUID(); 8 | } 9 | 10 | export function generateShortId(): string { 11 | return crypto.randomUUID().replace(/-/g, '').substring(0, 16); 12 | } -------------------------------------------------------------------------------- /tsconfig.worker.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.node.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.worker.tsbuildinfo", 6 | "types": ["@cloudflare/workers-types", "./worker-configuration.d.ts", "vite/client", "jest"], 7 | "lib": ["ES2023"] 8 | }, 9 | "include": ["./worker-configuration.d.ts", "./shared", "./worker", "./worker/types"] 10 | } 11 | -------------------------------------------------------------------------------- /worker/agents/git/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Git version control for Durable Objects 3 | */ 4 | 5 | export { GitVersionControl } from './git'; 6 | export { GitCloneService } from './git-clone-service'; 7 | export { MemFS } from './memfs'; 8 | export { SqliteFS } from './fs-adapter'; 9 | export type { CommitInfo } from './git'; 10 | export type { SqlExecutor } from './fs-adapter'; 11 | export type { RepositoryBuildOptions } from './git-clone-service'; -------------------------------------------------------------------------------- /worker/api/controllers/stats/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Type definitions for Stats Controller responses 3 | */ 4 | 5 | import { UserStats, UserActivity } from '../../../database/types'; 6 | 7 | /** 8 | * Response data for getUserStats - uses UserStats directly 9 | */ 10 | export type UserStatsData = UserStats; 11 | 12 | /** 13 | * Response data for getUserActivity 14 | */ 15 | export interface UserActivityData { 16 | activities: UserActivity[]; 17 | } -------------------------------------------------------------------------------- /worker/types/appenv.ts: -------------------------------------------------------------------------------- 1 | import { GlobalConfigurableSettings } from "../config"; 2 | import { AuthRequirement } from "../middleware/auth/routeAuth"; 3 | import { AuthUser } from "./auth-types"; 4 | 5 | 6 | export type AppEnv = { 7 | Bindings: Env; 8 | Variables: { 9 | user: AuthUser | null; 10 | sessionId: string | null; 11 | config: GlobalConfigurableSettings; 12 | authLevel: AuthRequirement; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /worker/agents/services/interfaces/IServiceOptions.ts: -------------------------------------------------------------------------------- 1 | import { IStateManager } from './IStateManager'; 2 | import { IFileManager } from './IFileManager'; 3 | import { StructuredLogger } from '../../../logger'; 4 | 5 | /** 6 | * Common options for all agent services 7 | */ 8 | export interface ServiceOptions { 9 | env: Env, 10 | stateManager: IStateManager; 11 | fileManager: IFileManager; 12 | getLogger: () => StructuredLogger; 13 | } 14 | -------------------------------------------------------------------------------- /worker/api/controllers/user/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Type definitions for User Controller responses 3 | */ 4 | 5 | import { EnhancedAppData, PaginationInfo } from '../../../database/types'; 6 | 7 | /** 8 | * Response data for getApps 9 | */ 10 | export interface UserAppsData { 11 | apps: EnhancedAppData[]; 12 | pagination: PaginationInfo; 13 | } 14 | 15 | /** 16 | * Response data for updateProfile 17 | */ 18 | export interface ProfileUpdateData { 19 | success: boolean; 20 | message: string; 21 | } -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/index.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /src/components/monaco-editor/monaco-editor.module.css: -------------------------------------------------------------------------------- 1 | :global(.diffDelete) { 2 | background-color: rgba(255, 0, 0, 0.2); 3 | text-decoration: line-through; 4 | opacity: 0.7; 5 | } 6 | 7 | :global(.dark .diffDelete) { 8 | background-color: rgba(255, 100, 100, 0.15); 9 | } 10 | 11 | :global(.diffInsert) { 12 | background-color: rgba(0, 255, 0, 0.2); 13 | margin-left: 4px; 14 | padding: 1px 4px; 15 | border-radius: 2px; 16 | } 17 | 18 | :global(.dark .diffInsert) { 19 | background-color: rgba(100, 255, 100, 0.15); 20 | } 21 | -------------------------------------------------------------------------------- /worker/api/controllers/analytics/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Analytics Controller Types 3 | * Type definitions for analytics controller requests and responses 4 | */ 5 | 6 | import { 7 | UserAnalyticsData, 8 | ChatAnalyticsData, 9 | } from '../../../services/analytics/types'; 10 | 11 | /** 12 | * User analytics response data 13 | */ 14 | export interface UserAnalyticsResponseData extends UserAnalyticsData {} 15 | 16 | /** 17 | * Agent analytics response data 18 | */ 19 | export interface AgentAnalyticsResponseData extends ChatAnalyticsData {} 20 | -------------------------------------------------------------------------------- /worker/utils/deployToCf.ts: -------------------------------------------------------------------------------- 1 | export function prepareCloudflareButton(repositoryUrl: string, format: 'markdown' | 'html' | 'url'): string { 2 | const url = `https://deploy.workers.cloudflare.com/?url=${repositoryUrl}`; 3 | if (format === 'markdown') { 4 | return `[](${url})`; 5 | } else if (format === 'html') { 6 | return ``; 7 | } else { 8 | return url; 9 | } 10 | } -------------------------------------------------------------------------------- /src/assets/provider-logos/anthropic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /worker/api/controllers/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Base Controller Types 3 | */ 4 | 5 | import { BaseApiResponse } from "../responses"; 6 | 7 | 8 | /** 9 | * Typed response wrapper for controller methods 10 | * Ensures controller responses match their expected interface types 11 | */ 12 | export type ControllerResponse = Response & { 13 | __typedData: T; // Phantom type for compile-time checking 14 | }; 15 | 16 | /** 17 | * Type-safe API response interface that ensures data is properly typed 18 | */ 19 | export interface ApiResponse extends BaseApiResponse {} -------------------------------------------------------------------------------- /src/assets/provider-logos/google.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /worker/api/routes/statusRoutes.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from 'hono'; 2 | import { StatusController } from '../controllers/status/controller'; 3 | import { adaptController } from '../honoAdapter'; 4 | import { AppEnv } from '../../types/appenv'; 5 | import { AuthConfig, setAuthLevel } from '../../middleware/auth/routeAuth'; 6 | 7 | export function setupStatusRoutes(app: Hono): void { 8 | const statusRouter = new Hono(); 9 | 10 | statusRouter.get('/', setAuthLevel(AuthConfig.public), adaptController(StatusController, StatusController.getPlatformStatus)); 11 | 12 | app.route('/api/status', statusRouter); 13 | } 14 | -------------------------------------------------------------------------------- /worker/agents/utils/idGenerator.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility functions for generating unique IDs 3 | */ 4 | export class IdGenerator { 5 | /** 6 | * Generate a unique conversation ID 7 | * Format: conv-{timestamp}-{random} 8 | */ 9 | static generateConversationId(): string { 10 | return `conv-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`; 11 | } 12 | 13 | /** 14 | * Generate a generic unique ID with custom prefix 15 | */ 16 | static generateId(prefix: string = 'id'): string { 17 | return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`; 18 | } 19 | } -------------------------------------------------------------------------------- /worker/utils/dispatcherUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Dispatcher Utility Functions 3 | * 4 | * Shared utilities for checking dispatch namespace availability and handling 5 | * Workers for Platforms functionality across the application. 6 | */ 7 | 8 | export function isDispatcherAvailable(env: Env): boolean { 9 | // Check if DISPATCHER binding exists in the environment 10 | // This will be false if dispatch_namespaces is commented out in wrangler.jsonc 11 | // or if Workers for Platforms is not enabled for the account (as binding would be removed by the deploy.ts script) 12 | return 'DISPATCHER' in env && env.DISPATCHER != null; 13 | } -------------------------------------------------------------------------------- /container/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "process-monitoring-system", 3 | "version": "2.0.0", 4 | "description": "Unified process monitoring system for Cloudflare sandbox containers", 5 | "main": "cli-tools.ts", 6 | "scripts": { 7 | "build": "bun build cli-tools.ts --outdir ./dist --target bun", 8 | "start": "bun run cli-tools.ts process start", 9 | "monitor": "bun run cli-tools.ts", 10 | "errors": "bun run cli-tools.ts errors", 11 | "logs": "bun run cli-tools.ts logs", 12 | "test": "bun test", 13 | "clean": "rm -f ./data/*.db" 14 | }, 15 | "dependencies": { 16 | "zod": "^3.22.4" 17 | }, 18 | "type": "module" 19 | } -------------------------------------------------------------------------------- /src/hooks/use-mobile.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | const MOBILE_BREAKPOINT = 768 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState(undefined) 7 | 8 | React.useEffect(() => { 9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) 10 | const onChange = () => { 11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 12 | } 13 | mql.addEventListener("change", onChange) 14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 15 | return () => mql.removeEventListener("change", onChange) 16 | }, []) 17 | 18 | return !!isMobile 19 | } 20 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config'; 2 | 3 | export default defineWorkersConfig({ 4 | test: { 5 | poolOptions: { 6 | workers: { 7 | wrangler: { configPath: './wrangler.test.jsonc' }, 8 | miniflare: { 9 | compatibilityDate: '2024-12-12', 10 | compatibilityFlags: ['nodejs_compat'], 11 | }, 12 | }, 13 | }, 14 | globals: true, 15 | setupFiles: ['./test/setup.ts'], 16 | include: ['**/*.{test,spec}.{js,ts,jsx,tsx}'], 17 | exclude: ['**/node_modules/**', '**/dist/**', '**/.git/**', '**/test/**', '**/worker/api/routes/**'], 18 | }, 19 | }); -------------------------------------------------------------------------------- /src/components/primitives/button.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | 3 | type ButtonProps = React.ComponentProps<'button'> & { 4 | variant?: 'primary' | 'secondary'; 5 | }; 6 | 7 | export function Button({ 8 | variant = 'secondary', 9 | children, 10 | className, 11 | ...props 12 | }: ButtonProps) { 13 | return ( 14 | 23 | {children} 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from "next-themes" 2 | import { Toaster as Sonner, type ToasterProps } from "sonner" 3 | 4 | const Toaster = ({ ...props }: ToasterProps) => { 5 | const { theme = "system" } = useTheme() 6 | 7 | return ( 8 | 20 | ) 21 | } 22 | 23 | export { Toaster } 24 | -------------------------------------------------------------------------------- /worker/api/controllers/agent/types.ts: -------------------------------------------------------------------------------- 1 | import { PreviewType } from "../../../services/sandbox/sandboxTypes"; 2 | import type { ImageAttachment } from '../../../types/image-attachment'; 3 | 4 | export interface CodeGenArgs { 5 | query: string; 6 | language?: string; 7 | frameworks?: string[]; 8 | selectedTemplate?: string; 9 | agentMode: 'deterministic' | 'smart'; 10 | images?: ImageAttachment[]; 11 | } 12 | 13 | /** 14 | * Data structure for connectToExistingAgent response 15 | */ 16 | export interface AgentConnectionData { 17 | websocketUrl: string; 18 | agentId: string; 19 | } 20 | 21 | export interface AgentPreviewResponse extends PreviewType { 22 | } 23 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ "**" ] # Run on all branches 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup Bun 18 | uses: oven-sh/setup-bun@v1 19 | with: 20 | bun-version: latest 21 | 22 | - name: Install dependencies 23 | run: bun install --frozen-lockfile 24 | 25 | - name: Run build 26 | run: bun run build 27 | 28 | # - name: Run linter 29 | # run: bun run lint 30 | 31 | # - name: Run tests 32 | # run: bun run test 33 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as LabelPrimitive from "@radix-ui/react-label" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | function Label({ 7 | className, 8 | ...props 9 | }: React.ComponentProps) { 10 | return ( 11 | 19 | ) 20 | } 21 | 22 | export { Label } 23 | -------------------------------------------------------------------------------- /worker/services/sandbox/factory.ts: -------------------------------------------------------------------------------- 1 | import { SandboxSdkClient } from "./sandboxSdkClient"; 2 | import { RemoteSandboxServiceClient } from "./remoteSandboxService"; 3 | import { BaseSandboxService } from "./BaseSandboxService"; 4 | import { env } from 'cloudflare:workers' 5 | 6 | export function getSandboxService(sessionId: string, agentId: string): BaseSandboxService { 7 | if (env.SANDBOX_SERVICE_TYPE == 'runner') { 8 | console.log("[getSandboxService] Using runner service for sandboxing"); 9 | return new RemoteSandboxServiceClient(sessionId); 10 | } 11 | console.log("[getSandboxService] Using sandboxsdk service for sandboxing"); 12 | return new SandboxSdkClient(sessionId, agentId); 13 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Build 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | 8 | export function getPreviewUrl(previewURL?: string, tunnelURL?: string): string { 9 | // return import.meta.env.VITE_PREVIEW_MODE === 'tunnel' ? tunnelURL || previewURL || '' : previewURL || tunnelURL || ''; 10 | return previewURL || tunnelURL || ''; 11 | } 12 | 13 | export function capitalizeFirstLetter(str: string) { 14 | if (typeof str !== 'string' || str.length === 0) { 15 | return str; // Handle non-string input or empty string 16 | } 17 | return str.charAt(0).toUpperCase() + str.slice(1); 18 | } -------------------------------------------------------------------------------- /worker/database/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Database Services Export Index 3 | * Centralized exports for all database services and utilities 4 | */ 5 | 6 | // Core database service and utilities 7 | export { DatabaseService, createDatabaseService } from './database'; 8 | 9 | // Domain-specific services 10 | export { AnalyticsService } from './services/AnalyticsService'; 11 | export { BaseService } from './services/BaseService'; 12 | export { UserService } from './services/UserService'; 13 | export { AppService } from './services/AppService'; 14 | export { SecretsService } from './services/SecretsService'; 15 | export { ModelConfigService } from './services/ModelConfigService'; 16 | export { ModelTestService } from './services/ModelTestService'; -------------------------------------------------------------------------------- /worker/agents/services/interfaces/IStateManager.ts: -------------------------------------------------------------------------------- 1 | import { CodeGenState } from '../../core/state'; 2 | 3 | /** 4 | * Interface for state management 5 | * Abstracts state persistence and updates 6 | */ 7 | export interface IStateManager { 8 | /** 9 | * Get current state 10 | */ 11 | getState(): Readonly; 12 | 13 | /** 14 | * Update state immutably 15 | */ 16 | setState(newState: CodeGenState): void; 17 | 18 | /** 19 | * Update specific field 20 | */ 21 | updateField(field: K, value: CodeGenState[K]): void; 22 | 23 | /** 24 | * Batch update multiple fields 25 | */ 26 | batchUpdate(updates: Partial): void; 27 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log* 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | .tmp 14 | *.local 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | 27 | # wrangler files 28 | .wrangler 29 | .dev.vars* 30 | .prod.vars 31 | .env 32 | .env.* 33 | 34 | runner/downloads 35 | /generated/prisma 36 | 37 | /generated/prisma 38 | test.ts 39 | temp/ 40 | 41 | templates 42 | debugging.ipynb 43 | 44 | #bun.lock 45 | #*.lock 46 | 47 | debug-tools/*.json 48 | # Sentry Config File 49 | .env.sentry-build-plugin 50 | data-dump 51 | debug-tools/extracted -------------------------------------------------------------------------------- /container/bun-sqlite.d.ts: -------------------------------------------------------------------------------- 1 | // Type declarations for Bun's built-in SQLite module 2 | declare module 'bun:sqlite' { 3 | export interface Statement { 4 | all(...params: Params): T[]; 5 | get(...params: Params): T | null; 6 | run(...params: Params): { changes: number }; 7 | } 8 | 9 | export class Database { 10 | constructor(filename: string); 11 | 12 | query(sql: string): Statement; 13 | prepare(sql: string): Statement; 14 | exec(sql: string): void; 15 | close(): void; 16 | transaction(fn: (...args: TArgs) => TResult): (...args: TArgs) => TResult; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /worker/api/routes/sentryRoutes.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from 'hono'; 2 | import { SentryTunnelController } from '../controllers/sentry/tunnelController'; 3 | import { AppEnv } from '../../types/appenv'; 4 | import { adaptController } from '../honoAdapter'; 5 | import { AuthConfig, setAuthLevel } from '../../middleware/auth/routeAuth'; 6 | 7 | export function setupSentryRoutes(app: Hono): void { 8 | const sentryRouter = new Hono(); 9 | 10 | // Sentry tunnel endpoint for frontend events (public - no auth required) 11 | sentryRouter.post('/tunnel', setAuthLevel(AuthConfig.public), adaptController(SentryTunnelController, SentryTunnelController.tunnel)); 12 | 13 | // Mount the router under /api/sentry 14 | app.route('/api/sentry', sentryRouter); 15 | } 16 | -------------------------------------------------------------------------------- /worker/api/routes/statsRoutes.ts: -------------------------------------------------------------------------------- 1 | import { StatsController } from '../controllers/stats/controller'; 2 | import { Hono } from 'hono'; 3 | import { AppEnv } from '../../types/appenv'; 4 | import { adaptController } from '../honoAdapter'; 5 | import { AuthConfig, setAuthLevel } from '../../middleware/auth/routeAuth'; 6 | 7 | /** 8 | * Setup user statistics routes 9 | */ 10 | export function setupStatsRoutes(app: Hono): void { 11 | // User statistics 12 | app.get('/api/stats', setAuthLevel(AuthConfig.authenticated), adaptController(StatsController, StatsController.getUserStats)); 13 | 14 | // User activity timeline 15 | app.get('/api/stats/activity', setAuthLevel(AuthConfig.authenticated), adaptController(StatsController, StatsController.getUserActivity)); 16 | } -------------------------------------------------------------------------------- /worker/api/routes/userRoutes.ts: -------------------------------------------------------------------------------- 1 | import { UserController } from '../controllers/user/controller'; 2 | import { Hono } from 'hono'; 3 | import { AppEnv } from '../../types/appenv'; 4 | import { adaptController } from '../honoAdapter'; 5 | import { AuthConfig, setAuthLevel } from '../../middleware/auth/routeAuth'; 6 | 7 | /** 8 | * Setup user management routes 9 | */ 10 | export function setupUserRoutes(app: Hono): void { 11 | // User apps with pagination (this is what the frontend needs) 12 | app.get('/api/user/apps', setAuthLevel(AuthConfig.authenticated), adaptController(UserController, UserController.getApps)); 13 | 14 | // User profile 15 | app.put('/api/user/profile', setAuthLevel(AuthConfig.authenticated), adaptController(UserController, UserController.updateProfile)); 16 | } -------------------------------------------------------------------------------- /migrations/meta/_journal.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "7", 3 | "dialect": "sqlite", 4 | "entries": [ 5 | { 6 | "idx": 0, 7 | "version": "6", 8 | "when": 1757000935039, 9 | "tag": "0000_living_forge", 10 | "breakpoints": true 11 | }, 12 | { 13 | "idx": 1, 14 | "version": "6", 15 | "when": 1757447858711, 16 | "tag": "0001_married_moondragon", 17 | "breakpoints": true 18 | }, 19 | { 20 | "idx": 2, 21 | "version": "6", 22 | "when": 1758608665393, 23 | "tag": "0002_nebulous_fantastic_four", 24 | "breakpoints": true 25 | }, 26 | { 27 | "idx": 3, 28 | "version": "6", 29 | "when": 1759483967805, 30 | "tag": "0003_bumpy_albert_cleary", 31 | "breakpoints": true 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /src/components/header.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import { CloudflareLogo } from './icons/logos'; 4 | import { Link } from 'react-router'; 5 | 6 | export function Header({ 7 | className, 8 | children, 9 | }: React.ComponentProps<'header'>) { 10 | return ( 11 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | {children} 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as SeparatorPrimitive from "@radix-ui/react-separator" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | function Separator({ 7 | className, 8 | orientation = "horizontal", 9 | decorative = true, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 23 | ) 24 | } 25 | 26 | export { Separator } 27 | -------------------------------------------------------------------------------- /src/routes/chat/utils/websocket-helpers.ts: -------------------------------------------------------------------------------- 1 | import type { WebSocket } from 'partysocket'; 2 | 3 | /** 4 | * Check if WebSocket is ready for communication 5 | */ 6 | export function isWebSocketReady(websocket: WebSocket | undefined): websocket is WebSocket { 7 | return !!websocket && websocket.readyState === 1; // OPEN state 8 | } 9 | 10 | /** 11 | * Send a message via WebSocket if connection is ready 12 | */ 13 | export function sendWebSocketMessage( 14 | websocket: WebSocket | undefined, 15 | type: string, 16 | data?: Record 17 | ): boolean { 18 | if (!isWebSocketReady(websocket)) { 19 | console.warn(`WebSocket not ready for message type: ${type}`); 20 | return false; 21 | } 22 | 23 | websocket.send(JSON.stringify({ type, ...data })); 24 | return true; 25 | } 26 | -------------------------------------------------------------------------------- /src/routes/protected-route.tsx: -------------------------------------------------------------------------------- 1 | import { Navigate } from 'react-router'; 2 | import { useAuth } from '../contexts/auth-context'; 3 | import { Skeleton } from '../components/ui/skeleton'; 4 | 5 | interface ProtectedRouteProps { 6 | children: React.ReactNode; 7 | } 8 | 9 | export function ProtectedRoute({ children }: ProtectedRouteProps) { 10 | const { isAuthenticated, isLoading } = useAuth(); 11 | 12 | if (isLoading) { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | } 22 | 23 | if (!isAuthenticated) { 24 | return ; 25 | } 26 | 27 | return <>{children}>; 28 | } -------------------------------------------------------------------------------- /src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { 6 | return ( 7 | 15 | ) 16 | } 17 | 18 | export { Textarea } 19 | -------------------------------------------------------------------------------- /worker/api/controllers/secrets/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Type definitions for Secrets Controller responses 3 | */ 4 | 5 | import type { EncryptedSecret } from '../../../database/types'; 6 | import { SecretTemplate } from '../../../types/secretsTemplates'; 7 | 8 | /** 9 | * Response data for getSecrets 10 | */ 11 | export interface SecretsData { 12 | secrets: EncryptedSecret[]; 13 | } 14 | 15 | /** 16 | * Response data for storeSecret 17 | */ 18 | export interface SecretStoreData { 19 | secret: EncryptedSecret; 20 | message: string; 21 | } 22 | 23 | /** 24 | * Response data for deleteSecret 25 | * Simple message response 26 | */ 27 | export interface SecretDeleteData { 28 | message: string; 29 | } 30 | 31 | /** 32 | * Response data for getTemplates 33 | */ 34 | export interface SecretTemplatesData { 35 | templates: SecretTemplate[]; 36 | } -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | import { createBrowserRouter } from 'react-router'; 3 | import { RouterProvider } from 'react-router/dom'; 4 | import { initSentry } from './utils/sentry'; 5 | 6 | import { routes } from './routes.ts'; 7 | import './index.css'; 8 | 9 | // Initialize Sentry before rendering 10 | initSentry(); 11 | 12 | // Type for React Router hydration data 13 | import type { RouterState } from 'react-router'; 14 | 15 | declare global { 16 | interface Window { 17 | __staticRouterHydrationData?: Partial>; 18 | } 19 | } 20 | 21 | const router = createBrowserRouter(routes, { 22 | hydrationData: window.__staticRouterHydrationData, 23 | }); 24 | 25 | createRoot(document.getElementById('root')!).render( 26 | 27 | ); 28 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | # Auto-label PRs based on changed files 2 | 3 | 'worker': 4 | - changed-files: 5 | - any-glob-to-any-file: 'worker/**/*' 6 | 7 | 'frontend': 8 | - changed-files: 9 | - any-glob-to-any-file: 'src/**/*' 10 | 11 | 'database': 12 | - changed-files: 13 | - any-glob-to-any-file: 14 | - 'worker/database/**/*' 15 | - 'migrations/**/*' 16 | 17 | 'agent': 18 | - changed-files: 19 | - any-glob-to-any-file: 'worker/agents/**/*' 20 | 21 | 'ci/cd': 22 | - changed-files: 23 | - any-glob-to-any-file: 24 | - '.github/**/*' 25 | - 'wrangler.jsonc' 26 | 27 | 'documentation': 28 | - changed-files: 29 | - any-glob-to-any-file: 30 | - 'docs/**/*' 31 | - '*.md' 32 | 33 | 'dependencies': 34 | - changed-files: 35 | - any-glob-to-any-file: 36 | - 'package.json' 37 | - 'bun.lock' 38 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router'; 2 | import { AuthProvider } from './contexts/auth-context'; 3 | import { AuthModalProvider } from './components/auth/AuthModalProvider'; 4 | import { ThemeProvider } from './contexts/theme-context'; 5 | import { Toaster } from './components/ui/sonner'; 6 | import { AppLayout } from './components/layout/app-layout'; 7 | import { ErrorBoundary } from './components/ErrorBoundary'; 8 | 9 | export default function App() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | } -------------------------------------------------------------------------------- /src/utils/string.ts: -------------------------------------------------------------------------------- 1 | export const getFileType = (path: string): string => { 2 | if (!path || typeof path !== 'string') return 'plaintext'; 3 | const extension = path.split('.').pop(); 4 | 5 | switch (extension) { 6 | case 'ts': 7 | case 'tsx': 8 | return 'typescript'; 9 | case 'js': 10 | case 'jsx': 11 | return 'javascript'; 12 | case 'css': 13 | return 'css'; 14 | case 'html': 15 | return 'html'; 16 | case 'json': 17 | return 'json'; 18 | default: 19 | return 'plaintext'; 20 | } 21 | }; 22 | 23 | export const formatFileSize = (bytes?: number) => { 24 | if (!bytes) return ''; 25 | const units = ['B', 'KB', 'MB', 'GB']; 26 | let size = bytes; 27 | let unitIndex = 0; 28 | while (size >= 1024 && unitIndex < units.length - 1) { 29 | size /= 1024; 30 | unitIndex++; 31 | } 32 | return `${size.toFixed(1)} ${units[unitIndex]}`; 33 | }; 34 | -------------------------------------------------------------------------------- /worker/services/sandbox/types.ts: -------------------------------------------------------------------------------- 1 | export interface ResourceProvisioningResult { 2 | success: boolean; 3 | provisioned: Array<{ 4 | placeholder: string; 5 | resourceType: 'KV' | 'D1'; 6 | resourceId: string; 7 | binding?: string; 8 | }>; 9 | failed: Array<{ 10 | placeholder: string; 11 | resourceType: 'KV' | 'D1'; 12 | error: string; 13 | binding?: string; 14 | }>; 15 | replacements: Record; 16 | wranglerUpdated: boolean; 17 | } 18 | 19 | export interface ResourceProvisioningOptions { 20 | projectName: string; 21 | instanceId: string; 22 | continueOnError: boolean; 23 | } 24 | 25 | export interface WranglerConfigValidationResult { 26 | isValid: boolean; 27 | hasPlaceholders: boolean; 28 | unresolvedPlaceholders: string[]; 29 | errors?: string[]; 30 | } -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "shared/*": [ 6 | "./shared/*" 7 | ], 8 | "worker/*": [ 9 | "./worker/*" 10 | ] 11 | }, 12 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 13 | "target": "ES2022", 14 | "lib": ["ES2023"], 15 | "module": "ESNext", 16 | "skipLibCheck": true, 17 | 18 | /* Bundler mode */ 19 | "moduleResolution": "bundler", 20 | "allowImportingTsExtensions": true, 21 | "isolatedModules": true, 22 | "moduleDetection": "force", 23 | "noEmit": true, 24 | 25 | /* Linting */ 26 | "strict": true, 27 | "noUnusedLocals": true, 28 | "noUnusedParameters": true, 29 | "noFallthroughCasesInSwitch": true, 30 | "noUncheckedSideEffectImports": true 31 | }, 32 | "include": ["vite.config.ts"] 33 | } 34 | -------------------------------------------------------------------------------- /src/components/ui/progress.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as ProgressPrimitive from "@radix-ui/react-progress" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | function Progress({ 7 | className, 8 | value, 9 | ...props 10 | }: React.ComponentProps) { 11 | return ( 12 | 20 | 25 | 26 | ) 27 | } 28 | 29 | export { Progress } 30 | -------------------------------------------------------------------------------- /drizzle.config.local.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'drizzle-kit'; 2 | // import * as dotenv from 'dotenv'; 3 | // import * as fs from 'fs'; 4 | // import * as path from 'path'; 5 | 6 | // // Load environment variables from .dev.vars 7 | // const devVarsPath = path.join(__dirname, '.dev.vars'); 8 | // if (fs.existsSync(devVarsPath)) { 9 | // const devVars = fs.readFileSync(devVarsPath, 'utf-8'); 10 | // const parsed = dotenv.parse(devVars); 11 | // Object.assign(process.env, parsed); 12 | // } 13 | 14 | export default defineConfig({ 15 | schema: './worker/database/schema.ts', 16 | out: './migrations', 17 | dialect: 'sqlite', 18 | driver: 'd1-http', 19 | // dbCredentials: { 20 | // accountId: process.env.CLOUDFLARE_ACCOUNT_ID!, 21 | // token: process.env.CLOUDFLARE_D1_TOKEN!, 22 | // databaseId: process.env.CLOUDFLARE_D1_ID!, 23 | // }, 24 | verbose: true, 25 | strict: true, 26 | }); 27 | -------------------------------------------------------------------------------- /drizzle.config.remote.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'drizzle-kit'; 2 | // import * as dotenv from 'dotenv'; 3 | // import * as fs from 'fs'; 4 | // import * as path from 'path'; 5 | 6 | // // Load environment variables from .dev.vars 7 | // const devVarsPath = path.join(__dirname, '.dev.vars'); 8 | // if (fs.existsSync(devVarsPath)) { 9 | // const devVars = fs.readFileSync(devVarsPath, 'utf-8'); 10 | // const parsed = dotenv.parse(devVars); 11 | // Object.assign(process.env, parsed); 12 | // } 13 | 14 | export default defineConfig({ 15 | schema: './worker/database/schema.ts', 16 | out: './migrations', 17 | dialect: 'sqlite', 18 | driver: 'd1-http', 19 | // dbCredentials: { 20 | // accountId: process.env.CLOUDFLARE_ACCOUNT_ID!, 21 | // token: process.env.CLOUDFLARE_D1_TOKEN!, 22 | // databaseId: process.env.CLOUDFLARE_D1_ID!, 23 | // }, 24 | verbose: true, 25 | strict: true, 26 | }); 27 | -------------------------------------------------------------------------------- /worker/agents/output-formats/diff-formats/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Diff Format Implementations for LLM-generated code changes 3 | * 4 | * This module provides two different diff format implementations, 5 | * each with the same API but different approaches to handling changes. 6 | */ 7 | 8 | import { applyDiff as applyUnifiedDiff } from './udiff'; 9 | import { 10 | applyDiff as applySearchReplaceDiff, 11 | createSearchReplaceDiff, 12 | validateDiff as validateSearchReplaceDiff, 13 | ApplyResult, 14 | FailedBlock, 15 | MatchingStrategy 16 | } from './search-replace'; 17 | 18 | export { 19 | // Unified Diff Format (git-style) 20 | applyUnifiedDiff, 21 | 22 | // Search/Replace Format (simpler, more reliable for LLMs) 23 | applySearchReplaceDiff, 24 | createSearchReplaceDiff, 25 | validateSearchReplaceDiff, 26 | 27 | // Types and utilities 28 | type ApplyResult, 29 | type FailedBlock, 30 | MatchingStrategy, 31 | }; -------------------------------------------------------------------------------- /migrations/0002_nebulous_fantastic_four.sql: -------------------------------------------------------------------------------- 1 | -- Migration to replace deploymentUrl with deploymentId 2 | -- For URLs like https://., extract the deploymentId 3 | 4 | -- Step 1: Add the new deploymentId column 5 | ALTER TABLE `apps` ADD COLUMN `deployment_id` TEXT; 6 | 7 | -- Step 2: Update existing rows to populate deploymentId from deploymentUrl 8 | -- Extract the substring between 'https://' and the first '.' 9 | UPDATE `apps` 10 | SET `deployment_id` = CASE 11 | WHEN `deployment_url` IS NOT NULL AND `deployment_url` != '' 12 | THEN substr( 13 | `deployment_url`, 14 | 9, -- Skip 'https://' (8 chars + 1) 15 | instr(substr(`deployment_url`, 9), '.') - 1 -- Find position of first '.' after 'https://' 16 | ) 17 | ELSE NULL 18 | END 19 | WHERE `deployment_url` IS NOT NULL; 20 | 21 | -- Step 3: Drop the old deploymentUrl column 22 | ALTER TABLE `apps` DROP COLUMN `deployment_url`; 23 | -------------------------------------------------------------------------------- /src/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 2 | 3 | function Collapsible({ 4 | ...props 5 | }: React.ComponentProps) { 6 | return 7 | } 8 | 9 | function CollapsibleTrigger({ 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 17 | ) 18 | } 19 | 20 | function CollapsibleContent({ 21 | ...props 22 | }: React.ComponentProps) { 23 | return ( 24 | 28 | ) 29 | } 30 | 31 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 32 | -------------------------------------------------------------------------------- /worker/middleware/security/websocket.ts: -------------------------------------------------------------------------------- 1 | import { isOriginAllowed } from '../../config/security'; 2 | import { createLogger } from '../../logger'; 3 | 4 | const logger = createLogger('WebSocketSecurity'); 5 | 6 | export function validateWebSocketOrigin(request: Request, env: Env): boolean { 7 | const origin = request.headers.get('Origin'); 8 | 9 | if (!origin) { 10 | logger.warn('WebSocket connection attempt without Origin header'); 11 | return false; 12 | } 13 | 14 | if (!isOriginAllowed(env, origin)) { 15 | logger.warn('WebSocket connection rejected from unauthorized origin', { origin }); 16 | return false; 17 | } 18 | 19 | return true; 20 | } 21 | 22 | export function getWebSocketSecurityHeaders(): Record { 23 | return { 24 | 'X-Frame-Options': 'DENY', 25 | 'X-Content-Type-Options': 'nosniff', 26 | 'X-XSS-Protection': '1; mode=block' 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/hooks/useSentryUser.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { setSentryUser, clearSentryUser } from '@/utils/sentry'; 3 | import { AuthUser } from '@/api-types'; 4 | 5 | /** 6 | * Hook to automatically sync user context with Sentry 7 | * Use this in your app's root or authentication provider 8 | */ 9 | export function useSentryUser(user: AuthUser | null) { 10 | useEffect(() => { 11 | if (user) { 12 | setSentryUser({ 13 | id: user.id, 14 | email: user.email, 15 | username: user.displayName, 16 | }); 17 | } else { 18 | clearSentryUser(); 19 | } 20 | }, [user]); 21 | } 22 | 23 | /** 24 | * Hook to track user actions as breadcrumbs 25 | */ 26 | export function useSentryBreadcrumb() { 27 | return (message: string, data?: Record) => { 28 | import('@/utils/sentry').then(({ addBreadcrumb }) => { 29 | addBreadcrumb(message, 'user-action', 'info', data); 30 | }); 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /worker/agents/assistants/assistant.ts: -------------------------------------------------------------------------------- 1 | // An assistant to agents 2 | 3 | import { Message } from "../inferutils/common"; 4 | import { InferenceContext } from "../inferutils/config.types"; 5 | 6 | class Assistant { 7 | protected history: Message[] = []; 8 | protected env: Env; 9 | protected inferenceContext: InferenceContext; 10 | 11 | constructor(env: Env, inferenceContext: InferenceContext, systemPrompt?: Message) { 12 | this.env = env; 13 | this.inferenceContext = inferenceContext; 14 | if (systemPrompt) { 15 | this.history.push(systemPrompt); 16 | } 17 | } 18 | 19 | save(messages: Message[]): Message[] { 20 | this.history.push(...messages); 21 | return this.history; 22 | } 23 | 24 | getHistory(): Message[] { 25 | return this.history; 26 | } 27 | 28 | clearHistory() { 29 | this.history = []; 30 | } 31 | 32 | 33 | } 34 | 35 | export default Assistant; 36 | -------------------------------------------------------------------------------- /.github/workflows/pr-labeler.yml: -------------------------------------------------------------------------------- 1 | name: PR Size & Type Labeler 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize] 6 | 7 | jobs: 8 | size-label: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | pull-requests: write 12 | 13 | steps: 14 | - uses: codelytv/pr-size-labeler@v1 15 | with: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | xs_label: 'size/XS' 18 | xs_max_size: 10 19 | s_label: 'size/S' 20 | s_max_size: 100 21 | m_label: 'size/M' 22 | m_max_size: 500 23 | l_label: 'size/L' 24 | l_max_size: 1000 25 | xl_label: 'size/XL' 26 | 27 | auto-label: 28 | runs-on: ubuntu-latest 29 | permissions: 30 | pull-requests: write 31 | contents: read 32 | 33 | steps: 34 | - uses: actions/labeler@v5 35 | with: 36 | repo-token: ${{ secrets.GITHUB_TOKEN }} 37 | configuration-path: .github/labeler.yml 38 | -------------------------------------------------------------------------------- /src/components/agent-mode-display.tsx: -------------------------------------------------------------------------------- 1 | import { Zap } from 'lucide-react'; 2 | import { type AgentMode } from './agent-mode-toggle'; 3 | 4 | interface AgentModeDisplayProps { 5 | mode: AgentMode; 6 | className?: string; 7 | } 8 | 9 | export function AgentModeDisplay({ 10 | mode, 11 | className = '' 12 | }: AgentModeDisplayProps) { 13 | return ( 14 | 15 | {mode === 'smart' ? ( 16 | <> 17 | 18 | Smart 19 | > 20 | ) : ( 21 | <> 22 | 23 | Reliable 24 | > 25 | )} 26 | 27 | ); 28 | } -------------------------------------------------------------------------------- /src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Input({ className, type, ...props }: React.ComponentProps<"input">) { 6 | return ( 7 | 18 | ) 19 | } 20 | 21 | export { Input } 22 | -------------------------------------------------------------------------------- /worker/api/routes/analyticsRoutes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Setup routes for AI Gateway analytics endpoints 3 | */ 4 | import { AnalyticsController } from '../controllers/analytics/controller'; 5 | import { Hono } from 'hono'; 6 | import { AppEnv } from '../../types/appenv'; 7 | import { AuthConfig, setAuthLevel } from '../../middleware/auth/routeAuth'; 8 | import { adaptController } from '../honoAdapter'; 9 | 10 | /** 11 | * Setup analytics routes 12 | */ 13 | export function setupAnalyticsRoutes(app: Hono): void { 14 | // User analytics - requires authentication 15 | app.get( 16 | '/api/user/:id/analytics', 17 | setAuthLevel(AuthConfig.ownerOnly), 18 | adaptController(AnalyticsController, AnalyticsController.getUserAnalytics) 19 | ); 20 | 21 | // Agent/Chat analytics - requires authentication 22 | app.get( 23 | '/api/agent/:id/analytics', 24 | setAuthLevel(AuthConfig.ownerOnly), 25 | adaptController(AnalyticsController, AnalyticsController.getAgentAnalytics) 26 | ); 27 | } -------------------------------------------------------------------------------- /src/assets/provider-logos/cloudflare.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /worker/utils/timeFormatter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Time formatting utilities 3 | * Provides functions for formatting dates and times in user-friendly formats 4 | */ 5 | 6 | /** 7 | * Format a date as relative time (e.g., "2 hours ago", "just now") 8 | */ 9 | export function formatRelativeTime(date: Date | null): string { 10 | if (!date) return 'Unknown'; 11 | 12 | const now = new Date(); 13 | const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); 14 | 15 | if (diffInSeconds < 60) return 'just now'; 16 | if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)} minutes ago`; 17 | if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)} hours ago`; 18 | if (diffInSeconds < 604800) return `${Math.floor(diffInSeconds / 86400)} days ago`; 19 | if (diffInSeconds < 2592000) return `${Math.floor(diffInSeconds / 604800)} weeks ago`; 20 | if (diffInSeconds < 31536000) return `${Math.floor(diffInSeconds / 2592000)} months ago`; 21 | return `${Math.floor(diffInSeconds / 31536000)} years ago`; 22 | } -------------------------------------------------------------------------------- /worker/api/routes/imagesRoutes.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from 'hono'; 2 | import { AppEnv } from '../../types/appenv'; 3 | import { ScreenshotsController } from '../controllers/screenshots/controller'; 4 | import { adaptController } from '../honoAdapter'; 5 | import { setAuthLevel, AuthConfig } from '../../middleware/auth/routeAuth'; 6 | 7 | export function setupScreenshotRoutes(app: Hono): void { 8 | const screenshotsRouter = new Hono(); 9 | 10 | // Publicly serve screenshots (they are non-sensitive previews of generated apps) 11 | screenshotsRouter.get('/:id/:file', setAuthLevel(AuthConfig.authenticated), adaptController(ScreenshotsController, ScreenshotsController.serveScreenshot)); 12 | 13 | app.route('/api/screenshots', screenshotsRouter); 14 | 15 | // const imagesRouter = new Hono(); 16 | // // Publicly serve image uploads 17 | // imagesRouter.get('/:id/:file', setAuthLevel(AuthConfig.authenticated), adaptController(ScreenshotsController, ScreenshotsController.serveScreenshot)); 18 | 19 | // app.route('/api/uploads', imagesRouter); 20 | } 21 | -------------------------------------------------------------------------------- /worker/services/sandbox/utils.ts: -------------------------------------------------------------------------------- 1 | import { TemplateDetails, TemplateFile } from "./sandboxTypes"; 2 | 3 | export function getTemplateImportantFiles(templateDetails: TemplateDetails, filterRedacted: boolean = true): TemplateFile[] { 4 | const { importantFiles, allFiles, redactedFiles } = templateDetails; 5 | const redactedSet = new Set(redactedFiles); 6 | 7 | const result: TemplateFile[] = []; 8 | for (const [filePath, fileContents] of Object.entries(allFiles)) { 9 | if (importantFiles.some(pattern => filePath === pattern || filePath.startsWith(pattern))) { 10 | const contents = filterRedacted && redactedSet.has(filePath) ? 'REDACTED' : fileContents; 11 | if (contents) result.push({ filePath, fileContents: contents }); 12 | } 13 | } 14 | 15 | return result; 16 | } 17 | 18 | export function getTemplateFiles(templateDetails: TemplateDetails): TemplateFile[] { 19 | return Object.entries(templateDetails.allFiles).map(([filePath, fileContents]) => ({ 20 | filePath, 21 | fileContents, 22 | })); 23 | } -------------------------------------------------------------------------------- /worker/agents/tools/types.ts: -------------------------------------------------------------------------------- 1 | import { ChatCompletionFunctionTool } from 'openai/resources'; 2 | export interface MCPServerConfig { 3 | name: string; 4 | sseUrl: string; 5 | } 6 | export interface MCPResult { 7 | content: string; 8 | } 9 | 10 | export interface ErrorResult { 11 | error: string; 12 | } 13 | 14 | export interface ToolCallResult { 15 | id: string; 16 | name: string; 17 | arguments: Record; 18 | result?: unknown; 19 | } 20 | 21 | export type ToolImplementation, TResult = unknown> = 22 | (args: TArgs) => Promise; 23 | 24 | export type ToolDefinition< 25 | TArgs = Record, 26 | TResult = unknown 27 | > = ChatCompletionFunctionTool & { 28 | implementation: ToolImplementation; 29 | onStart?: (args: TArgs) => void; 30 | onComplete?: (args: TArgs, result: TResult) => void; 31 | }; 32 | 33 | export type ExtractToolArgs = T extends ToolImplementation ? A : never; 34 | 35 | export type ExtractToolResult = T extends ToolImplementation ? R : never; -------------------------------------------------------------------------------- /worker/agents/utils/operationError.ts: -------------------------------------------------------------------------------- 1 | import { StructuredLogger } from "../../logger"; 2 | 3 | /** 4 | * Utility for consistent error handling in operations 5 | */ 6 | export class OperationError { 7 | /** 8 | * Log error and re-throw with consistent format 9 | */ 10 | static logAndThrow(logger: StructuredLogger, operation: string, error: unknown): never { 11 | const errorMessage = error instanceof Error ? error.message : String(error); 12 | logger.error(`Error in ${operation}:`, error); 13 | throw new Error(`${operation} failed: ${errorMessage}`); 14 | } 15 | 16 | /** 17 | * Log error and return default value instead of throwing 18 | */ 19 | static logAndReturn(logger: StructuredLogger, operation: string, error: unknown, defaultValue: T): T { 20 | const errorMessage = error instanceof Error ? error.message : String(error); 21 | logger.error(`Error in ${operation}:`, error); 22 | logger.warn(`Returning default value for ${operation} due to error: ${errorMessage}`); 23 | return defaultValue; 24 | } 25 | } -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2022", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2022", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | // "types": ["./worker-configuration.d.ts", "vite/client", "jest"], 10 | 11 | /* Bundler mode */ 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "isolatedModules": true, 15 | "verbatimModuleSyntax": false, 16 | "moduleDetection": "force", 17 | "noEmit": true, 18 | "jsx": "react-jsx", 19 | 20 | /* Linting */ 21 | "strict": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "noFallthroughCasesInSwitch": true, 25 | "noUncheckedSideEffectImports": true, 26 | "baseUrl": ".", 27 | "paths": { 28 | "@/*": [ 29 | "./src/*" 30 | ], 31 | "shared/*": [ 32 | "./shared/*" 33 | ], 34 | "worker/*": [ 35 | "./worker/*" 36 | ] 37 | } 38 | }, 39 | "include": ["src", "shared"], 40 | } 41 | -------------------------------------------------------------------------------- /worker/api/controllers/status/controller.ts: -------------------------------------------------------------------------------- 1 | import { BaseController } from '../baseController'; 2 | import type { ApiResponse, ControllerResponse } from '../types'; 3 | import type { RouteContext } from '../../types/route-context'; 4 | import type { PlatformStatusData } from './types'; 5 | 6 | export class StatusController extends BaseController { 7 | static async getPlatformStatus( 8 | _request: Request, 9 | _env: Env, 10 | _ctx: ExecutionContext, 11 | context: RouteContext 12 | ): Promise>> { 13 | const messaging = context.config.globalMessaging ?? { globalUserMessage: '', changeLogs: '' }; 14 | const globalUserMessage = messaging.globalUserMessage ?? ''; 15 | const changeLogs = messaging.changeLogs ?? ''; 16 | 17 | const data: PlatformStatusData = { 18 | globalUserMessage, 19 | changeLogs, 20 | hasActiveMessage: globalUserMessage.trim().length > 0, 21 | }; 22 | 23 | return StatusController.createSuccessResponse(data); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /worker/logger/types.ts: -------------------------------------------------------------------------------- 1 | export type LogLevel = 'debug' | 'info' | 'warn' | 'error'; 2 | 3 | export interface LoggerConfig { 4 | /** Base log level - messages below this level won't be logged */ 5 | level?: LogLevel; 6 | /** Pretty print for development (uses console formatting vs JSON) */ 7 | prettyPrint?: boolean; 8 | } 9 | 10 | export interface ObjectContext { 11 | /** Unique identifier for this object instance */ 12 | id?: string; 13 | /** Type/class name of the object */ 14 | type?: string; 15 | /** Additional object-specific metadata */ 16 | meta?: Record; 17 | } 18 | 19 | export interface LogEntry { 20 | /** Log level */ 21 | level: LogLevel; 22 | /** Timestamp in ISO format */ 23 | time: string; 24 | /** Component name */ 25 | component: string; 26 | /** Primary log message */ 27 | msg: string; 28 | /** Object context if applicable */ 29 | object?: ObjectContext; 30 | /** Error object if applicable */ 31 | error?: { 32 | name: string; 33 | message: string; 34 | stack?: string; 35 | }; 36 | /** Additional structured data */ 37 | [key: string]: unknown; 38 | } 39 | -------------------------------------------------------------------------------- /src/routes/chat/components/copy.tsx: -------------------------------------------------------------------------------- 1 | import { AnimatePresence, motion } from 'framer-motion'; 2 | import { useState } from 'react'; 3 | import { Check, Link2 } from 'react-feather'; 4 | 5 | const MotionCheck = motion.create(Check); 6 | const MotionLink = motion.create(Link2); 7 | 8 | export function Copy({ text }: { text: string }) { 9 | const [copied, setCopied] = useState(false); 10 | 11 | return ( 12 | { 15 | navigator.clipboard.writeText(text); 16 | setCopied(true); 17 | setTimeout(() => { 18 | setCopied(false); 19 | }, 2500); 20 | }} 21 | > 22 | 23 | {copied ? ( 24 | 30 | ) : ( 31 | 37 | )} 38 | 39 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Cloudflare 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /worker/api/routes/githubExporterRoutes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * GitHub Exporter Routes 3 | * Handles GitHub repository export flows 4 | */ 5 | 6 | import { GitHubExporterController } from '../controllers/githubExporter/controller'; 7 | import { Hono } from 'hono'; 8 | import { AppEnv } from '../../types/appenv'; 9 | import { adaptController } from '../honoAdapter'; 10 | import { AuthConfig, setAuthLevel } from '../../middleware/auth/routeAuth'; 11 | 12 | /** 13 | * Setup GitHub Exporter routes 14 | */ 15 | export function setupGitHubExporterRoutes(app: Hono): void { 16 | app.get('/api/github-exporter/callback', setAuthLevel(AuthConfig.public), adaptController(GitHubExporterController, GitHubExporterController.handleOAuthCallback)); 17 | 18 | // Repository export routes with OAuth flow 19 | app.post('/api/github-app/export', setAuthLevel(AuthConfig.authenticated), adaptController(GitHubExporterController, GitHubExporterController.initiateGitHubExport)); 20 | 21 | // Check remote repository status 22 | app.post('/api/github-app/check-remote', setAuthLevel(AuthConfig.authenticated), adaptController(GitHubExporterController, GitHubExporterController.checkRemoteStatus)); 23 | } 24 | -------------------------------------------------------------------------------- /worker/agents/services/implementations/StateManager.ts: -------------------------------------------------------------------------------- 1 | import { IStateManager } from '../interfaces/IStateManager'; 2 | import { CodeGenState } from '../../core/state'; 3 | 4 | /** 5 | * State manager implementation for Durable Objects 6 | * Works with the Agent's state management 7 | */ 8 | export class StateManager implements IStateManager { 9 | constructor( 10 | private getStateFunc: () => CodeGenState, 11 | private setStateFunc: (state: CodeGenState) => void 12 | ) {} 13 | 14 | getState(): Readonly { 15 | return this.getStateFunc(); 16 | } 17 | 18 | setState(newState: CodeGenState): void { 19 | this.setStateFunc(newState); 20 | } 21 | 22 | updateField(field: K, value: CodeGenState[K]): void { 23 | const currentState = this.getState(); 24 | this.setState({ 25 | ...currentState, 26 | [field]: value 27 | }); 28 | } 29 | 30 | batchUpdate(updates: Partial): void { 31 | const currentState = this.getState(); 32 | this.setState({ 33 | ...currentState, 34 | ...updates 35 | }); 36 | } 37 | } -------------------------------------------------------------------------------- /worker/agents/tools/toolkit/run-analysis.ts: -------------------------------------------------------------------------------- 1 | import { ToolDefinition } from '../types'; 2 | import { StructuredLogger } from '../../../logger'; 3 | import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; 4 | import { StaticAnalysisResponse } from 'worker/services/sandbox/sandboxTypes'; 5 | 6 | export type RunAnalysisArgs = { 7 | files?: string[]; 8 | }; 9 | 10 | export type RunAnalysisResult = StaticAnalysisResponse; 11 | 12 | export function createRunAnalysisTool( 13 | agent: CodingAgentInterface, 14 | logger: StructuredLogger, 15 | ): ToolDefinition { 16 | return { 17 | type: 'function' as const, 18 | function: { 19 | name: 'run_analysis', 20 | description: 21 | 'Run static analysis (lint + typecheck), optionally scoped to given files.', 22 | parameters: { 23 | type: 'object', 24 | properties: { 25 | files: { type: 'array', items: { type: 'string' } }, 26 | }, 27 | required: [], 28 | }, 29 | }, 30 | implementation: async ({ files }) => { 31 | logger.info('Running static analysis', { 32 | filesCount: files?.length || 0, 33 | }); 34 | return await agent.runStaticAnalysisCode(files); 35 | }, 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /worker/utils/urls.ts: -------------------------------------------------------------------------------- 1 | export const getProtocolForHost = (host: string): string => { 2 | if (host.startsWith('localhost') || host.startsWith('127.0.0.1') || host.startsWith('0.0.0.0') || host.startsWith('::1')) { 3 | return 'http'; 4 | } else { 5 | return 'https'; 6 | } 7 | } 8 | export function getPreviewDomain(env: Env): string { 9 | if (env.CUSTOM_PREVIEW_DOMAIN && env.CUSTOM_PREVIEW_DOMAIN.trim() !== '') { 10 | return env.CUSTOM_PREVIEW_DOMAIN; 11 | } 12 | return env.CUSTOM_DOMAIN; 13 | } 14 | 15 | export function buildUserWorkerUrl(env: Env, deploymentId: string): string { 16 | const domain = getPreviewDomain(env); 17 | const protocol = getProtocolForHost(domain); 18 | return `${protocol}://${deploymentId}.${domain}`; 19 | } 20 | 21 | export function buildGitCloneUrl(env: Env, appId: string, token?: string): string { 22 | const domain = env.CUSTOM_DOMAIN; 23 | const protocol = getProtocolForHost(domain); 24 | // Git expects username:password format. Use 'oauth2' as username and token as password 25 | // This is a standard pattern for token-based git authentication 26 | const auth = token ? `oauth2:${token}@` : ''; 27 | return `${protocol}://${auth}${domain}/apps/${appId}.git`; 28 | } -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'type-enum': [ 5 | 2, 6 | 'always', 7 | [ 8 | 'feat', // New feature (minor version bump) 9 | 'fix', // Bug fix (patch version bump) 10 | 'docs', // Documentation only changes 11 | 'style', // Code style changes (formatting, semicolons, etc.) 12 | 'refactor', // Code refactoring without feature/fix 13 | 'perf', // Performance improvements 14 | 'test', // Adding or updating tests 15 | 'build', // Build system or external dependencies 16 | 'ci', // CI/CD configuration changes 17 | 'chore', // Other changes (maintenance) 18 | 'revert', // Revert previous commit 19 | ], 20 | ], 21 | 'type-case': [2, 'always', 'lower-case'], 22 | 'type-empty': [2, 'never'], 23 | 'subject-empty': [2, 'never'], 24 | 'subject-full-stop': [2, 'never', '.'], 25 | 'header-max-length': [2, 'always', 100], 26 | 'body-leading-blank': [1, 'always'], 27 | 'body-max-line-length': [2, 'always', 100], 28 | 'footer-leading-blank': [1, 'always'], 29 | 'footer-max-line-length': [2, 'always', 100], 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /src/components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SwitchPrimitive from "@radix-ui/react-switch" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Switch({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | 27 | 28 | ) 29 | } 30 | 31 | export { Switch } 32 | -------------------------------------------------------------------------------- /src/components/layout/app-layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Outlet, useLocation } from 'react-router'; 3 | import { SidebarProvider, SidebarInset } from '@/components/ui/sidebar'; 4 | import { AppSidebar } from './app-sidebar'; 5 | import { GlobalHeader } from './global-header'; 6 | import { AppsDataProvider } from '@/contexts/apps-data-context'; 7 | import clsx from 'clsx'; 8 | 9 | interface AppLayoutProps { 10 | children?: React.ReactNode; 11 | } 12 | 13 | export function AppLayout({ children }: AppLayoutProps) { 14 | const { pathname } = useLocation(); 15 | return ( 16 | 17 | 25 | 26 | 27 | 28 | 29 | {children || } 30 | 31 | 32 | 33 | 34 | ); 35 | } -------------------------------------------------------------------------------- /src/components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Avatar({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | ) 22 | } 23 | 24 | function AvatarImage({ 25 | className, 26 | ...props 27 | }: React.ComponentProps) { 28 | return ( 29 | 34 | ) 35 | } 36 | 37 | function AvatarFallback({ 38 | className, 39 | ...props 40 | }: React.ComponentProps) { 41 | return ( 42 | 50 | ) 51 | } 52 | 53 | export { Avatar, AvatarImage, AvatarFallback } 54 | -------------------------------------------------------------------------------- /src/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox" 5 | import { CheckIcon } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | function Checkbox({ 10 | className, 11 | ...props 12 | }: React.ComponentProps) { 13 | return ( 14 | 22 | 26 | 27 | 28 | 29 | ) 30 | } 31 | 32 | export { Checkbox } 33 | -------------------------------------------------------------------------------- /src/routes.ts: -------------------------------------------------------------------------------- 1 | import type { RouteObject } from 'react-router'; 2 | import React from 'react'; 3 | 4 | import App from './App'; 5 | import Home from './routes/home'; 6 | import Chat from './routes/chat/chat'; 7 | import Profile from './routes/profile'; 8 | import Settings from './routes/settings/index'; 9 | import AppsPage from './routes/apps'; 10 | import AppView from './routes/app'; 11 | import DiscoverPage from './routes/discover'; 12 | import { ProtectedRoute } from './routes/protected-route'; 13 | 14 | const routes = [ 15 | { 16 | path: '/', 17 | Component: App, 18 | children: [ 19 | { 20 | index: true, 21 | Component: Home, 22 | }, 23 | { 24 | path: 'chat/:chatId', 25 | Component: Chat, 26 | }, 27 | { 28 | path: 'profile', 29 | element: React.createElement(ProtectedRoute, { children: React.createElement(Profile) }), 30 | }, 31 | { 32 | path: 'settings', 33 | element: React.createElement(ProtectedRoute, { children: React.createElement(Settings) }), 34 | }, 35 | { 36 | path: 'apps', 37 | element: React.createElement(ProtectedRoute, { children: React.createElement(AppsPage) }), 38 | }, 39 | { 40 | path: 'app/:id', 41 | Component: AppView, 42 | }, 43 | { 44 | path: 'discover', 45 | Component: DiscoverPage, 46 | }, 47 | ], 48 | }, 49 | ] satisfies RouteObject[]; 50 | 51 | export { routes }; 52 | -------------------------------------------------------------------------------- /src/hooks/use-app.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useCallback } from 'react'; 2 | import { apiClient } from '@/lib/api-client'; 3 | import type { AppDetailsData } from '@/api-types'; 4 | 5 | export function useApp(appId: string | undefined) { 6 | // Always declare hooks in the same order to satisfy React Rules of Hooks 7 | const [app, setApp] = useState(null); 8 | const [loading, setLoading] = useState(!!appId && appId !== 'new'); 9 | const [error, setError] = useState(null); 10 | 11 | const fetchApp = useCallback(async () => { 12 | // Guard: if no valid appId or it's a new app, reset state and skip fetching 13 | if (!appId || appId === 'new') { 14 | setApp(null); 15 | setError(null); 16 | setLoading(false); 17 | return; 18 | } 19 | 20 | try { 21 | setLoading(true); 22 | const response = await apiClient.getAppDetails(appId); 23 | setApp(response.data || null); 24 | setError(null); 25 | } catch (err) { 26 | console.error('Error fetching app:', err); 27 | setError(err instanceof Error ? err.message : 'Failed to fetch app'); 28 | setApp(null); 29 | } finally { 30 | setLoading(false); 31 | } 32 | }, [appId]); 33 | 34 | useEffect(() => { 35 | void fetchApp(); 36 | }, [fetchApp]); 37 | 38 | return { app, loading, error, refetch: fetchApp }; 39 | } 40 | -------------------------------------------------------------------------------- /worker/agents/tools/toolkit/queue-request.ts: -------------------------------------------------------------------------------- 1 | import { ToolDefinition } from '../types'; 2 | import { StructuredLogger } from '../../../logger'; 3 | import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; 4 | 5 | type QueueRequestArgs = { 6 | modificationRequest: string; 7 | }; 8 | 9 | export function createQueueRequestTool( 10 | agent: CodingAgentInterface, 11 | logger: StructuredLogger 12 | ): ToolDefinition { 13 | return { 14 | type: 'function' as const, 15 | function: { 16 | name: 'queue_request', 17 | description: 18 | 'Queue up modification requests or changes, to be implemented in the next development phase', 19 | parameters: { 20 | type: 'object', 21 | additionalProperties: false, 22 | properties: { 23 | modificationRequest: { 24 | type: 'string', 25 | minLength: 8, 26 | description: 27 | "The changes needed to be made to the app. Please don't supply any code level or implementation details. Provide detailed requirements and description of the changes you want to make.", 28 | }, 29 | }, 30 | required: ['modificationRequest'], 31 | }, 32 | }, 33 | implementation: async (args) => { 34 | logger.info('Received app edit request', { 35 | modificationRequest: args.modificationRequest, 36 | }); 37 | agent.queueRequest(args.modificationRequest); 38 | return null; 39 | }, 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /worker/api/honoAdapter.ts: -------------------------------------------------------------------------------- 1 | import { Context } from 'hono'; 2 | import { RouteContext } from './types/route-context'; 3 | import { AppEnv } from '../types/appenv'; 4 | import { BaseController } from './controllers/baseController'; 5 | import { enforceAuthRequirement } from '../middleware/auth/routeAuth'; 6 | /* 7 | * This is a simple adapter to convert Hono context to our base controller's expected arguments 8 | */ 9 | 10 | type ControllerMethod = ( 11 | this: T, 12 | request: Request, 13 | env: Env, 14 | ctx: ExecutionContext, 15 | context: RouteContext 16 | ) => Promise; 17 | 18 | export function adaptController( 19 | controller: T, 20 | method: ControllerMethod 21 | ) { 22 | return async (c: Context): Promise => { 23 | const authResult = await enforceAuthRequirement(c); 24 | if (authResult) { 25 | return authResult; 26 | } 27 | 28 | const routeContext: RouteContext = { 29 | user: c.get('user'), 30 | sessionId: c.get('sessionId'), 31 | config: c.get('config'), 32 | pathParams: c.req.param(), 33 | queryParams: new URL(c.req.url).searchParams, 34 | }; 35 | return await method.call( 36 | controller, 37 | c.req.raw, 38 | c.env, 39 | c.executionCtx, 40 | routeContext 41 | ); 42 | }; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /worker/agents/core/smartGeneratorAgent.ts: -------------------------------------------------------------------------------- 1 | import { SimpleCodeGeneratorAgent } from "./simpleGeneratorAgent"; 2 | import { CodeGenState } from "./state"; 3 | import { AgentInitArgs } from "./types"; 4 | 5 | /** 6 | * SmartCodeGeneratorAgent - Smartly orchestrated AI-powered code generation 7 | * using an LLM orchestrator instead of state machine based orchestrator. 8 | * TODO: NOT YET IMPLEMENTED, CURRENTLY Just uses SimpleCodeGeneratorAgent 9 | */ 10 | export class SmartCodeGeneratorAgent extends SimpleCodeGeneratorAgent { 11 | 12 | /** 13 | * Initialize the smart code generator with project blueprint and template 14 | * Sets up services and begins deployment process 15 | */ 16 | async initialize( 17 | initArgs: AgentInitArgs, 18 | agentMode: 'deterministic' | 'smart' 19 | ): Promise { 20 | this.logger().info('🧠 Initializing SmartCodeGeneratorAgent with enhanced AI orchestration', { 21 | queryLength: initArgs.query.length, 22 | agentType: agentMode 23 | }); 24 | 25 | // Call the parent initialization 26 | return await super.initialize(initArgs); 27 | } 28 | 29 | async generateAllFiles(reviewCycles: number = 10): Promise { 30 | if (this.state.agentMode === 'deterministic') { 31 | return super.generateAllFiles(reviewCycles); 32 | } else { 33 | return this.builderLoop(); 34 | } 35 | } 36 | 37 | async builderLoop() { 38 | // TODO 39 | } 40 | } -------------------------------------------------------------------------------- /worker/api/routes/modelProviderRoutes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Model Provider Routes 3 | * Routes for custom model provider management 4 | */ 5 | import { Hono } from 'hono'; 6 | import { AppEnv } from '../../types/appenv'; 7 | import { ModelProvidersController } from '../controllers/modelProviders/controller'; 8 | import { AuthConfig, setAuthLevel } from '../../middleware/auth/routeAuth'; 9 | import { adaptController } from '../honoAdapter'; 10 | 11 | export function setupModelProviderRoutes(app: Hono): void { 12 | // Custom model provider routes 13 | app.get('/api/user/providers', setAuthLevel(AuthConfig.authenticated), adaptController(ModelProvidersController, ModelProvidersController.getProviders)); 14 | app.get('/api/user/providers/:id', setAuthLevel(AuthConfig.authenticated), adaptController(ModelProvidersController, ModelProvidersController.getProvider)); 15 | app.post('/api/user/providers', setAuthLevel(AuthConfig.authenticated), adaptController(ModelProvidersController, ModelProvidersController.createProvider)); 16 | app.put('/api/user/providers/:id', setAuthLevel(AuthConfig.authenticated), adaptController(ModelProvidersController, ModelProvidersController.updateProvider)); 17 | app.delete('/api/user/providers/:id', setAuthLevel(AuthConfig.authenticated), adaptController(ModelProvidersController, ModelProvidersController.deleteProvider)); 18 | app.post('/api/user/providers/test', setAuthLevel(AuthConfig.authenticated), adaptController(ModelProvidersController, ModelProvidersController.testProvider)); 19 | } -------------------------------------------------------------------------------- /worker/api/types/route-context.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Route Context Types 3 | */ 4 | 5 | import { GlobalConfigurableSettings } from '../../config'; 6 | import { AuthUser } from '../../types/auth-types'; 7 | 8 | /** 9 | * Route context containing authenticated user and path parameters 10 | */ 11 | export interface RouteContext { 12 | /** 13 | * Authenticated user (null if not authenticated or public route) 14 | */ 15 | user: AuthUser | null; 16 | 17 | /** 18 | * Session ID (null if not authenticated or public route) 19 | */ 20 | sessionId: string | null; 21 | 22 | /** 23 | * Global configurations for the application 24 | */ 25 | config: GlobalConfigurableSettings; 26 | 27 | /** 28 | * Path parameters extracted from the route (e.g., :id, :agentId) 29 | */ 30 | pathParams: Record; 31 | 32 | /** 33 | * Query parameters from the URL 34 | */ 35 | queryParams: URLSearchParams; 36 | } 37 | 38 | /** 39 | * Extended request handler that receives structured context 40 | */ 41 | export type ContextualRequestHandler = ( 42 | request: Request, 43 | env: Env, 44 | ctx: ExecutionContext, 45 | context: RouteContext, 46 | ) => Promise; 47 | 48 | /** 49 | * Route parameter configuration for type safety 50 | */ 51 | export interface RouteParamConfig { 52 | /** 53 | * Required path parameters for this route 54 | */ 55 | requiredParams?: string[]; 56 | 57 | /** 58 | * Optional path parameters for this route 59 | */ 60 | optionalParams?: string[]; 61 | } 62 | 63 | -------------------------------------------------------------------------------- /.dev.vars.example: -------------------------------------------------------------------------------- 1 | # Security Configuration 2 | #CUSTOM_DOMAIN="your-domain.com" # Your custom domain for CORS 3 | #ENVIRONMENT="prod" # Options: dev, staging, prod 4 | 5 | # Cloudflare Credentials (Required for manual deployment via `bun run deploy`) 6 | # These are automatically provided when using "Deploy to Cloudflare" button 7 | # Get these from: https://dash.cloudflare.com/profile/api-tokens 8 | #CLOUDFLARE_API_TOKEN="" # API token with Account-level permissions (see README for required permissions) 9 | #CLOUDFLARE_ACCOUNT_ID="" # Your Cloudflare account ID 10 | 11 | # Essential Secrets: 12 | #CLOUDFLARE_AI_GATEWAY_TOKEN="" # If this has read and edit permissions, the AI Gateway will be created automatically. run is required at the least 13 | 14 | # Provider specific secrets: 15 | #ANTHROPIC_API_KEY="" 16 | #OPENAI_API_KEY="" 17 | GOOGLE_AI_STUDIO_API_KEY="" 18 | #OPENROUTER_API_KEY="default" 19 | #GROQ_API_KEY="default" 20 | 21 | # Stuff for Oauths: 22 | #GOOGLE_CLIENT_SECRET="" 23 | #GOOGLE_CLIENT_ID="" 24 | 25 | # GitHub OAuth 26 | #GITHUB_CLIENT_ID="" 27 | #GITHUB_CLIENT_SECRET="" 28 | 29 | # Github Export OAuth 30 | # GITHUB_EXPORTER_CLIENT_SECRET="" 31 | # GITHUB_EXPORTER_CLIENT_ID="" 32 | 33 | # Secrets for various internal services of this platform 34 | JWT_SECRET="" 35 | WEBHOOK_SECRET="" 36 | 37 | # AI Gateway URL Override (Leave as-is if not going to set it) 38 | #CLOUDFLARE_AI_GATEWAY_URL="" 39 | 40 | # Cloudflare API Key and Account ID. This is important 41 | # CLOUDFLARE_API_TOKEN="" 42 | # CLOUDFLARE_ACCOUNT_ID="" -------------------------------------------------------------------------------- /worker/api/controllers/appView/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Type definitions for AppView Controller responses 3 | * Following strict DRY principles by reusing existing database types 4 | */ 5 | 6 | import { AgentSummary } from '../../../agents/core/types'; 7 | import { EnhancedAppData } from '../../../database/types'; 8 | 9 | /** 10 | * Generated code file structure 11 | */ 12 | export interface GeneratedCodeFile { 13 | filePath: string; 14 | fileContents: string; 15 | explanation?: string; 16 | } 17 | 18 | /** 19 | * Response data for getAppDetails - extends existing EnhancedAppData 20 | * Adds only fields unique to app view response, uses EnhancedAppData stats directly 21 | */ 22 | export interface AppDetailsData extends EnhancedAppData { 23 | cloudflareUrl: string | null; 24 | previewUrl: string | null; 25 | user: { 26 | id: string; 27 | displayName: string; 28 | avatarUrl: string | null; 29 | }; 30 | agentSummary: AgentSummary | null; 31 | } 32 | 33 | /** 34 | * Response data for toggleAppStar 35 | */ 36 | export interface AppStarToggleData { 37 | isStarred: boolean; 38 | starCount: number; 39 | } 40 | 41 | /** 42 | * Response data for git clone token generation 43 | */ 44 | export interface GitCloneTokenData { 45 | token: string; 46 | expiresIn: number; 47 | expiresAt: string; 48 | cloneUrl: string; 49 | } 50 | 51 | // /** 52 | // * Response data for forkApp 53 | // */ 54 | // export interface ForkAppData { 55 | // forkedAppId: string; 56 | // message: string; 57 | // } -------------------------------------------------------------------------------- /src/components/theme-toggle.tsx: -------------------------------------------------------------------------------- 1 | import { Moon, Sun } from 'lucide-react'; 2 | import { Button } from './ui/button'; 3 | import { 4 | DropdownMenu, 5 | DropdownMenuContent, 6 | DropdownMenuItem, 7 | DropdownMenuTrigger, 8 | } from './ui/dropdown-menu'; 9 | import { useTheme } from '../contexts/theme-context'; 10 | 11 | export function ThemeToggle() { 12 | const { setTheme } = useTheme(); 13 | 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | Toggle theme 21 | 22 | 23 | 24 | setTheme('light')}> 25 | 26 | Light 27 | 28 | setTheme('dark')}> 29 | 30 | Dark 31 | 32 | setTheme('system')}> 33 | 💻 34 | System 35 | 36 | 37 | 38 | ); 39 | } -------------------------------------------------------------------------------- /worker/agents/tools/toolkit/wait-for-debug.ts: -------------------------------------------------------------------------------- 1 | import { ToolDefinition } from '../types'; 2 | import { StructuredLogger } from '../../../logger'; 3 | import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; 4 | 5 | export function createWaitForDebugTool( 6 | agent: CodingAgentInterface, 7 | logger: StructuredLogger 8 | ): ToolDefinition, { status: string } | { error: string }> { 9 | return { 10 | type: 'function', 11 | function: { 12 | name: 'wait_for_debug', 13 | description: 14 | 'Wait for the current debug session to complete. Use when deep_debug returns DEBUG_IN_PROGRESS error. Returns immediately if no debug session is running.', 15 | parameters: { 16 | type: 'object', 17 | properties: {}, 18 | required: [], 19 | }, 20 | }, 21 | implementation: async () => { 22 | try { 23 | if (agent.isDeepDebugging()) { 24 | logger.info('Waiting for debug session to complete...'); 25 | await agent.waitForDeepDebug(); 26 | logger.info('Debug session completed'); 27 | return { status: 'Debug session completed' }; 28 | } else { 29 | logger.info('No debug session in progress'); 30 | return { status: 'No debug session was running' }; 31 | } 32 | } catch (error) { 33 | logger.error('Error waiting for debug session', error); 34 | return { 35 | error: 36 | error instanceof Error 37 | ? `Failed to wait for debug session: ${error.message}` 38 | : 'Unknown error while waiting for debug session', 39 | }; 40 | } 41 | }, 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /worker/agents/tools/toolkit/wait-for-generation.ts: -------------------------------------------------------------------------------- 1 | import { ToolDefinition } from '../types'; 2 | import { StructuredLogger } from '../../../logger'; 3 | import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; 4 | 5 | export function createWaitForGenerationTool( 6 | agent: CodingAgentInterface, 7 | logger: StructuredLogger 8 | ): ToolDefinition, { status: string } | { error: string }> { 9 | return { 10 | type: 'function', 11 | function: { 12 | name: 'wait_for_generation', 13 | description: 14 | 'Wait for code generation to complete. Use when deep_debug returns GENERATION_IN_PROGRESS error. Returns immediately if no generation is running.', 15 | parameters: { 16 | type: 'object', 17 | properties: {}, 18 | required: [], 19 | }, 20 | }, 21 | implementation: async () => { 22 | try { 23 | if (agent.isCodeGenerating()) { 24 | logger.info('Waiting for code generation to complete...'); 25 | await agent.waitForGeneration(); 26 | logger.info('Code generation completed'); 27 | return { status: 'Generation completed' }; 28 | } else { 29 | logger.info('No code generation in progress'); 30 | return { status: 'No generation was running' }; 31 | } 32 | } catch (error) { 33 | logger.error('Error waiting for generation', error); 34 | return { 35 | error: 36 | error instanceof Error 37 | ? `Failed to wait for generation: ${error.message}` 38 | : 'Unknown error while waiting for generation', 39 | }; 40 | } 41 | }, 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /worker/agents/services/interfaces/IFileManager.ts: -------------------------------------------------------------------------------- 1 | import { FileOutputType } from '../../schemas'; 2 | import { FileState } from '../../core/state'; 3 | 4 | /** 5 | * Interface for file management operations 6 | * Abstracts file storage and retrieval 7 | */ 8 | export interface IFileManager { 9 | /** 10 | * Get a generated file by path 11 | */ 12 | getGeneratedFile(path: string): FileOutputType | null; 13 | 14 | /** 15 | * Get all relevant files (template (important) + generated) 16 | */ 17 | getAllRelevantFiles(): FileOutputType[]; 18 | 19 | /** 20 | * Get all files (template (important) + generated) 21 | */ 22 | getAllFiles(): FileOutputType[]; 23 | 24 | /** 25 | * Save a generated file 26 | */ 27 | saveGeneratedFile(file: FileOutputType, commitMessage: string): Promise; 28 | 29 | /** 30 | * Save multiple generated files 31 | */ 32 | saveGeneratedFiles(files: FileOutputType[], commitMessage: string): Promise; 33 | 34 | /** 35 | * Delete files from the file manager 36 | */ 37 | deleteFiles(filePaths: string[]): void; 38 | /** 39 | * Check if file exists (template or generated) 40 | */ 41 | fileExists(path: string): boolean; 42 | 43 | /** 44 | * Get all generated file paths 45 | */ 46 | getGeneratedFilePaths(): string[]; 47 | /** 48 | * Get generated files map 49 | */ 50 | getGeneratedFilesMap(): Record; 51 | 52 | /** 53 | * Get generated files 54 | */ 55 | getGeneratedFiles(): FileOutputType[]; 56 | } -------------------------------------------------------------------------------- /src/hooks/use-infinite-scroll.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | interface UseInfiniteScrollOptions { 4 | threshold?: number; 5 | enabled?: boolean; 6 | onLoadMore?: () => void; 7 | } 8 | 9 | interface UseInfiniteScrollResult { 10 | triggerRef: React.RefObject; 11 | } 12 | 13 | export function useInfiniteScroll({ 14 | threshold = 400, 15 | enabled = true, 16 | onLoadMore 17 | }: UseInfiniteScrollOptions = {}): UseInfiniteScrollResult { 18 | const triggerRef = useRef(null); 19 | const isLoadingRef = useRef(false); 20 | 21 | useEffect(() => { 22 | if (!triggerRef.current || !enabled || !onLoadMore) return; 23 | 24 | const observer = new IntersectionObserver( 25 | (entries) => { 26 | const [entry] = entries; 27 | if (entry.isIntersecting && !isLoadingRef.current) { 28 | isLoadingRef.current = true; 29 | onLoadMore(); 30 | 31 | // Reset after a brief delay to prevent duplicate calls 32 | setTimeout(() => { 33 | isLoadingRef.current = false; 34 | }, 300); 35 | } 36 | }, 37 | { 38 | rootMargin: `${threshold}px`, 39 | threshold: 0 40 | } 41 | ); 42 | 43 | observer.observe(triggerRef.current); 44 | return () => observer.disconnect(); 45 | }, [threshold, enabled, onLoadMore]); 46 | 47 | // Reset loading state when enabled changes to prevent stuck state 48 | useEffect(() => { 49 | if (!enabled) { 50 | isLoadingRef.current = false; 51 | } 52 | }, [enabled]); 53 | 54 | return { triggerRef }; 55 | } -------------------------------------------------------------------------------- /worker/utils/githubUtils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * GitHub-specific Utilities 3 | * Centralized GitHub API helpers 4 | */ 5 | 6 | /** 7 | * Create standardized GitHub API headers with consistent User-Agent 8 | */ 9 | export function createGitHubHeaders( 10 | accessToken: string, 11 | ): Record { 12 | return { 13 | Authorization: `token ${accessToken}`, 14 | 'Content-Type': 'application/json', 15 | Accept: 'application/vnd.github.v3+json', 16 | 'User-Agent': 'Cloudflare-OrangeBuild-OAuth-Integration/1.0', 17 | }; 18 | } 19 | 20 | /** 21 | * Extract error text from GitHub API response 22 | */ 23 | export async function extractGitHubErrorText( 24 | response: Response, 25 | ): Promise { 26 | try { 27 | // Try to parse as JSON first (GitHub usually returns JSON errors) 28 | const contentType = response.headers.get('content-type') || ''; 29 | 30 | if (contentType.includes('application/json')) { 31 | const errorData = (await response.json()) as { 32 | message?: string; 33 | error?: string; 34 | }; 35 | return ( 36 | errorData.message || 37 | errorData.error || 38 | `HTTP ${response.status}` 39 | ); 40 | } else { 41 | // Fallback to plain text 42 | const errorText = await response.text(); 43 | return errorText || `HTTP ${response.status}`; 44 | } 45 | } catch (parseError) { 46 | // If parsing fails, return generic error 47 | return `HTTP ${response.status}`; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/components/ui/radio-group.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" 3 | import { CircleIcon } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | function RadioGroup({ 8 | className, 9 | ...props 10 | }: React.ComponentProps) { 11 | return ( 12 | 17 | ) 18 | } 19 | 20 | function RadioGroupItem({ 21 | className, 22 | ...props 23 | }: React.ComponentProps) { 24 | return ( 25 | 33 | 37 | 38 | 39 | 40 | ) 41 | } 42 | 43 | export { RadioGroup, RadioGroupItem } 44 | -------------------------------------------------------------------------------- /worker/api/controllers/apps/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Type definitions for App Controller responses 3 | */ 4 | 5 | import { App } from '../../../database/schema'; 6 | import { AppWithFavoriteStatus, PaginationInfo, FavoriteToggleResult, EnhancedAppData } from '../../../database/types'; 7 | 8 | /** 9 | * App with extended user and social stats for public listings 10 | */ 11 | export type AppWithUserAndStats = EnhancedAppData & { 12 | updatedAtFormatted: string; 13 | }; 14 | 15 | /** 16 | * Response data for getUserApps, getRecentApps, getFavoriteApps 17 | */ 18 | export interface AppsListData { 19 | apps: AppWithFavoriteStatus[]; 20 | } 21 | 22 | /** 23 | * Response data for getPublicApps 24 | */ 25 | export interface PublicAppsData { 26 | apps: AppWithUserAndStats[]; 27 | pagination: PaginationInfo; 28 | } 29 | 30 | /** 31 | * Response data for getApp 32 | */ 33 | export interface SingleAppData { 34 | app: AppWithFavoriteStatus; 35 | } 36 | 37 | /** 38 | * Response data for toggleFavorite 39 | */ 40 | export type FavoriteToggleData = FavoriteToggleResult; 41 | 42 | /** 43 | * Response data for createApp 44 | */ 45 | export interface CreateAppData { 46 | app: App; 47 | } 48 | 49 | /** 50 | * Response data for updateAppVisibility 51 | */ 52 | export interface UpdateAppVisibilityData { 53 | app: { 54 | id: string; 55 | title: string; 56 | visibility: App['visibility']; 57 | updatedAt: Date | null; 58 | }; 59 | message: string; 60 | } 61 | 62 | /** 63 | * Response data for deleteApp 64 | */ 65 | export interface AppDeleteData { 66 | success: boolean; 67 | message: string; 68 | } -------------------------------------------------------------------------------- /worker/agents/tools/toolkit/wait.ts: -------------------------------------------------------------------------------- 1 | import { ToolDefinition } from '../types'; 2 | import { StructuredLogger } from '../../../logger'; 3 | 4 | type WaitArgs = { 5 | seconds: number; 6 | reason?: string; 7 | }; 8 | 9 | type WaitResult = { message: string }; 10 | 11 | export function createWaitTool( 12 | logger: StructuredLogger, 13 | ): ToolDefinition { 14 | return { 15 | type: 'function' as const, 16 | function: { 17 | name: 'wait', 18 | description: 19 | 'Wait/sleep for a specified number of seconds. Use this after deploying changes when you need the user to interact with the app before checking logs. Typical usage: wait 15-30 seconds after deploy_preview to allow time for user interaction.', 20 | parameters: { 21 | type: 'object', 22 | properties: { 23 | seconds: { 24 | type: 'number', 25 | description: 'Number of seconds to wait (typically 15-30 for user interaction)', 26 | }, 27 | reason: { 28 | type: 'string', 29 | description: 'Optional: why you are waiting (e.g., "Waiting for user to interact with app")', 30 | }, 31 | }, 32 | required: ['seconds'], 33 | }, 34 | }, 35 | implementation: async ({ seconds, reason }) => { 36 | const waitMs = Math.min(Math.max(seconds * 1000, 1000), 60000); // Clamp between 1-60 seconds 37 | const actualSeconds = waitMs / 1000; 38 | 39 | logger.info('Waiting', { seconds: actualSeconds, reason }); 40 | 41 | await new Promise(resolve => setTimeout(resolve, waitMs)); 42 | 43 | return { 44 | message: `Waited ${actualSeconds} seconds${reason ? `: ${reason}` : ''}`, 45 | }; 46 | }, 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /src/assets/provider-logos/openai.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | import importPlugin from 'eslint-plugin-import' 7 | 8 | export default tseslint.config( 9 | { ignores: ['dist', 'wrangler-configuration.d.ts', 'test-diff-formatters/**', '**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx'] }, 10 | { 11 | extends: [ 12 | js.configs.recommended, 13 | ...tseslint.configs.recommended 14 | ], 15 | files: ['src/**/*.{ts,tsx}', 'worker/**/*.{ts,tsx}'], 16 | languageOptions: { 17 | ecmaVersion: 2022, 18 | globals: globals.browser, 19 | }, 20 | plugins: { 21 | 'react-hooks': reactHooks, 22 | 'react-refresh': reactRefresh, 23 | }, 24 | rules: { 25 | ...reactHooks.configs.recommended.rules, 26 | 'react-hooks/exhaustive-deps': 'error', 27 | '@typescript-eslint/no-explicit-any': 'off', 28 | '@typescript-eslint/no-unused-vars': 'off', 29 | 'react-refresh/only-export-components': [ 30 | 'warn', 31 | { allowConstantExport: true }, 32 | ], 33 | }, 34 | settings: { 35 | 'import/resolver': { 36 | typescript: true, 37 | node: true, 38 | }, 39 | }, 40 | }, 41 | // Disable react-refresh/only-export-components for UI components 42 | // as shadcn/ui components commonly export both components and utilities 43 | { 44 | files: ['src/components/ui/**/*.{ts,tsx}'], 45 | rules: { 46 | 'react-refresh/only-export-components': 'off', 47 | }, 48 | }, 49 | ) 50 | -------------------------------------------------------------------------------- /src/components/ui/hover-card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | function HoverCard({ 7 | ...props 8 | }: React.ComponentProps) { 9 | return 10 | } 11 | 12 | function HoverCardTrigger({ 13 | ...props 14 | }: React.ComponentProps) { 15 | return ( 16 | 17 | ) 18 | } 19 | 20 | function HoverCardContent({ 21 | className, 22 | align = "center", 23 | sideOffset = 4, 24 | ...props 25 | }: React.ComponentProps) { 26 | return ( 27 | 28 | 38 | 39 | ) 40 | } 41 | 42 | export { HoverCard, HoverCardTrigger, HoverCardContent } 43 | -------------------------------------------------------------------------------- /worker/agents/tools/toolkit/rename-project.ts: -------------------------------------------------------------------------------- 1 | import { ToolDefinition } from '../types'; 2 | import { StructuredLogger } from '../../../logger'; 3 | import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; 4 | 5 | type RenameArgs = { 6 | newName: string; 7 | }; 8 | 9 | type RenameResult = { projectName: string }; 10 | 11 | export function createRenameProjectTool( 12 | agent: CodingAgentInterface, 13 | logger: StructuredLogger 14 | ): ToolDefinition { 15 | return { 16 | type: 'function' as const, 17 | function: { 18 | name: 'rename_project', 19 | description: 'Rename the project. Lowercase letters, numbers, hyphens, and underscores only. No spaces or dots. Call this alongside queue_request tool to update the codebase', 20 | parameters: { 21 | type: 'object', 22 | additionalProperties: false, 23 | properties: { 24 | newName: { 25 | type: 'string', 26 | minLength: 3, 27 | maxLength: 50, 28 | pattern: '^[a-z0-9-_]+$' 29 | }, 30 | }, 31 | required: ['newName'], 32 | }, 33 | }, 34 | implementation: async (args) => { 35 | logger.info('Renaming project', { newName: args.newName }); 36 | const ok = await agent.updateProjectName(args.newName); 37 | if (!ok) { 38 | throw new Error('Failed to rename project'); 39 | } 40 | return { projectName: args.newName }; 41 | }, 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /worker/middleware/auth/auth.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Authentication Middleware 3 | * Handles JWT validation and session management 4 | */ 5 | 6 | import { AuthUserSession } from '../../types/auth-types'; 7 | import { createLogger } from '../../logger'; 8 | import { AuthService } from '../../database/services/AuthService'; 9 | import { extractToken } from '../../utils/authUtils'; 10 | 11 | const logger = createLogger('AuthMiddleware'); 12 | /** 13 | * Validate JWT token and return user 14 | */ 15 | export async function validateToken( 16 | token: string, 17 | env: Env 18 | ): Promise { 19 | try { 20 | // Use AuthService for token validation and user retrieval 21 | const authService = new AuthService(env); 22 | return authService.validateTokenAndGetUser(token, env); 23 | } catch (error) { 24 | logger.error('Token validation error', error); 25 | return null; 26 | } 27 | } 28 | 29 | /** 30 | * Authentication middleware 31 | */ 32 | export async function authMiddleware( 33 | request: Request, 34 | env: Env 35 | ): Promise { 36 | try { 37 | // Extract token 38 | const token = extractToken(request); 39 | 40 | if (token) { 41 | const userResponse = await validateToken(token, env); 42 | if (userResponse) { 43 | logger.debug('User authenticated', { userId: userResponse.user.id }); 44 | return userResponse; 45 | } 46 | } 47 | 48 | logger.debug('No authentication found'); 49 | return null; 50 | } catch (error) { 51 | logger.error('Auth middleware error', error); 52 | return null; 53 | } 54 | } -------------------------------------------------------------------------------- /src/components/ui/toggle.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as TogglePrimitive from "@radix-ui/react-toggle" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const toggleVariants = cva( 8 | "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-bg-3 hover:text-text-tertiary disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-text-secondary [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-transparent", 13 | outline: 14 | "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-text-secondary", 15 | }, 16 | size: { 17 | default: "h-9 px-2 min-w-9", 18 | sm: "h-8 px-1.5 min-w-8", 19 | lg: "h-10 px-2.5 min-w-10", 20 | }, 21 | }, 22 | defaultVariants: { 23 | variant: "default", 24 | size: "default", 25 | }, 26 | } 27 | ) 28 | 29 | function Toggle({ 30 | className, 31 | variant, 32 | size, 33 | ...props 34 | }: React.ComponentProps & 35 | VariantProps) { 36 | return ( 37 | 42 | ) 43 | } 44 | 45 | export { Toggle, toggleVariants } 46 | -------------------------------------------------------------------------------- /worker/api/routes/secretsRoutes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Secrets Routes 3 | * API routes for user secrets management 4 | */ 5 | 6 | import { SecretsController } from '../controllers/secrets/controller'; 7 | import { Hono } from 'hono'; 8 | import { AppEnv } from '../../types/appenv'; 9 | import { adaptController } from '../honoAdapter'; 10 | import { AuthConfig, setAuthLevel } from '../../middleware/auth/routeAuth'; 11 | 12 | /** 13 | * Setup secrets-related routes 14 | */ 15 | export function setupSecretsRoutes(app: Hono): void { 16 | // Create a sub-router for secrets routes 17 | const secretsRouter = new Hono(); 18 | 19 | // Secrets management routes 20 | secretsRouter.get('/', setAuthLevel(AuthConfig.authenticated), adaptController(SecretsController, SecretsController.getAllSecrets)); 21 | 22 | // DISABLED: BYOK Disabled for security reasons 23 | // secretsRouter.post('/', setAuthLevel(AuthConfig.authenticated), adaptController(SecretsController, SecretsController.storeSecret)); 24 | secretsRouter.post('/', setAuthLevel(AuthConfig.authenticated), (c) => { 25 | return c.json({ message: 'BYOK is not supported for now' }); 26 | }); 27 | // secretsRouter.patch('/:secretId/toggle', setAuthLevel(AuthConfig.authenticated), adaptController(SecretsController, SecretsController.toggleSecret)); 28 | // secretsRouter.delete('/:secretId', setAuthLevel(AuthConfig.authenticated), adaptController(SecretsController, SecretsController.deleteSecret)); 29 | 30 | // Templates route 31 | secretsRouter.get('/templates', setAuthLevel(AuthConfig.authenticated), adaptController(SecretsController, SecretsController.getTemplates)); 32 | 33 | // Mount the router under /api/secrets 34 | app.route('/api/secrets', secretsRouter); 35 | } -------------------------------------------------------------------------------- /worker/api/routes/codegenRoutes.ts: -------------------------------------------------------------------------------- 1 | import { CodingAgentController } from '../controllers/agent/controller'; 2 | import { AppEnv } from '../../types/appenv'; 3 | import { Hono } from 'hono'; 4 | import { AuthConfig, setAuthLevel } from '../../middleware/auth/routeAuth'; 5 | import { adaptController } from '../honoAdapter'; 6 | 7 | /** 8 | * Setup and configure the application router 9 | */ 10 | export function setupCodegenRoutes(app: Hono): void { 11 | // ======================================== 12 | // CODE GENERATION ROUTES 13 | // ======================================== 14 | 15 | // CRITICAL: Create new app - requires full authentication 16 | app.post('/api/agent', setAuthLevel(AuthConfig.authenticated), adaptController(CodingAgentController, CodingAgentController.startCodeGeneration)); 17 | 18 | // ======================================== 19 | // APP EDITING ROUTES (/chat/:id frontend) 20 | // ======================================== 21 | 22 | // WebSocket for app editing - OWNER ONLY (for /chat/:id route) 23 | // Only the app owner should be able to connect and modify via WebSocket 24 | app.get('/api/agent/:agentId/ws', setAuthLevel(AuthConfig.ownerOnly), adaptController(CodingAgentController, CodingAgentController.handleWebSocketConnection)); 25 | 26 | // Connect to existing agent for editing - OWNER ONLY 27 | // Only the app owner should be able to connect for editing purposes 28 | app.get('/api/agent/:agentId/connect', setAuthLevel(AuthConfig.ownerOnly), adaptController(CodingAgentController, CodingAgentController.connectToExistingAgent)); 29 | 30 | app.get('/api/agent/:agentId/preview', setAuthLevel(AuthConfig.authenticated), adaptController(CodingAgentController, CodingAgentController.deployPreview)); 31 | } -------------------------------------------------------------------------------- /worker/agents/tools/toolkit/regenerate-file.ts: -------------------------------------------------------------------------------- 1 | import { ToolDefinition, ErrorResult } from '../types'; 2 | import { StructuredLogger } from '../../../logger'; 3 | import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; 4 | 5 | export type RegenerateFileArgs = { 6 | path: string; 7 | issues: string[]; 8 | }; 9 | 10 | export type RegenerateFileResult = 11 | | { path: string; diff: string } 12 | | ErrorResult; 13 | 14 | export function createRegenerateFileTool( 15 | agent: CodingAgentInterface, 16 | logger: StructuredLogger, 17 | ): ToolDefinition { 18 | return { 19 | type: 'function' as const, 20 | function: { 21 | name: 'regenerate_file', 22 | description: 23 | `Autonomous AI agent that applies surgical fixes to code files. Takes file path and array of specific issues to fix. Returns diff showing changes made. 24 | 25 | CRITICAL: Provide detailed, specific issues - not vague descriptions. See system prompt for full usage guide. These would be implemented by an independent LLM AI agent`, 26 | parameters: { 27 | type: 'object', 28 | properties: { 29 | path: { type: 'string' }, 30 | issues: { type: 'array', items: { type: 'string' } }, 31 | }, 32 | required: ['path', 'issues'], 33 | }, 34 | }, 35 | implementation: async ({ path, issues }) => { 36 | try { 37 | logger.info('Regenerating file', { 38 | path, 39 | issuesCount: issues.length, 40 | }); 41 | return await agent.regenerateFile(path, issues); 42 | } catch (error) { 43 | return { 44 | error: 45 | error instanceof Error 46 | ? `Failed to regenerate file: ${error.message}` 47 | : 'Unknown error occurred while regenerating file', 48 | }; 49 | } 50 | }, 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /src/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const badgeVariants = cva( 8 | "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "border-transparent bg-text-secondary text-bg-3 [a&]:hover:bg-text-secondary/90", 14 | secondary: 15 | "border-transparent bg-bg-2 text-text-secondary [a&]:hover:bg-bg-2/90", 16 | destructive: 17 | "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", 18 | outline: 19 | "text-text-primary [a&]:hover:bg-accent [a&]:hover:text-text-secondary", 20 | }, 21 | }, 22 | defaultVariants: { 23 | variant: "default", 24 | }, 25 | } 26 | ) 27 | 28 | function Badge({ 29 | className, 30 | variant, 31 | asChild = false, 32 | ...props 33 | }: React.ComponentProps<"span"> & 34 | VariantProps & { asChild?: boolean }) { 35 | const Comp = asChild ? Slot : "span" 36 | 37 | return ( 38 | 43 | ) 44 | } 45 | 46 | export { Badge, badgeVariants } 47 | -------------------------------------------------------------------------------- /src/components/shared/TimePeriodSelector.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; 3 | import { Calendar } from 'lucide-react'; 4 | import type { TimePeriod } from '@/api-types'; 5 | 6 | interface TimePeriodSelectorProps { 7 | value: TimePeriod; 8 | onValueChange: (period: TimePeriod) => void; 9 | className?: string; 10 | disabled?: boolean; 11 | showForSort?: 'popular' | 'trending' | 'all'; // Show only for certain sort types 12 | } 13 | 14 | const TIME_PERIODS: Array<{ 15 | value: TimePeriod; 16 | label: string; 17 | shortLabel: string; 18 | }> = [ 19 | { value: 'today', label: 'Today', shortLabel: 'Today' }, 20 | { value: 'week', label: 'This Week', shortLabel: 'Week' }, 21 | { value: 'month', label: 'This Month', shortLabel: 'Month' }, 22 | { value: 'all', label: 'All Time', shortLabel: 'All' }, 23 | ]; 24 | 25 | export const TimePeriodSelector: React.FC = ({ 26 | value, 27 | onValueChange, 28 | className, 29 | disabled, 30 | showForSort = 'all' 31 | }) => { 32 | // Don't show the selector for 'recent' sort - it doesn't make sense 33 | if (showForSort !== 'all' && showForSort !== 'popular' && showForSort !== 'trending') { 34 | return null; 35 | } 36 | 37 | return ( 38 | 39 | 40 | 41 | 42 | 43 | 44 | {TIME_PERIODS.map((period) => ( 45 | 46 | {period.label} 47 | 48 | ))} 49 | 50 | 51 | ); 52 | }; -------------------------------------------------------------------------------- /src/components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as PopoverPrimitive from "@radix-ui/react-popover" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Popover({ 9 | ...props 10 | }: React.ComponentProps) { 11 | return 12 | } 13 | 14 | function PopoverTrigger({ 15 | ...props 16 | }: React.ComponentProps) { 17 | return 18 | } 19 | 20 | function PopoverContent({ 21 | className, 22 | align = "center", 23 | sideOffset = 4, 24 | ...props 25 | }: React.ComponentProps) { 26 | return ( 27 | 28 | 38 | 39 | ) 40 | } 41 | 42 | function PopoverAnchor({ 43 | ...props 44 | }: React.ComponentProps) { 45 | return 46 | } 47 | 48 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } 49 | -------------------------------------------------------------------------------- /shared/types/errors.ts: -------------------------------------------------------------------------------- 1 | import type { RateLimitError } from "worker/services/rate-limit/errors"; 2 | import type { RateLimitType } from "worker/services/rate-limit/config"; 3 | 4 | /** 5 | * Security error types for proper error handling 6 | */ 7 | export enum SecurityErrorType { 8 | UNAUTHORIZED = 'UNAUTHORIZED', 9 | FORBIDDEN = 'FORBIDDEN', 10 | INVALID_TOKEN = 'INVALID_TOKEN', 11 | TOKEN_EXPIRED = 'TOKEN_EXPIRED', 12 | RATE_LIMITED = 'RATE_LIMITED', 13 | INVALID_INPUT = 'INVALID_INPUT', 14 | CSRF_VIOLATION = 'CSRF_VIOLATION', 15 | } 16 | 17 | /** 18 | * Custom security error class 19 | */ 20 | export class SecurityError extends Error { 21 | constructor( 22 | public type: SecurityErrorType, 23 | message: string, 24 | public statusCode: number = 401 25 | ) { 26 | super(message); 27 | this.name = 'SecurityError'; 28 | } 29 | } 30 | 31 | export class RateLimitExceededError extends SecurityError { 32 | public details: RateLimitError; 33 | constructor( 34 | message: string, 35 | public limitType: RateLimitType, 36 | public limit?: number, 37 | public period?: number, 38 | public suggestions?: string[] 39 | ) { 40 | super(SecurityErrorType.RATE_LIMITED, message, 429); 41 | this.name = 'RateLimitExceededError'; 42 | this.details = { 43 | message, 44 | limitType, 45 | limit, 46 | period, 47 | suggestions 48 | }; 49 | } 50 | 51 | static fromRateLimitError(error: RateLimitError): RateLimitExceededError { 52 | return new RateLimitExceededError( 53 | error.message, 54 | error.limitType, 55 | error.limit, 56 | error.period, 57 | error.suggestions 58 | ); 59 | } 60 | } -------------------------------------------------------------------------------- /worker/agents/tools/toolkit/deploy-preview.ts: -------------------------------------------------------------------------------- 1 | import { ErrorResult, ToolDefinition } from '../types'; 2 | import { StructuredLogger } from '../../../logger'; 3 | import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; 4 | 5 | type DeployPreviewArgs = Record; 6 | 7 | type DeployPreviewResult = { message: string } | ErrorResult; 8 | 9 | export function createDeployPreviewTool( 10 | agent: CodingAgentInterface, 11 | logger: StructuredLogger 12 | ): ToolDefinition { 13 | return { 14 | type: 'function' as const, 15 | function: { 16 | name: 'deploy_preview', 17 | description: 18 | 'Uploads and syncs the current application to the preview environment. After deployment, the app is live at the preview URL, but runtime logs (get_logs) will only appear when the user interacts with the app - not automatically after deployment. CRITICAL: After deploying, use wait(20-30) to allow time for user interaction before checking logs. Use force_redeploy=true to force a redeploy (will reset session ID and spawn a new sandbox, is expensive) ', 19 | parameters: { 20 | type: 'object', 21 | properties: { 22 | force_redeploy: { type: 'boolean' }, 23 | }, 24 | required: [], 25 | }, 26 | }, 27 | implementation: async ({ force_redeploy }: { force_redeploy?: boolean }) => { 28 | try { 29 | logger.info('Deploying preview to sandbox environment'); 30 | const result = await agent.deployPreview(undefined, force_redeploy); 31 | logger.info('Preview deployment completed', { result }); 32 | return { message: result }; 33 | } catch (error) { 34 | return { 35 | error: 36 | error instanceof Error 37 | ? `Failed to deploy preview: ${error.message}` 38 | : 'Unknown error occurred while deploying preview', 39 | }; 40 | } 41 | }, 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /worker/agents/core/state.ts: -------------------------------------------------------------------------------- 1 | import type { Blueprint, PhaseConceptType , 2 | FileOutputType, 3 | } from '../schemas'; 4 | // import type { ScreenshotData } from './types'; 5 | import type { ConversationMessage } from '../inferutils/common'; 6 | import type { InferenceContext } from '../inferutils/config.types'; 7 | 8 | export interface FileState extends FileOutputType { 9 | lastDiff: string; 10 | } 11 | 12 | export interface PhaseState extends PhaseConceptType { 13 | // deploymentNeeded: boolean; 14 | completed: boolean; 15 | } 16 | 17 | export enum CurrentDevState { 18 | IDLE, 19 | PHASE_GENERATING, 20 | PHASE_IMPLEMENTING, 21 | REVIEWING, 22 | FINALIZING, 23 | } 24 | 25 | export const MAX_PHASES = 12; 26 | 27 | export interface CodeGenState { 28 | blueprint: Blueprint; 29 | projectName: string, 30 | query: string; 31 | generatedFilesMap: Record; 32 | generatedPhases: PhaseState[]; 33 | commandsHistory?: string[]; // History of commands run 34 | lastPackageJson?: string; // Last package.json file contents 35 | templateName: string; 36 | sandboxInstanceId?: string; 37 | 38 | shouldBeGenerating: boolean; // Persistent flag indicating generation should be active 39 | mvpGenerated: boolean; 40 | reviewingInitiated: boolean; 41 | agentMode: 'deterministic' | 'smart'; 42 | sessionId: string; 43 | hostname: string; 44 | phasesCounter: number; 45 | 46 | pendingUserInputs: string[]; 47 | currentDevState: CurrentDevState; 48 | reviewCycles?: number; // Number of review cycles for code review phase 49 | currentPhase?: PhaseConceptType; // Current phase being worked on 50 | 51 | conversationMessages: ConversationMessage[]; 52 | projectUpdatesAccumulator: string[]; 53 | inferenceContext: InferenceContext; 54 | 55 | lastDeepDebugTranscript: string | null; 56 | } 57 | -------------------------------------------------------------------------------- /worker/agents/tools/toolkit/read-files.ts: -------------------------------------------------------------------------------- 1 | import { ToolDefinition, ErrorResult } from '../types'; 2 | import { StructuredLogger } from '../../../logger'; 3 | import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; 4 | 5 | export type ReadFilesArgs = { 6 | paths: string[]; 7 | timeout?: number; 8 | }; 9 | 10 | export type ReadFilesResult = 11 | | { files: { path: string; content: string }[] } 12 | | ErrorResult; 13 | 14 | export function createReadFilesTool( 15 | agent: CodingAgentInterface, 16 | logger: StructuredLogger, 17 | ): ToolDefinition { 18 | return { 19 | type: 'function' as const, 20 | function: { 21 | name: 'read_files', 22 | description: 23 | 'Read file contents by exact RELATIVE paths (sandbox pwd = project root). Prefer batching multiple paths in a single call to reduce overhead. Target all relevant files useful for understanding current context', 24 | parameters: { 25 | type: 'object', 26 | properties: { 27 | paths: { type: 'array', items: { type: 'string' } }, 28 | timeout: { type: 'number', default: 30000 }, 29 | }, 30 | required: ['paths'], 31 | }, 32 | }, 33 | implementation: async ({ paths, timeout = 30000 }) => { 34 | try { 35 | logger.info('Reading files', { count: paths.length, timeout }); 36 | 37 | const timeoutPromise = new Promise((_, reject) => 38 | setTimeout(() => reject(new Error(`Read files operation timed out after ${timeout}ms`)), timeout) 39 | ); 40 | 41 | return await Promise.race([ 42 | agent.readFiles(paths), 43 | timeoutPromise 44 | ]); 45 | } catch (error) { 46 | return { 47 | error: 48 | error instanceof Error 49 | ? `Failed to read files: ${error.message}` 50 | : 'Unknown error occurred while reading files', 51 | }; 52 | } 53 | }, 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /src/components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const alertVariants = cva( 7 | "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", 8 | { 9 | variants: { 10 | variant: { 11 | default: "bg-bg-4 text-text-primary", 12 | destructive: 13 | "text-destructive bg-bg-4 [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", 14 | }, 15 | }, 16 | defaultVariants: { 17 | variant: "default", 18 | }, 19 | } 20 | ) 21 | 22 | function Alert({ 23 | className, 24 | variant, 25 | ...props 26 | }: React.ComponentProps<"div"> & VariantProps) { 27 | return ( 28 | 34 | ) 35 | } 36 | 37 | function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { 38 | return ( 39 | 47 | ) 48 | } 49 | 50 | function AlertDescription({ 51 | className, 52 | ...props 53 | }: React.ComponentProps<"div">) { 54 | return ( 55 | 63 | ) 64 | } 65 | 66 | export { Alert, AlertTitle, AlertDescription } 67 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | // import { sentryVitePlugin } from '@sentry/vite-plugin'; 2 | import { defineConfig } from 'vite'; 3 | import react from '@vitejs/plugin-react'; 4 | import svgr from 'vite-plugin-svgr'; 5 | import path from 'path'; 6 | 7 | import { cloudflare } from '@cloudflare/vite-plugin'; 8 | import tailwindcss from '@tailwindcss/vite'; 9 | 10 | // https://vite.dev/config/ 11 | export default defineConfig({ 12 | optimizeDeps: { 13 | exclude: ['format', 'editor.all'], 14 | include: ['monaco-editor/esm/vs/editor/editor.api'], 15 | force: true, 16 | }, 17 | 18 | // build: { 19 | // rollupOptions: { 20 | // output: { 21 | // advancedChunks: { 22 | // groups: [{name: 'vendor', test: /node_modules/}] 23 | // } 24 | // } 25 | // } 26 | // }, 27 | plugins: [ 28 | react(), 29 | svgr(), 30 | cloudflare({ 31 | configPath: 'wrangler.jsonc', 32 | }), 33 | tailwindcss(), 34 | // sentryVitePlugin({ 35 | // org: 'cloudflare-0u', 36 | // project: 'javascript-react', 37 | // }), 38 | ], 39 | 40 | resolve: { 41 | alias: { 42 | debug: 'debug/src/browser', 43 | '@': path.resolve(__dirname, './src'), 44 | 'shared': path.resolve(__dirname, './shared'), 45 | 'worker': path.resolve(__dirname, './worker'), 46 | }, 47 | }, 48 | 49 | // Configure for Prisma + Cloudflare Workers compatibility 50 | define: { 51 | // Ensure proper module definitions for Cloudflare Workers context 52 | 'process.env.NODE_ENV': JSON.stringify( 53 | process.env.NODE_ENV || 'development', 54 | ), 55 | global: 'globalThis', 56 | // '__filename': '""', 57 | // '__dirname': '""', 58 | }, 59 | 60 | worker: { 61 | // Handle Prisma in worker context for development 62 | format: 'es', 63 | }, 64 | 65 | server: { 66 | allowedHosts: true, 67 | }, 68 | 69 | // Clear cache more aggressively 70 | cacheDir: 'node_modules/.vite', 71 | }); 72 | -------------------------------------------------------------------------------- /worker/services/cache/KVCache.ts: -------------------------------------------------------------------------------- 1 | export interface KVCacheOptions { 2 | ttl?: number; // seconds 3 | prefix?: string; 4 | } 5 | 6 | export class KVCache { 7 | constructor(private kv: KVNamespace) {} 8 | 9 | private generateKey(prefix: string, key: string): string { 10 | return `cache-${prefix}:${key}`; 11 | } 12 | 13 | async get(prefix: string, key: string): Promise { 14 | const fullKey = this.generateKey(prefix, key); 15 | const value = await this.kv.get(fullKey, 'json'); 16 | return value as T | null; 17 | } 18 | 19 | async set(prefix: string, key: string, value: T, ttl?: number): Promise { 20 | const fullKey = this.generateKey(prefix, key); 21 | const options: KVNamespacePutOptions = {}; 22 | if (ttl) { 23 | options.expirationTtl = ttl; 24 | } 25 | await this.kv.put(fullKey, JSON.stringify(value), options); 26 | } 27 | 28 | async delete(prefix: string, key: string): Promise { 29 | const fullKey = this.generateKey(prefix, key); 30 | await this.kv.delete(fullKey); 31 | } 32 | 33 | async deleteByPrefix(prefix: string): Promise { 34 | let cursor: string | undefined = undefined; 35 | do { 36 | const list: KVNamespaceListResult = await this.kv.list({ prefix: `cache-${prefix}:`, cursor }); 37 | await Promise.all(list.keys.map((key: KVNamespaceListKey) => this.kv.delete(key.name))); 38 | cursor = list.list_complete ? undefined : list.cursor; 39 | } while (cursor); 40 | } 41 | 42 | async invalidate(patterns: string[]): Promise { 43 | await Promise.all(patterns.map(pattern => this.deleteByPrefix(pattern))); 44 | } 45 | } 46 | 47 | export function createKVCache(env: Env): KVCache { 48 | const kv = env.VibecoderStore; 49 | return new KVCache(kv); 50 | } 51 | -------------------------------------------------------------------------------- /src/components/image-upload-button.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, type ChangeEvent } from 'react'; 2 | import { ImagePlus } from 'lucide-react'; 3 | import { SUPPORTED_IMAGE_MIME_TYPES } from '@/api-types'; 4 | 5 | export interface ImageUploadButtonProps { 6 | onFilesSelected: (files: File[]) => void; 7 | disabled?: boolean; 8 | multiple?: boolean; 9 | className?: string; 10 | iconClassName?: string; 11 | } 12 | 13 | /** 14 | * Button component for uploading images 15 | */ 16 | export function ImageUploadButton({ 17 | onFilesSelected, 18 | disabled = false, 19 | multiple = true, 20 | className = '', 21 | iconClassName = 'size-4', 22 | }: ImageUploadButtonProps) { 23 | const fileInputRef = useRef(null); 24 | 25 | const handleClick = () => { 26 | fileInputRef.current?.click(); 27 | }; 28 | 29 | const handleFileChange = (e: ChangeEvent) => { 30 | const files = Array.from(e.target.files || []); 31 | if (files.length > 0) { 32 | onFilesSelected(files); 33 | } 34 | // Reset input so the same file can be selected again 35 | if (fileInputRef.current) { 36 | fileInputRef.current.value = ''; 37 | } 38 | }; 39 | 40 | return ( 41 | <> 42 | 51 | 59 | 60 | 61 | > 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /src/components/ui/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function ScrollArea({ 9 | className, 10 | children, 11 | ...props 12 | }: React.ComponentProps) { 13 | return ( 14 | 19 | 23 | {children} 24 | 25 | 26 | 27 | 28 | ) 29 | } 30 | 31 | function ScrollBar({ 32 | className, 33 | orientation = "vertical", 34 | ...props 35 | }: React.ComponentProps) { 36 | return ( 37 | 50 | 54 | 55 | ) 56 | } 57 | 58 | export { ScrollArea, ScrollBar } 59 | -------------------------------------------------------------------------------- /worker/services/cache/wrapper.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Cache wrapper for controller methods without decorators 3 | */ 4 | 5 | import { CacheService } from './CacheService'; 6 | import type { RouteContext } from '../../api/types/route-context'; 7 | import type { BaseController } from '../../api/controllers/baseController'; 8 | 9 | interface CacheOptions { 10 | ttlSeconds: number; 11 | tags?: string[]; 12 | } 13 | 14 | type ControllerMethod = ( 15 | this: T, 16 | request: Request, 17 | env: Env, 18 | ctx: ExecutionContext, 19 | context: RouteContext 20 | ) => Promise; 21 | 22 | /** 23 | * Wraps a controller method with caching functionality 24 | * Works without experimental decorators - pure higher-order function 25 | */ 26 | export function withCache( 27 | method: ControllerMethod, 28 | options: CacheOptions 29 | ): ControllerMethod { 30 | const cacheService = new CacheService(); 31 | 32 | return async function ( 33 | this: T, 34 | request: Request, 35 | env: Env, 36 | ctx: ExecutionContext, 37 | context: RouteContext 38 | ): Promise { 39 | // Try to get user for cache key differentiation 40 | let userId = context?.user?.id; 41 | 42 | // For public endpoints, try to get optional user if not already available 43 | if (!userId && 'getOptionalUser' in this && typeof this.getOptionalUser === 'function') { 44 | try { 45 | const user = await this.getOptionalUser(request, env); 46 | userId = user?.id; 47 | } catch { 48 | // Ignore auth errors for public endpoints 49 | } 50 | } 51 | 52 | // Use request directly as cache key (Cloudflare Workers way) 53 | const cacheKeyOrRequest = request; 54 | 55 | // Use cache wrapper 56 | return cacheService.withCache( 57 | cacheKeyOrRequest, 58 | () => method.call(this, request, env, ctx, context), 59 | { ttlSeconds: options.ttlSeconds, tags: options.tags } 60 | ); 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /src/routes/chat/utils/project-stage-helpers.ts: -------------------------------------------------------------------------------- 1 | export interface ProjectStage { 2 | id: 'bootstrap' | 'blueprint' | 'code'; 3 | title: string; 4 | status: 'pending' | 'active' | 'completed' | 'error'; 5 | metadata?: string; 6 | } 7 | 8 | export const initialStages: ProjectStage[] = [ 9 | { 10 | id: 'bootstrap', 11 | title: 'Bootstrapping project', 12 | status: 'active', 13 | }, 14 | { 15 | id: 'blueprint', 16 | title: 'Generating Blueprint', 17 | status: 'pending', 18 | }, 19 | { id: 'code', title: 'Generating code', status: 'pending' }, 20 | ]; 21 | 22 | /** 23 | * Update a specific stage's status and metadata 24 | */ 25 | export function updateStage( 26 | stages: ProjectStage[], 27 | stageId: ProjectStage['id'], 28 | updates: Partial> 29 | ): ProjectStage[] { 30 | return stages.map(stage => 31 | stage.id === stageId 32 | ? { ...stage, ...updates } 33 | : stage 34 | ); 35 | } 36 | 37 | /** 38 | * Complete multiple stages at once 39 | */ 40 | export function completeStages( 41 | stages: ProjectStage[], 42 | stageIds: ProjectStage['id'][] 43 | ): ProjectStage[] { 44 | return stages.map(stage => 45 | stageIds.includes(stage.id) 46 | ? { ...stage, status: 'completed' as const } 47 | : stage 48 | ); 49 | } 50 | 51 | /** 52 | * Get the status of a specific stage 53 | */ 54 | export function getStageStatus( 55 | stages: ProjectStage[], 56 | stageId: ProjectStage['id'] 57 | ): ProjectStage['status'] | undefined { 58 | return stages.find(stage => stage.id === stageId)?.status; 59 | } 60 | 61 | /** 62 | * Check if a stage is completed 63 | */ 64 | export function isStageCompleted( 65 | stages: ProjectStage[], 66 | stageId: ProjectStage['id'] 67 | ): boolean { 68 | return getStageStatus(stages, stageId) === 'completed'; 69 | } 70 | -------------------------------------------------------------------------------- /worker/agents/utils/codeSerializers.ts: -------------------------------------------------------------------------------- 1 | import { SCOFFormat } from '../output-formats/streaming-formats/scof'; 2 | import { FileOutputType } from '../schemas'; 3 | 4 | export enum CodeSerializerType { 5 | SIMPLE = 'simple', 6 | SCOF = 'scof', 7 | } 8 | 9 | export type CodeSerializer = (files: FileOutputType[]) => string; 10 | 11 | function detectLanguage(filePath: string): string { 12 | const extension = filePath.split('.').pop() ?? ''; 13 | switch (extension) { 14 | case 'js': 15 | return 'javascript'; 16 | case 'ts': 17 | return 'typescript'; 18 | case 'tsx': 19 | return 'typescript'; 20 | case 'jsx': 21 | return 'javascript'; 22 | case 'json': 23 | return 'json'; 24 | case 'html': 25 | return 'html'; 26 | case 'css': 27 | return 'css'; 28 | case 'md': 29 | return 'markdown'; 30 | case 'sh': 31 | return 'shell'; 32 | default: 33 | return ''; 34 | } 35 | } 36 | 37 | function simpleSerializer(files: FileOutputType[]): string { 38 | /* 39 | # File Name: 40 | # File Purpose: 41 | \`\`\` 42 | 43 | \`\`\` 44 | */ 45 | return files 46 | .map((file) => { 47 | return `# File Name: ${file.filePath}\n# File Purpose: ${file.filePurpose}\n\`\`\`${detectLanguage(file.filePath)}\n${file.fileContents}\n\`\`\`\n`; 48 | }) 49 | .join('\n\n'); 50 | } 51 | 52 | function scofSerializer(files: FileOutputType[]): string { 53 | return new SCOFFormat().serialize( 54 | files.map((file) => { 55 | return { 56 | ...file, 57 | format: 'full_content', 58 | }; 59 | }), 60 | ); 61 | } 62 | 63 | export const CODE_SERIALIZERS: Record = { 64 | [CodeSerializerType.SIMPLE]: simpleSerializer, 65 | [CodeSerializerType.SCOF]: scofSerializer, 66 | }; 67 | -------------------------------------------------------------------------------- /knip.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/knip@5/schema.json", 3 | "workspaces": { 4 | ".": { 5 | "entry": [ 6 | "src/main.tsx", 7 | "worker/index.ts", 8 | "scripts/**/*.ts", 9 | "container/**/*.ts", 10 | "debug-tools/**/*.{ts,py}" 11 | ], 12 | "project": [ 13 | "**/*.{ts,tsx,js,jsx,mjs,cjs}" 14 | ], 15 | "ignore": [ 16 | "**/*.d.ts", 17 | "dist/**", 18 | "build/**", 19 | ".wrangler/**", 20 | "migrations/**/*.sql", 21 | "public/**" 22 | ], 23 | "ignoreDependencies": [ 24 | "@rolldown/binding-linux-x64-gnu", 25 | "prettier-plugin-tailwindcss", 26 | "tw-animate-css", 27 | "@tailwindcss/typography", 28 | "@tailwindcss/vite" 29 | ], 30 | "ignoreBinaries": [ 31 | "wrangler", 32 | "drizzle-kit", 33 | "tsx", 34 | "vitest" 35 | ] 36 | } 37 | }, 38 | "rules": { 39 | "files": "error", 40 | "dependencies": "error", 41 | "devDependencies": "error", 42 | "optionalPeerDependencies": "warn", 43 | "unlisted": "error", 44 | "binaries": "error", 45 | "unresolved": "error", 46 | "exports": "error", 47 | "types": "error", 48 | "nsExports": "error", 49 | "duplicates": "warn", 50 | "enumMembers": "error", 51 | "classMembers": "error" 52 | }, 53 | "exclude": [ 54 | "unresolved" 55 | ], 56 | "ignoreExportsUsedInFile": true, 57 | "typescript": { 58 | "config": ["tsconfig.json", "tsconfig.*.json"] 59 | }, 60 | "vite": { 61 | "config": ["vite.config.ts"] 62 | }, 63 | "eslint": { 64 | "config": ["eslint.config.js"] 65 | }, 66 | "vitest": { 67 | "config": ["vitest.config.ts"], 68 | "entry": ["**/*.{test,spec}.{js,jsx,ts,tsx}"] 69 | }, 70 | "jest": { 71 | "config": ["jest.config.{js,ts,mjs,cjs,json}"], 72 | "entry": ["**/__tests__/**", "**/*.{test,spec}.{js,jsx,ts,tsx}"] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/components/ui/tooltip.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as TooltipPrimitive from "@radix-ui/react-tooltip" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | function TooltipProvider({ 7 | delayDuration = 0, 8 | ...props 9 | }: React.ComponentProps) { 10 | return ( 11 | 16 | ) 17 | } 18 | 19 | function Tooltip({ 20 | ...props 21 | }: React.ComponentProps) { 22 | return ( 23 | 24 | 25 | 26 | ) 27 | } 28 | 29 | function TooltipTrigger({ 30 | ...props 31 | }: React.ComponentProps) { 32 | return 33 | } 34 | 35 | function TooltipContent({ 36 | className, 37 | sideOffset = 0, 38 | children, 39 | ...props 40 | }: React.ComponentProps) { 41 | return ( 42 | 43 | 52 | {children} 53 | 54 | 55 | ) 56 | } 57 | 58 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } 59 | -------------------------------------------------------------------------------- /worker/api/controllers/modelProviders/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Model Providers API Types 3 | * Types for custom model provider CRUD operations 4 | */ 5 | 6 | import type { UserModelProvider } from '../../../database/schema'; 7 | import type { ApiResponse } from '../types'; 8 | 9 | // Response data types 10 | export interface ModelProvidersListData { 11 | providers: UserModelProvider[]; 12 | } 13 | 14 | export interface ModelProviderData { 15 | provider: UserModelProvider; 16 | } 17 | 18 | export interface ModelProviderCreateData { 19 | provider: UserModelProvider; 20 | } 21 | 22 | export interface ModelProviderUpdateData { 23 | provider: UserModelProvider; 24 | } 25 | 26 | export interface ModelProviderDeleteData { 27 | success: boolean; 28 | providerId: string; 29 | } 30 | 31 | export interface ModelProviderTestData { 32 | success: boolean; 33 | error?: string; 34 | responseTime?: number; 35 | } 36 | 37 | // Request input types 38 | export interface CreateProviderRequest { 39 | name: string; 40 | baseUrl: string; 41 | apiKey: string; 42 | } 43 | 44 | export interface UpdateProviderRequest { 45 | name?: string; 46 | baseUrl?: string; 47 | apiKey?: string; 48 | isActive?: boolean; 49 | } 50 | 51 | export interface TestProviderRequest { 52 | providerId?: string; // If testing existing provider 53 | baseUrl?: string; // If testing new provider config 54 | apiKey?: string; // If testing new provider config 55 | } 56 | 57 | // API response types 58 | export type ModelProvidersListResponse = ApiResponse; 59 | export type ModelProviderResponse = ApiResponse; 60 | export type ModelProviderCreateResponse = ApiResponse; 61 | export type ModelProviderUpdateResponse = ApiResponse; 62 | export type ModelProviderDeleteResponse = ApiResponse; 63 | export type ModelProviderTestResponse = ApiResponse; -------------------------------------------------------------------------------- /docs/v1dev-environment.postman_environment.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "v1dev-environment-uuid", 3 | "name": "V1 Dev Environment", 4 | "values": [ 5 | { 6 | "key": "baseUrl", 7 | "value": "https://your-production-domain.com", 8 | "enabled": true, 9 | "type": "default", 10 | "description": "Base URL for V1 Dev API - Update this to your actual domain" 11 | }, 12 | { 13 | "key": "localUrl", 14 | "value": "http://localhost:8787", 15 | "enabled": false, 16 | "type": "default", 17 | "description": "Local development URL (Wrangler dev server)" 18 | }, 19 | { 20 | "key": "csrf_token", 21 | "value": "", 22 | "enabled": true, 23 | "type": "secret", 24 | "description": "CSRF token - automatically populated by requests" 25 | }, 26 | { 27 | "key": "user_id", 28 | "value": "", 29 | "enabled": true, 30 | "type": "default", 31 | "description": "Current user ID - automatically populated after login" 32 | }, 33 | { 34 | "key": "session_id", 35 | "value": "", 36 | "enabled": true, 37 | "type": "secret", 38 | "description": "Current session ID - automatically populated after login" 39 | }, 40 | { 41 | "key": "agent_id", 42 | "value": "", 43 | "enabled": true, 44 | "type": "default", 45 | "description": "Current agent/app ID - automatically populated when creating apps" 46 | }, 47 | { 48 | "key": "app_id", 49 | "value": "", 50 | "enabled": true, 51 | "type": "default", 52 | "description": "Current app ID - automatically populated when working with specific apps" 53 | }, 54 | { 55 | "key": "provider_id", 56 | "value": "", 57 | "enabled": true, 58 | "type": "default", 59 | "description": "Model provider ID - set this when testing provider endpoints" 60 | }, 61 | { 62 | "key": "secret_id", 63 | "value": "", 64 | "enabled": true, 65 | "type": "default", 66 | "description": "Secret ID - set this when testing secret endpoints" 67 | } 68 | ], 69 | "_postman_variable_scope": "environment" 70 | } -------------------------------------------------------------------------------- /src/hooks/use-platform-status.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import type { PlatformStatusData } from '@/api-types'; 3 | import { apiClient } from '@/lib/api-client'; 4 | 5 | const defaultStatus: PlatformStatusData = { 6 | globalUserMessage: '', 7 | changeLogs: '', 8 | hasActiveMessage: false, 9 | }; 10 | 11 | let cachedStatus: PlatformStatusData | null = null; 12 | let inFlightRequest: Promise | null = null; 13 | 14 | async function fetchStatus(): Promise { 15 | try { 16 | const response = await apiClient.getPlatformStatus(true); 17 | if (response.success && response.data) { 18 | cachedStatus = response.data; 19 | return; 20 | } 21 | } catch (error) { 22 | console.debug('Failed to load platform status', error); 23 | } 24 | 25 | if (!cachedStatus) { 26 | cachedStatus = defaultStatus; 27 | } 28 | } 29 | 30 | export function usePlatformStatus() { 31 | const [status, setStatus] = useState(cachedStatus ?? defaultStatus); 32 | 33 | useEffect(() => { 34 | let disposed = false; 35 | 36 | if (!cachedStatus) { 37 | if (!inFlightRequest) { 38 | inFlightRequest = fetchStatus().finally(() => { 39 | inFlightRequest = null; 40 | }); 41 | } 42 | 43 | inFlightRequest 44 | .then(() => { 45 | if (!disposed && cachedStatus) { 46 | setStatus(cachedStatus); 47 | } 48 | }) 49 | .catch(() => { 50 | if (!disposed) { 51 | setStatus(defaultStatus); 52 | } 53 | }); 54 | } else { 55 | setStatus(cachedStatus); 56 | } 57 | 58 | return () => { 59 | disposed = true; 60 | }; 61 | }, []); 62 | 63 | return { 64 | status, 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | 2 | export type LogLevel = 'debug' | 'info' | 'warn' | 'error'; 3 | 4 | interface LoggerOptions { 5 | level?: LogLevel; 6 | prefix?: string; 7 | enabled?: boolean; 8 | } 9 | 10 | export class Logger { 11 | private level: LogLevel; 12 | private prefix: string; 13 | private enabled: boolean; 14 | 15 | private static levels: Record = { 16 | debug: 0, 17 | info: 1, 18 | warn: 2, 19 | error: 3, 20 | }; 21 | 22 | constructor(options: LoggerOptions = {}) { 23 | this.level = options.level ?? 'info'; 24 | this.prefix = options.prefix ?? ''; 25 | this.enabled = options.enabled ?? true; 26 | } 27 | 28 | private shouldLog(level: LogLevel): boolean { 29 | return this.enabled && Logger.levels[level] >= Logger.levels[this.level]; 30 | } 31 | 32 | private formatMessage(_level: LogLevel, args: unknown[]): unknown[] { 33 | // const time = new Date().toISOString(); 34 | const prefix = this.prefix ? `[${this.prefix}]` : ''; 35 | // return [`[${time}] ${prefix} [${level.toUpperCase()}]`, ...args]; 36 | return [prefix, ...args]; 37 | // return args; 38 | } 39 | 40 | debug(...args: unknown[]) { 41 | if (this.shouldLog('debug')) { 42 | console.debug(...this.formatMessage('debug', args)); 43 | } 44 | } 45 | 46 | info(...args: unknown[]) { 47 | if (this.shouldLog('info')) { 48 | console.info(...this.formatMessage('info', args)); 49 | } 50 | } 51 | 52 | warn(...args: unknown[]) { 53 | if (this.shouldLog('warn')) { 54 | console.warn(...this.formatMessage('warn', args)); 55 | } 56 | } 57 | 58 | error(...args: unknown[]) { 59 | if (this.shouldLog('error')) { 60 | console.error(...this.formatMessage('error', args)); 61 | } 62 | } 63 | 64 | setLevel(level: LogLevel) { 65 | this.level = level; 66 | } 67 | 68 | enable() { 69 | this.enabled = true; 70 | } 71 | 72 | disable() { 73 | this.enabled = false; 74 | } 75 | } 76 | 77 | export const logger = new Logger({ 78 | level: import.meta.env.DEV ? 'debug' : 'info', 79 | }); 80 | -------------------------------------------------------------------------------- /src/components/analytics/cost-display.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Clean Cost Display Component 3 | * Minimal, theme-integrated cost display for analytics data 4 | */ 5 | 6 | import { DollarSign } from 'lucide-react'; 7 | import { cn } from '@/lib/utils'; 8 | import { formatCost, type AnalyticsDisplayProps } from '@/utils/analytics'; 9 | 10 | interface CostDisplayProps extends AnalyticsDisplayProps { 11 | loading?: boolean; 12 | variant?: 'inline' | 'card'; 13 | className?: string; 14 | label?: string; 15 | } 16 | 17 | export function CostDisplay({ 18 | cost, 19 | loading = false, 20 | variant = 'inline', 21 | className, 22 | label 23 | }: CostDisplayProps) { 24 | if (loading) { 25 | return ( 26 | 33 | ); 34 | } 35 | 36 | if (variant === 'inline') { 37 | return ( 38 | 43 | 44 | 45 | {formatCost(cost)} 46 | 47 | 48 | ); 49 | } 50 | 51 | // Card variant for future use 52 | if (variant === 'card') { 53 | return ( 54 | 58 | 59 | 60 | {label || 'Total Cost'} 61 | 62 | 63 | {formatCost(cost)} 64 | 65 | 66 | ); 67 | } 68 | 69 | return null; 70 | } -------------------------------------------------------------------------------- /src/hooks/use-auto-scroll.ts: -------------------------------------------------------------------------------- 1 | import { RefObject, useCallback, useEffect, useRef } from 'react'; 2 | 3 | type AutoScrollOptions = { 4 | enabled?: boolean; 5 | behavior?: ScrollBehavior; 6 | bottomThreshold?: number; // px distance considered "at bottom" 7 | watch?: ReadonlyArray; 8 | }; 9 | 10 | export function useAutoScroll( 11 | containerRef: RefObject, 12 | { enabled = true, behavior = 'auto', bottomThreshold = 128, watch }: AutoScrollOptions = {} 13 | ): { scrollToBottom: () => void } { 14 | const isAtBottomRef = useRef(true); 15 | 16 | const scrollToBottom = useCallback(() => { 17 | const el = containerRef.current; 18 | if (!el) return; 19 | el.scrollTo({ top: el.scrollHeight, behavior }); 20 | }, [behavior, containerRef]); 21 | 22 | useEffect(() => { 23 | const el = containerRef.current; 24 | if (!el || !enabled) return; 25 | 26 | const onScroll = () => { 27 | const distance = el.scrollHeight - el.scrollTop - el.clientHeight; 28 | isAtBottomRef.current = distance <= bottomThreshold; 29 | }; 30 | 31 | // initial stick 32 | isAtBottomRef.current = true; 33 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 34 | // setTimeout(scrollToBottom, 0); 35 | Promise.resolve().then(scrollToBottom); 36 | 37 | el.addEventListener('scroll', onScroll, { passive: true }); 38 | 39 | const ro = new ResizeObserver(() => { 40 | if (isAtBottomRef.current) scrollToBottom(); 41 | }); 42 | ro.observe(el); 43 | 44 | return () => { 45 | el.removeEventListener('scroll', onScroll); 46 | ro.disconnect(); 47 | }; 48 | }, [containerRef, enabled, bottomThreshold, scrollToBottom]); 49 | 50 | // Trigger scroll when watched values change, but only if user is near bottom 51 | useEffect(() => { 52 | if (!enabled) return; 53 | if (watch === undefined) return; 54 | if (isAtBottomRef.current) scrollToBottom(); 55 | }, [enabled, watch, scrollToBottom]); 56 | 57 | return { scrollToBottom }; 58 | } 59 | -------------------------------------------------------------------------------- /src/components/shared/AppFiltersForm.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Search } from 'lucide-react'; 3 | import { Button } from '@/components/ui/button'; 4 | import { Input } from '@/components/ui/input'; 5 | import type { TimePeriod, AppSortOption } from '@/api-types'; 6 | 7 | interface AppFiltersFormProps { 8 | // Search props 9 | searchQuery: string; 10 | onSearchChange: (query: string) => void; 11 | onSearchSubmit: (e: React.FormEvent) => void; 12 | searchPlaceholder?: string; 13 | showSearchButton?: boolean; 14 | 15 | // Framework filter props 16 | filterFramework: string; 17 | onFrameworkChange: (framework: string) => void; 18 | 19 | // Visibility filter props (optional - only for user apps) 20 | filterVisibility?: string; 21 | onVisibilityChange?: (visibility: string) => void; 22 | showVisibility?: boolean; 23 | 24 | // Time period props (conditional) 25 | period?: TimePeriod; 26 | onPeriodChange?: (period: TimePeriod) => void; 27 | sortBy?: AppSortOption; 28 | 29 | // Layout props 30 | className?: string; 31 | } 32 | 33 | 34 | export const AppFiltersForm: React.FC = ({ 35 | searchQuery, 36 | onSearchChange, 37 | onSearchSubmit, 38 | searchPlaceholder = 'Search apps...', 39 | showSearchButton = false, 40 | className = '' 41 | }) => { 42 | 43 | return ( 44 | 45 | 46 | 47 | 48 | onSearchChange(e.target.value)} 53 | className="pl-10 bg-bg-4 w-90" 54 | /> 55 | 56 | 57 | 58 | {showSearchButton && ( 59 | 60 | Search 61 | 62 | )} 63 | 64 | 65 | ); 66 | }; -------------------------------------------------------------------------------- /container/example-usage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Example usage of CLI tools outside Docker environment 4 | # This script demonstrates how to use the CLI tools in any directory 5 | 6 | echo "=== CLI Tools Configuration Example ===" 7 | echo 8 | 9 | # Set custom data directory (optional) 10 | export CLI_DATA_DIR="./monitoring-data" 11 | 12 | # Set custom database paths (optional) 13 | export CLI_ERROR_DB_PATH="./monitoring-data/runtime-errors.db" 14 | export CLI_LOG_DB_PATH="./monitoring-data/process-logs.db" 15 | 16 | echo "Data directory: $CLI_DATA_DIR" 17 | echo "Error database: $CLI_ERROR_DB_PATH" 18 | echo "Log database: $CLI_LOG_DB_PATH" 19 | echo 20 | 21 | # Example: Start monitoring a process 22 | echo "=== Starting Process Monitor ===" 23 | bun run cli-tools.ts process start --instance-id "my-app" --port 3000 -- npm run dev & 24 | MONITOR_PID=$! 25 | 26 | # Wait a bit for the process to start 27 | sleep 2 28 | 29 | # Example: Check process status 30 | echo "=== Process Status ===" 31 | bun run cli-tools.ts process status --instance-id "my-app" 32 | echo 33 | 34 | # Example: List recent errors 35 | echo "=== Recent Errors ===" 36 | bun run cli-tools.ts errors list --instance-id "my-app" --limit 10 --format table 37 | echo 38 | 39 | # Example: Get recent logs 40 | echo "=== Recent Logs ===" 41 | bun run cli-tools.ts logs get --instance-id "my-app" --format raw 42 | echo 43 | 44 | # Example: Get error statistics 45 | echo "=== Error Statistics ===" 46 | bun run cli-tools.ts errors stats --instance-id "my-app" 47 | echo 48 | 49 | # Example: Get log statistics 50 | echo "=== Log Statistics ===" 51 | bun run cli-tools.ts logs stats --instance-id "my-app" 52 | echo 53 | 54 | # Example: Get all logs and reset (useful for periodic log collection) 55 | echo "=== All Logs (and reset) ===" 56 | bun run cli-tools.ts logs get --instance-id "my-app" --format raw --reset > collected-logs.txt 57 | echo "Logs saved to collected-logs.txt" 58 | echo 59 | 60 | # Cleanup: Stop the monitor 61 | echo "=== Stopping Monitor ===" 62 | bun run cli-tools.ts process stop --instance-id "my-app" 63 | 64 | echo "=== Example Complete ===" -------------------------------------------------------------------------------- /worker/api/routes/index.ts: -------------------------------------------------------------------------------- 1 | import { setupAuthRoutes } from './authRoutes'; 2 | import { setupAppRoutes } from './appRoutes'; 3 | import { setupUserRoutes } from './userRoutes'; 4 | import { setupStatsRoutes } from './statsRoutes'; 5 | import { setupAnalyticsRoutes } from './analyticsRoutes'; 6 | import { setupSecretsRoutes } from './secretsRoutes'; 7 | import { setupModelConfigRoutes } from './modelConfigRoutes'; 8 | import { setupModelProviderRoutes } from './modelProviderRoutes'; 9 | import { setupGitHubExporterRoutes } from './githubExporterRoutes'; 10 | import { setupCodegenRoutes } from './codegenRoutes'; 11 | import { setupScreenshotRoutes } from './imagesRoutes'; 12 | import { setupSentryRoutes } from './sentryRoutes'; 13 | import { Hono } from "hono"; 14 | import { AppEnv } from "../../types/appenv"; 15 | import { setupStatusRoutes } from './statusRoutes'; 16 | 17 | export function setupRoutes(app: Hono): void { 18 | // Health check route 19 | app.get('/api/health', (c) => { 20 | return c.json({ status: 'ok' }); 21 | }); 22 | 23 | // Sentry tunnel routes (public - no auth required) 24 | setupSentryRoutes(app); 25 | 26 | // Platform status routes (public) 27 | setupStatusRoutes(app); 28 | 29 | // Authentication and user management routes 30 | setupAuthRoutes(app); 31 | 32 | // Codegen routes 33 | setupCodegenRoutes(app); 34 | 35 | // User dashboard and profile routes 36 | setupUserRoutes(app); 37 | 38 | // App management routes 39 | setupAppRoutes(app); 40 | 41 | // Stats routes 42 | setupStatsRoutes(app); 43 | 44 | // AI Gateway Analytics routes 45 | setupAnalyticsRoutes(app); 46 | 47 | // Secrets management routes 48 | setupSecretsRoutes(app); 49 | 50 | // Model configuration and provider keys routes 51 | setupModelConfigRoutes(app); 52 | 53 | // Model provider routes 54 | setupModelProviderRoutes(app); 55 | 56 | // GitHub Exporter routes 57 | setupGitHubExporterRoutes(app); 58 | 59 | // Screenshot serving routes (public) 60 | setupScreenshotRoutes(app); 61 | } 62 | -------------------------------------------------------------------------------- /worker/agents/domain/pure/DependencyManagement.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Pure functions for dependency management 3 | * No side effects, handles package.json and dependency merging 4 | */ 5 | 6 | import type { StructuredLogger } from '../../../logger'; 7 | export class DependencyManagement { 8 | /** 9 | * Merge dependencies from template and package.json 10 | * Preserves exact behavior from original implementation 11 | */ 12 | static mergeDependencies( 13 | templateDeps: Record = {}, 14 | lastPackageJson?: string, 15 | logger?: Pick 16 | ): Record { 17 | const deps = { ...templateDeps }; 18 | 19 | // Add additional dependencies from the last package.json 20 | if (lastPackageJson) { 21 | try { 22 | const parsedPackageJson = JSON.parse(lastPackageJson); 23 | const packageDeps = parsedPackageJson.dependencies as Record; 24 | 25 | if (packageDeps) { 26 | Object.assign(deps, packageDeps); 27 | logger?.info(`Adding dependencies from last package.json: ${Object.keys(packageDeps).join(', ')}`); 28 | } 29 | } catch (error) { 30 | logger?.warn('Failed to parse lastPackageJson:', error); 31 | } 32 | } 33 | 34 | return deps; 35 | } 36 | 37 | /** 38 | * Extract dependencies from package.json string 39 | */ 40 | static extractDependenciesFromPackageJson(packageJson: string): Record { 41 | try { 42 | const parsed = JSON.parse(packageJson); 43 | return parsed.dependencies || {}; 44 | } catch { 45 | return {}; 46 | } 47 | } 48 | 49 | /** 50 | * Format dependency list for display 51 | */ 52 | static formatDependencyList(deps: Record): string { 53 | return Object.entries(deps) 54 | .map(([name, version]) => `${name}@${version}`) 55 | .join(', '); 56 | } 57 | } -------------------------------------------------------------------------------- /src/components/image-attachment-preview.tsx: -------------------------------------------------------------------------------- 1 | import { X } from 'lucide-react'; 2 | import type { ImageAttachment } from '@/api-types'; 3 | import { motion, AnimatePresence } from 'framer-motion'; 4 | 5 | export interface ImageAttachmentPreviewProps { 6 | images: ImageAttachment[]; 7 | onRemove?: (id: string) => void; 8 | className?: string; 9 | compact?: boolean; 10 | } 11 | 12 | /** 13 | * Component to display image attachment previews 14 | */ 15 | export function ImageAttachmentPreview({ 16 | images, 17 | onRemove, 18 | className = '', 19 | compact = false, 20 | }: ImageAttachmentPreviewProps) { 21 | if (images.length === 0) return null; 22 | 23 | return ( 24 | 25 | 26 | {images.map((image) => ( 27 | 35 | 40 | {onRemove && ( 41 | onRemove(image.id)} 44 | className="absolute top-1 right-1 p-0.5 rounded-full bg-bg-1/90 hover:bg-bg-1 text-text-primary opacity-0 group-hover:opacity-100 transition-opacity" 45 | aria-label={`Remove ${image.filename}`} 46 | > 47 | 48 | 49 | )} 50 | {!compact && ( 51 | 52 | 53 | {image.filename} 54 | 55 | 56 | )} 57 | 58 | ))} 59 | 60 | 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /src/hooks/use-stats.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useCallback } from 'react'; 2 | import { useAuth } from '@/contexts/auth-context'; 3 | import { apiClient } from '@/lib/api-client'; 4 | import type { UserStats, UserActivity } from '@/api-types'; 5 | 6 | export function useUserStats() { 7 | const { isAuthenticated } = useAuth(); 8 | const [stats, setStats] = useState(null); 9 | const [loading, setLoading] = useState(true); 10 | const [error, setError] = useState(null); 11 | 12 | const fetchStats = useCallback(async () => { 13 | if (!isAuthenticated) { 14 | setLoading(false); 15 | return; 16 | } 17 | 18 | try { 19 | const response = await apiClient.getUserStats(); 20 | setStats(response.data || null); 21 | } catch (err) { 22 | console.error('Error fetching stats:', err); 23 | setError(err instanceof Error ? err.message : 'Failed to fetch stats'); 24 | } finally { 25 | setLoading(false); 26 | } 27 | }, [isAuthenticated]); 28 | 29 | useEffect(() => { 30 | fetchStats(); 31 | }, [fetchStats]); 32 | 33 | return { stats, loading, error, refetch: fetchStats }; 34 | } 35 | 36 | export function useUserActivity() { 37 | const { isAuthenticated } = useAuth(); 38 | const [activities, setActivities] = useState([]); 39 | const [loading, setLoading] = useState(true); 40 | const [error, setError] = useState(null); 41 | 42 | const fetchActivity = useCallback(async () => { 43 | if (!isAuthenticated) { 44 | setLoading(false); 45 | return; 46 | } 47 | 48 | try { 49 | const response = await apiClient.getUserActivity(); 50 | setActivities(response.data?.activities || []); 51 | } catch (err) { 52 | console.error('Error fetching activity:', err); 53 | setError(err instanceof Error ? err.message : 'Failed to fetch activity'); 54 | } finally { 55 | setLoading(false); 56 | } 57 | }, [isAuthenticated]); 58 | 59 | useEffect(() => { 60 | fetchActivity(); 61 | }, [fetchActivity]); 62 | 63 | return { activities, loading, error, refetch: fetchActivity }; 64 | } -------------------------------------------------------------------------------- /worker/api/routes/modelConfigRoutes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Routes for managing user model configurations 3 | */ 4 | 5 | import { ModelConfigController } from '../controllers/modelConfig/controller'; 6 | import { Hono } from 'hono'; 7 | import { AppEnv } from '../../types/appenv'; 8 | import { adaptController } from '../honoAdapter'; 9 | import { AuthConfig, setAuthLevel } from '../../middleware/auth/routeAuth'; 10 | 11 | /** 12 | * Setup model configuration routes 13 | * All routes are protected and require authentication 14 | */ 15 | export function setupModelConfigRoutes(app: Hono): void { 16 | // Create a sub-router for model config routes 17 | const modelConfigRouter = new Hono(); 18 | 19 | // Model Configuration Routes 20 | modelConfigRouter.get('/', setAuthLevel(AuthConfig.authenticated), adaptController(ModelConfigController, ModelConfigController.getModelConfigs)); 21 | modelConfigRouter.get('/defaults', setAuthLevel(AuthConfig.authenticated), adaptController(ModelConfigController, ModelConfigController.getDefaults)); 22 | modelConfigRouter.get('/byok-providers', setAuthLevel(AuthConfig.authenticated), adaptController(ModelConfigController, ModelConfigController.getByokProviders)); 23 | modelConfigRouter.get('/:agentAction', setAuthLevel(AuthConfig.authenticated), adaptController(ModelConfigController, ModelConfigController.getModelConfig)); 24 | modelConfigRouter.put('/:agentAction', setAuthLevel(AuthConfig.authenticated), adaptController(ModelConfigController, ModelConfigController.updateModelConfig)); 25 | modelConfigRouter.delete('/:agentAction', setAuthLevel(AuthConfig.authenticated), adaptController(ModelConfigController, ModelConfigController.deleteModelConfig)); 26 | modelConfigRouter.post('/test', setAuthLevel(AuthConfig.authenticated), adaptController(ModelConfigController, ModelConfigController.testModelConfig)); 27 | modelConfigRouter.post('/reset-all', setAuthLevel(AuthConfig.authenticated), adaptController(ModelConfigController, ModelConfigController.resetAllConfigs)); 28 | 29 | // Mount the router under /api/model-configs 30 | app.route('/api/model-configs', modelConfigRouter); 31 | } -------------------------------------------------------------------------------- /worker/api/controllers/modelConfig/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Type definitions for ModelConfig Controller responses 3 | */ 4 | 5 | import type { UserModelConfigWithMetadata, ModelTestResult } from '../../../database/types'; 6 | import type { AgentActionKey, ModelConfig, AIModels } from '../../../agents/inferutils/config.types'; 7 | 8 | export interface UserProviderStatus { 9 | provider: string; 10 | hasValidKey: boolean; 11 | keyPreview?: string; 12 | } 13 | 14 | export interface ModelsByProvider { 15 | [provider: string]: AIModels[]; 16 | } 17 | import { UserModelConfig } from '../../../database/schema'; 18 | 19 | /** 20 | * Response data for getModelConfigs 21 | */ 22 | export interface ModelConfigsData { 23 | configs: Record; 24 | defaults: Record; 25 | message: string; 26 | } 27 | 28 | /** 29 | * Response data for getModelConfig 30 | */ 31 | export interface ModelConfigData { 32 | config: UserModelConfigWithMetadata; 33 | defaultConfig: ModelConfig; 34 | message: string; 35 | } 36 | 37 | /** 38 | * Response data for updateModelConfig 39 | */ 40 | export interface ModelConfigUpdateData { 41 | config: UserModelConfig; 42 | message: string; 43 | } 44 | 45 | /** 46 | * Response data for testModelConfig 47 | */ 48 | export interface ModelConfigTestData { 49 | testResult: ModelTestResult; 50 | message: string; 51 | } 52 | 53 | /** 54 | * Response data for resetAllConfigs 55 | */ 56 | export interface ModelConfigResetData { 57 | resetCount: number; 58 | message: string; 59 | } 60 | 61 | /** 62 | * Response data for getDefaults 63 | */ 64 | export interface ModelConfigDefaultsData { 65 | defaults: Record; 66 | message: string; 67 | } 68 | 69 | /** 70 | * Response data for deleteModelConfig 71 | */ 72 | export interface ModelConfigDeleteData { 73 | message: string; 74 | } 75 | 76 | /** 77 | * Response data for getByokProviders 78 | */ 79 | export interface ByokProvidersData { 80 | providers: UserProviderStatus[]; 81 | modelsByProvider: ModelsByProvider; 82 | platformModels: AIModels[]; 83 | } -------------------------------------------------------------------------------- /worker/agents/tools/toolkit/exec-commands.ts: -------------------------------------------------------------------------------- 1 | import { ToolDefinition, ErrorResult } from '../types'; 2 | import { StructuredLogger } from '../../../logger'; 3 | import { CodingAgentInterface } from 'worker/agents/services/implementations/CodingAgent'; 4 | import { ExecuteCommandsResponse } from 'worker/services/sandbox/sandboxTypes'; 5 | 6 | export type ExecCommandsArgs = { 7 | commands: string[]; 8 | shouldSave: boolean; 9 | timeout?: number; 10 | }; 11 | 12 | export type ExecCommandsResult = ExecuteCommandsResponse | ErrorResult; 13 | 14 | export function createExecCommandsTool( 15 | agent: CodingAgentInterface, 16 | logger: StructuredLogger, 17 | ): ToolDefinition { 18 | return { 19 | type: 'function' as const, 20 | function: { 21 | name: 'exec_commands', 22 | description: 23 | 'Execute shell commands in the sandbox. CRITICAL shouldSave rules: (1) Set shouldSave=true ONLY for package management with specific packages (e.g., "bun add react", "npm install lodash"). (2) Set shouldSave=false for: file operations (rm, mv, cp), plain installs ("bun install"), run commands ("bun run dev"), and temporary operations. Invalid commands in shouldSave=true will be automatically filtered out. Always use bun for package management.', 24 | parameters: { 25 | type: 'object', 26 | properties: { 27 | commands: { type: 'array', items: { type: 'string' } }, 28 | shouldSave: { type: 'boolean', default: true }, 29 | timeout: { type: 'number', default: 30000 }, 30 | }, 31 | required: ['commands'], 32 | }, 33 | }, 34 | implementation: async ({ commands, shouldSave = true, timeout = 30000 }) => { 35 | try { 36 | logger.info('Executing commands', { 37 | count: commands.length, 38 | commands, 39 | shouldSave, 40 | timeout, 41 | }); 42 | return await agent.execCommands(commands, shouldSave, timeout); 43 | } catch (error) { 44 | return { 45 | error: 46 | error instanceof Error 47 | ? `Failed to execute commands: ${error.message}` 48 | : 'Unknown error occurred while executing commands', 49 | }; 50 | } 51 | }, 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /src/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Card({ className, ...props }: React.ComponentProps<"div">) { 6 | return ( 7 | 15 | ) 16 | } 17 | 18 | function CardHeader({ className, variant, ...props }: React.ComponentProps<"div"> & { variant?: "minimal"}) { 19 | return ( 20 | 29 | ) 30 | } 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLDivElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 | 41 | )) 42 | CardTitle.displayName = "CardTitle" 43 | 44 | const CardDescription = React.forwardRef< 45 | HTMLDivElement, 46 | React.HTMLAttributes 47 | >(({ className, ...props }, ref) => ( 48 | 53 | )) 54 | CardDescription.displayName = "CardDescription" 55 | 56 | const CardContent = React.forwardRef< 57 | HTMLDivElement, 58 | React.HTMLAttributes 59 | >(({ className, ...props }, ref) => ( 60 | 61 | )) 62 | CardContent.displayName = "CardContent" 63 | 64 | const CardFooter = React.forwardRef< 65 | HTMLDivElement, 66 | React.HTMLAttributes 67 | >(({ className, ...props }, ref) => ( 68 | 73 | )) 74 | CardFooter.displayName = "CardFooter" 75 | 76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 77 | -------------------------------------------------------------------------------- /worker/database/services/BaseService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Base Database Service Class 3 | * Provides common database functionality and patterns for all domain services 4 | */ 5 | 6 | import { createDatabaseService, DatabaseService } from '../database'; 7 | import { SQL, and } from 'drizzle-orm'; 8 | import { createLogger } from '../../logger'; 9 | 10 | /** 11 | * Base class for all database domain services 12 | * Provides shared utilities and database access patterns 13 | */ 14 | export abstract class BaseService { 15 | protected logger = createLogger(this.constructor.name); 16 | protected db: DatabaseService; 17 | protected env: Env; 18 | constructor(env: Env) { 19 | this.db = createDatabaseService(env); 20 | this.env = env; 21 | } 22 | 23 | /** 24 | * Helper to build type-safe where conditions 25 | */ 26 | protected buildWhereConditions(conditions: (SQL | undefined)[]): SQL | undefined { 27 | const validConditions = conditions.filter((c): c is SQL => c !== undefined); 28 | if (validConditions.length === 0) return undefined; 29 | if (validConditions.length === 1) return validConditions[0]; 30 | // Use Drizzle's and() function to properly combine conditions 31 | return and(...validConditions); 32 | } 33 | 34 | /** 35 | * Standard error handling for database operations 36 | */ 37 | protected handleDatabaseError(error: unknown, operation: string, context?: Record): never { 38 | this.logger.error(`Database error in ${operation}`, { error, context }); 39 | throw error; 40 | } 41 | 42 | /** 43 | * Get database connection for direct queries when needed 44 | */ 45 | protected get database() { 46 | return this.db.db; 47 | } 48 | 49 | /** 50 | * Get read-optimized database connection using D1 read replicas 51 | * For read-only queries to reduce global latency 52 | * 53 | * @param strategy - 'fast' for lowest latency, 'fresh' for latest data 54 | */ 55 | protected getReadDb(strategy: 'fast' | 'fresh' = 'fast') { 56 | return this.db.getReadDb(strategy); 57 | } 58 | } -------------------------------------------------------------------------------- /worker/agents/operations/common.ts: -------------------------------------------------------------------------------- 1 | import { StructuredLogger } from "../../logger"; 2 | import { GenerationContext } from "../domain/values/GenerationContext"; 3 | import { Message } from "../inferutils/common"; 4 | import { InferenceContext } from "../inferutils/config.types"; 5 | import { createUserMessage, createSystemMessage, createAssistantMessage } from "../inferutils/common"; 6 | import { generalSystemPromptBuilder, USER_PROMPT_FORMATTER } from "../prompts"; 7 | import { CodeSerializerType } from "../utils/codeSerializers"; 8 | import { CodingAgentInterface } from "../services/implementations/CodingAgent"; 9 | 10 | export function getSystemPromptWithProjectContext( 11 | systemPrompt: string, 12 | context: GenerationContext, 13 | serializerType: CodeSerializerType = CodeSerializerType.SIMPLE, 14 | sharePhases: boolean = true 15 | ): Message[] { 16 | const { query, blueprint, templateDetails, dependencies, allFiles, commandsHistory } = context; 17 | 18 | const messages = [ 19 | createSystemMessage(generalSystemPromptBuilder(systemPrompt, { 20 | query, 21 | blueprint, 22 | templateDetails, 23 | dependencies, 24 | })), 25 | createUserMessage( 26 | USER_PROMPT_FORMATTER.PROJECT_CONTEXT( 27 | sharePhases ? context.getCompletedPhases() : [], 28 | allFiles, 29 | context.getFileTree(), 30 | commandsHistory, 31 | serializerType 32 | ) 33 | ), 34 | createAssistantMessage(`I have thoroughly gone through the whole codebase and understood the current implementation and project requirements. We can continue.`) 35 | ]; 36 | return messages; 37 | } 38 | 39 | export interface OperationOptions { 40 | env: Env; 41 | agentId: string; 42 | context: GenerationContext; 43 | logger: StructuredLogger; 44 | inferenceContext: InferenceContext; 45 | agent: CodingAgentInterface; 46 | } 47 | 48 | export abstract class AgentOperation { 49 | abstract execute( 50 | inputs: InputType, 51 | options: OperationOptions 52 | ): Promise; 53 | } -------------------------------------------------------------------------------- /src/components/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import * as Sentry from '@sentry/react'; 2 | import { ReactNode } from 'react'; 3 | import { Button } from '@/components/ui/button'; 4 | import { AlertCircle } from 'lucide-react'; 5 | 6 | function ErrorFallback({ error, resetError }: { error: Error | unknown; resetError: () => void; }) { 7 | const errorMessage = error instanceof Error ? error.message : 'An unexpected error occurred'; 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Something went wrong 17 | 18 | An unexpected error occurred. Our team has been notified. 19 | 20 | 21 | 22 | {import.meta.env.DEV && ( 23 | 24 | 25 | {errorMessage} 26 | 27 | 28 | )} 29 | 30 | 31 | 32 | Try Again 33 | 34 | window.location.href = '/'} 36 | variant="outline" 37 | > 38 | Go Home 39 | 40 | 41 | 42 | 43 | ); 44 | } 45 | 46 | interface ErrorBoundaryProps { 47 | children: ReactNode; 48 | showDialog?: boolean; 49 | } 50 | 51 | export function ErrorBoundary({ 52 | children, 53 | showDialog = false 54 | }: ErrorBoundaryProps) { 55 | return ( 56 | 60 | {children} 61 | 62 | ); 63 | } 64 | 65 | // Export the default fallback component for reuse 66 | export { ErrorFallback }; 67 | -------------------------------------------------------------------------------- /worker/agents/core/types.ts: -------------------------------------------------------------------------------- 1 | 2 | import type { RuntimeError, StaticAnalysisResponse } from '../../services/sandbox/sandboxTypes'; 3 | import type { FileOutputType, PhaseConceptType } from '../schemas'; 4 | import type { ConversationMessage } from '../inferutils/common'; 5 | import type { InferenceContext } from '../inferutils/config.types'; 6 | import type { TemplateDetails } from '../../services/sandbox/sandboxTypes'; 7 | import { TemplateSelection } from '../schemas'; 8 | import { CurrentDevState } from './state'; 9 | import { ProcessedImageAttachment } from 'worker/types/image-attachment'; 10 | 11 | export interface AgentInitArgs { 12 | query: string; 13 | language?: string; 14 | frameworks?: string[]; 15 | hostname: string; 16 | inferenceContext: InferenceContext; 17 | templateInfo: { 18 | templateDetails: TemplateDetails; 19 | selection: TemplateSelection; 20 | } 21 | images?: ProcessedImageAttachment[]; 22 | onBlueprintChunk: (chunk: string) => void; 23 | } 24 | 25 | export interface AllIssues { 26 | runtimeErrors: RuntimeError[]; 27 | staticAnalysis: StaticAnalysisResponse; 28 | } 29 | 30 | /** 31 | * Agent state definition for code generation 32 | */ 33 | export interface ScreenshotData { 34 | url: string; 35 | timestamp: number; 36 | viewport: { width: number; height: number }; 37 | userAgent?: string; 38 | screenshot?: string; // Base64 data URL from Cloudflare Browser Rendering REST API 39 | } 40 | 41 | export interface AgentSummary { 42 | query: string; 43 | generatedCode: FileOutputType[]; 44 | conversation: ConversationMessage[]; 45 | } 46 | 47 | export interface UserContext { 48 | suggestions?: string[]; 49 | images?: ProcessedImageAttachment[]; // Image URLs 50 | } 51 | 52 | export interface PhaseExecutionResult { 53 | currentDevState: CurrentDevState; 54 | staticAnalysis?: StaticAnalysisResponse; 55 | result?: PhaseConceptType; 56 | userSuggestions?: string[]; 57 | userContext?: UserContext; 58 | } 59 | 60 | /** 61 | * Result type for deep debug operations 62 | */ 63 | export type DeepDebugResult = 64 | | { success: true; transcript: string } 65 | | { success: false; error: string }; -------------------------------------------------------------------------------- /src/components/ui/tabs.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as TabsPrimitive from "@radix-ui/react-tabs" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | function Tabs({ 7 | className, 8 | ...props 9 | }: React.ComponentProps) { 10 | return ( 11 | 16 | ) 17 | } 18 | 19 | function TabsList({ 20 | className, 21 | ...props 22 | }: React.ComponentProps) { 23 | return ( 24 | 32 | ) 33 | } 34 | 35 | function TabsTrigger({ 36 | className, 37 | ...props 38 | }: React.ComponentProps) { 39 | return ( 40 | 48 | ) 49 | } 50 | 51 | function TabsContent({ 52 | className, 53 | ...props 54 | }: React.ComponentProps) { 55 | return ( 56 | 61 | ) 62 | } 63 | 64 | export { Tabs, TabsList, TabsTrigger, TabsContent } 65 | -------------------------------------------------------------------------------- /src/contexts/theme-context.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useEffect, useState } from 'react'; 2 | 3 | type Theme = 'light' | 'dark' | 'system'; 4 | 5 | interface ThemeContextType { 6 | theme: Theme; 7 | setTheme: (theme: Theme) => void; 8 | } 9 | 10 | const ThemeContext = createContext({ 11 | theme: 'system', 12 | setTheme: () => {}, 13 | }); 14 | 15 | export const useTheme = () => { 16 | const context = useContext(ThemeContext); 17 | if (!context) { 18 | throw new Error('useTheme must be used within a ThemeProvider'); 19 | } 20 | return context; 21 | }; 22 | 23 | interface ThemeProviderProps { 24 | children: React.ReactNode; 25 | } 26 | 27 | export function ThemeProvider({ children }: ThemeProviderProps) { 28 | const [theme, setTheme] = useState(() => { 29 | const savedTheme = localStorage.getItem('theme') as Theme; 30 | return savedTheme || 'system'; 31 | }); 32 | 33 | const applyTheme = (newTheme: Theme) => { 34 | const root = window.document.documentElement; 35 | root.classList.remove('light', 'dark'); 36 | 37 | if (newTheme === 'system') { 38 | const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; 39 | root.classList.add(systemTheme); 40 | } else { 41 | root.classList.add(newTheme); 42 | } 43 | }; 44 | 45 | useEffect(() => { 46 | applyTheme(theme); 47 | 48 | // Listen for system theme changes 49 | const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); 50 | const handleChange = () => { 51 | if (theme === 'system') { 52 | applyTheme('system'); 53 | } 54 | }; 55 | 56 | mediaQuery.addEventListener('change', handleChange); 57 | return () => mediaQuery.removeEventListener('change', handleChange); 58 | }, [theme]); 59 | 60 | const handleSetTheme = (newTheme: Theme) => { 61 | setTheme(newTheme); 62 | localStorage.setItem('theme', newTheme); 63 | // Apply theme immediately for instant feedback 64 | applyTheme(newTheme); 65 | }; 66 | 67 | return ( 68 | 69 | {children} 70 | 71 | ); 72 | } -------------------------------------------------------------------------------- /worker/api/responses.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Standardized API response utilities 3 | */ 4 | 5 | import { RateLimitError } from "../services/rate-limit/errors"; 6 | import { SecurityError, SecurityErrorType } from 'shared/types/errors'; 7 | /** 8 | * Standard response shape for all API endpoints 9 | */ 10 | 11 | export interface BaseErrorResponse { 12 | message: string; 13 | name: string; 14 | type?: SecurityErrorType; 15 | } 16 | 17 | export interface RateLimitErrorResponse extends BaseErrorResponse { 18 | details: RateLimitError; 19 | } 20 | 21 | type ErrorResponse = BaseErrorResponse | RateLimitErrorResponse; 22 | 23 | export interface BaseApiResponse { 24 | success: boolean; 25 | data?: T; 26 | error?: ErrorResponse; 27 | message?: string; 28 | } 29 | 30 | /** 31 | * Creates a success response with standard format 32 | */ 33 | export function successResponse(data: T, message?: string): Response { 34 | const responseBody: BaseApiResponse = { 35 | success: true, 36 | data, 37 | message, 38 | }; 39 | 40 | return new Response(JSON.stringify(responseBody), { 41 | status: 200, 42 | headers: { 43 | 'Content-Type': 'application/json' 44 | } 45 | }); 46 | } 47 | 48 | /** 49 | * Creates an error response with standard format 50 | */ 51 | export function errorResponse(error: string | Error | SecurityError, statusCode = 500, message?: string): Response { 52 | let errorResp: ErrorResponse = { 53 | message: error instanceof Error ? error.message : error, 54 | name: error instanceof Error ? error.name : 'Error', 55 | } 56 | if (error instanceof SecurityError) { 57 | errorResp = { 58 | ...errorResp, 59 | type: error.type, 60 | } 61 | } 62 | const responseBody: BaseApiResponse = { 63 | success: false, 64 | error: errorResp, 65 | message: message || (error instanceof Error ? error.message : 'An error occurred'), 66 | }; 67 | 68 | return new Response(JSON.stringify(responseBody), { 69 | status: statusCode, 70 | headers: { 71 | 'Content-Type': 'application/json' 72 | } 73 | }); 74 | } -------------------------------------------------------------------------------- /src/components/ui/resizable.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { GripVerticalIcon } from "lucide-react" 3 | import * as ResizablePrimitive from "react-resizable-panels" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | function ResizablePanelGroup({ 8 | className, 9 | ...props 10 | }: React.ComponentProps) { 11 | return ( 12 | 20 | ) 21 | } 22 | 23 | function ResizablePanel({ 24 | ...props 25 | }: React.ComponentProps) { 26 | return 27 | } 28 | 29 | function ResizableHandle({ 30 | withHandle, 31 | className, 32 | ...props 33 | }: React.ComponentProps & { 34 | withHandle?: boolean 35 | }) { 36 | return ( 37 | div]:rotate-90", 41 | className 42 | )} 43 | {...props} 44 | > 45 | {withHandle && ( 46 | 47 | 48 | 49 | )} 50 | 51 | ) 52 | } 53 | 54 | export { ResizablePanelGroup, ResizablePanel, ResizableHandle } 55 | -------------------------------------------------------------------------------- /worker/services/oauth/github-exporter.ts: -------------------------------------------------------------------------------- 1 | import type { OAuthUserInfo } from '../../types/auth-types'; 2 | import { createLogger } from '../../logger'; 3 | import { createGitHubHeaders } from '../../utils/githubUtils'; 4 | import { GitHubOAuthProvider } from './github'; 5 | 6 | const logger = createLogger('GitHubExporterOAuth'); 7 | 8 | export class GitHubExporterOAuthProvider extends GitHubOAuthProvider { 9 | protected readonly scopes = [ 10 | 'public_repo', 11 | 'repo' 12 | ]; 13 | 14 | async getUserInfo(accessToken: string): Promise { 15 | try { 16 | const userResponse = await fetch(this.userInfoUrl, { 17 | headers: createGitHubHeaders(accessToken) 18 | }); 19 | 20 | if (!userResponse.ok) { 21 | throw new Error('Failed to retrieve user information from GitHub'); 22 | } 23 | 24 | const userData = await userResponse.json() as { 25 | id: number; 26 | login: string; 27 | email?: string; 28 | name?: string; 29 | avatar_url?: string; 30 | }; 31 | 32 | return { 33 | id: String(userData.id), 34 | email: userData.email || `${userData.login}@github.local`, 35 | name: userData.name || userData.login, 36 | picture: userData.avatar_url, 37 | emailVerified: true 38 | }; 39 | } catch (error) { 40 | logger.error('Error getting user info', error); 41 | throw error; 42 | } 43 | } 44 | 45 | static create(env: Env, baseUrl: string): GitHubExporterOAuthProvider { 46 | if (!env.GITHUB_EXPORTER_CLIENT_ID || !env.GITHUB_EXPORTER_CLIENT_SECRET) { 47 | throw new Error('GitHub App OAuth credentials not configured'); 48 | } 49 | 50 | const redirectUri = `${baseUrl}/api/github-exporter/callback`; 51 | 52 | return new GitHubExporterOAuthProvider( 53 | env.GITHUB_EXPORTER_CLIENT_ID, 54 | env.GITHUB_EXPORTER_CLIENT_SECRET, 55 | redirectUri 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /worker/types/image-attachment.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Supported image MIME types for upload 3 | * Limited to most common web formats for reliability 4 | */ 5 | export const SUPPORTED_IMAGE_MIME_TYPES = [ 6 | 'image/png', 7 | 'image/jpeg', 8 | 'image/webp', 9 | ] as const; 10 | 11 | export type SupportedImageMimeType = typeof SUPPORTED_IMAGE_MIME_TYPES[number]; 12 | 13 | /** 14 | * Image attachment for user messages 15 | * Represents an image that can be sent with text prompts 16 | */ 17 | export interface ImageAttachment { 18 | /** Unique identifier for this attachment */ 19 | id: string; 20 | /** Original filename */ 21 | filename: string; 22 | /** MIME type of the image */ 23 | mimeType: SupportedImageMimeType; 24 | /** Base64-encoded image data (without data URL prefix) */ 25 | base64Data: string; 26 | /** Size of the original file in bytes */ 27 | size?: number; 28 | /** Optional dimensions if available */ 29 | dimensions?: { 30 | width: number; 31 | height: number; 32 | }; 33 | } 34 | 35 | export interface ProcessedImageAttachment { 36 | /** MIME type of the image */ 37 | mimeType: SupportedImageMimeType; 38 | /** Base64-encoded image data (without data URL prefix) */ 39 | base64Data?: string; 40 | /** R2 key of the image */ 41 | r2Key: string; 42 | /** URL of the image */ 43 | publicUrl: string; 44 | /** image data hash */ 45 | hash: string; 46 | } 47 | 48 | /** 49 | * Utility to check if a MIME type is supported 50 | */ 51 | export function isSupportedImageType(mimeType: string): mimeType is SupportedImageMimeType { 52 | return SUPPORTED_IMAGE_MIME_TYPES.includes(mimeType as SupportedImageMimeType); 53 | } 54 | 55 | /** 56 | * Utility to get file extension from MIME type 57 | */ 58 | export function getFileExtensionFromMimeType(mimeType: SupportedImageMimeType): string { 59 | const map: Record = { 60 | 'image/png': 'png', 61 | 'image/jpeg': 'jpg', 62 | 'image/webp': 'webp', 63 | }; 64 | return map[mimeType] || 'jpg'; 65 | } 66 | 67 | /** 68 | * Maximum file size for images (10MB) 69 | */ 70 | export const MAX_IMAGE_SIZE_BYTES = 10 * 1024 * 1024; 71 | 72 | /** 73 | * Maximum number of images per message 74 | */ 75 | export const MAX_IMAGES_PER_MESSAGE = 2; 76 | -------------------------------------------------------------------------------- /src/components/ui/toggle-group.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group" 5 | import { type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | import { toggleVariants } from "@/components/ui/toggle" 9 | 10 | const ToggleGroupContext = React.createContext< 11 | VariantProps 12 | >({ 13 | size: "default", 14 | variant: "default", 15 | }) 16 | 17 | function ToggleGroup({ 18 | className, 19 | variant, 20 | size, 21 | children, 22 | ...props 23 | }: React.ComponentProps & 24 | VariantProps) { 25 | return ( 26 | 36 | 37 | {children} 38 | 39 | 40 | ) 41 | } 42 | 43 | function ToggleGroupItem({ 44 | className, 45 | children, 46 | variant, 47 | size, 48 | ...props 49 | }: React.ComponentProps & 50 | VariantProps) { 51 | const context = React.useContext(ToggleGroupContext) 52 | 53 | return ( 54 | 68 | {children} 69 | 70 | ) 71 | } 72 | 73 | export { ToggleGroup, ToggleGroupItem } 74 | -------------------------------------------------------------------------------- /src/lib/app-events.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple event system for app-related events 3 | * Allows different parts of the app to communicate about app state changes 4 | */ 5 | 6 | import { AppWithFavoriteStatus } from "@/api-types"; 7 | 8 | // Define specific event data types 9 | export interface AppDeletedEvent { 10 | type: 'app-deleted'; 11 | appId: string; 12 | } 13 | 14 | export interface AppCreatedEvent { 15 | type: 'app-created'; 16 | appId: string; 17 | data?: { 18 | title?: string; 19 | description?: string; 20 | visibility?: string; 21 | isForked?: boolean; 22 | }; 23 | } 24 | 25 | export interface AppUpdatedEvent { 26 | type: 'app-updated'; 27 | appId: string; 28 | data?: AppWithFavoriteStatus 29 | } 30 | 31 | export type AppEvent = AppDeletedEvent | AppCreatedEvent | AppUpdatedEvent; 32 | export type AppEventType = AppEvent['type']; 33 | export type AppEventListener = (event: AppEvent) => void; 34 | 35 | class AppEventEmitter { 36 | private listeners: Map> = new Map(); 37 | 38 | on(eventType: AppEventType, listener: AppEventListener) { 39 | if (!this.listeners.has(eventType)) { 40 | this.listeners.set(eventType, new Set()); 41 | } 42 | this.listeners.get(eventType)!.add(listener); 43 | 44 | // Return cleanup function 45 | return () => { 46 | this.listeners.get(eventType)?.delete(listener); 47 | }; 48 | } 49 | 50 | emit(event: AppEvent) { 51 | const listeners = this.listeners.get(event.type); 52 | if (listeners) { 53 | listeners.forEach(listener => { 54 | try { 55 | listener(event); 56 | } catch (error) { 57 | console.error('Error in app event listener:', error); 58 | } 59 | }); 60 | } 61 | } 62 | 63 | // Convenience methods 64 | emitAppDeleted(appId: string) { 65 | this.emit({ type: 'app-deleted', appId }); 66 | } 67 | 68 | emitAppCreated(appId: string, data?: AppCreatedEvent['data']) { 69 | this.emit({ type: 'app-created', appId, data }); 70 | } 71 | 72 | emitAppUpdated(appId: string, data?: AppUpdatedEvent['data']) { 73 | this.emit({ type: 'app-updated', appId, data }); 74 | } 75 | } 76 | 77 | // Export singleton instance 78 | export const appEvents = new AppEventEmitter(); -------------------------------------------------------------------------------- /src/components/ui/slider.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SliderPrimitive from "@radix-ui/react-slider" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Slider({ 9 | className, 10 | defaultValue, 11 | value, 12 | min = 0, 13 | max = 100, 14 | ...props 15 | }: React.ComponentProps) { 16 | const _values = React.useMemo( 17 | () => 18 | Array.isArray(value) 19 | ? value 20 | : Array.isArray(defaultValue) 21 | ? defaultValue 22 | : [min, max], 23 | [value, defaultValue, min, max] 24 | ) 25 | 26 | return ( 27 | 39 | 45 | 51 | 52 | {Array.from({ length: _values.length }, (_, index) => ( 53 | 58 | ))} 59 | 60 | ) 61 | } 62 | 63 | export { Slider } 64 | --------------------------------------------------------------------------------
53 | {image.filename} 54 |
18 | An unexpected error occurred. Our team has been notified. 19 |
25 | {errorMessage} 26 |