diff --git a/.github/workflows/_publish.yml b/.github/workflows/_publish.yml index c9a490f4c..bfca58783 100755 --- a/.github/workflows/_publish.yml +++ b/.github/workflows/_publish.yml @@ -52,6 +52,18 @@ jobs: rustup default stable cargo --version + - name: 🦀 Install cross-compilation tools + if: ${{ inputs.release }} + run: | + # Install cross for easy cross-compilation + cargo install cross --locked + + # Add required targets + rustup target add x86_64-unknown-linux-gnu + rustup target add aarch64-unknown-linux-gnu + rustup target add x86_64-apple-darwin + rustup target add aarch64-apple-darwin + - name: ☕ Setup Java uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: @@ -226,11 +238,67 @@ jobs: tags: ${{ steps.docker-meta.outputs.tags }} labels: ${{ steps.docker-meta.outputs.labels }} + - name: 🔨 Build native binaries for GitHub Release + if: ${{ inputs.release }} + run: | + set -euo pipefail + cd clients/agent-runtime + mkdir -p dist + + # Build for Linux x64 using cross + echo "Building for Linux x64..." + cross build --release --target x86_64-unknown-linux-gnu + cp target/x86_64-unknown-linux-gnu/release/corvus dist/corvus-linux-x64 + + # Build for Linux arm64 using cross + echo "Building for Linux arm64..." + cross build --release --target aarch64-unknown-linux-gnu + cp target/aarch64-unknown-linux-gnu/release/corvus dist/corvus-linux-arm64 + + # Build for macOS x64 using cross + echo "Building for macOS x64..." + if cross build --release --target x86_64-apple-darwin 2>/dev/null; then + cp target/x86_64-apple-darwin/release/corvus dist/corvus-darwin-x64 + else + echo "⚠️ Cross-compilation for macOS x64 not available, skipping..." + echo "Note: macOS binaries must be built on macOS runners or with special tooling" + fi + + # Build for macOS arm64 using cross + echo "Building for macOS arm64..." + if cross build --release --target aarch64-apple-darwin 2>/dev/null; then + cp target/aarch64-apple-darwin/release/corvus dist/corvus-darwin-arm64 + else + echo "⚠️ Cross-compilation for macOS arm64 not available, skipping..." + echo "Note: macOS binaries must be built on macOS runners or with special tooling" + fi + + ls -la dist/ + + - name: 📋 Generate SHA256 checksums + if: ${{ inputs.release }} + run: | + set -euo pipefail + cd clients/agent-runtime/dist + + for binary in corvus-*; do + if [ -f "$binary" ] && [[ ! "$binary" == *.sha256 ]] && [[ ! "$binary" == *.skip ]]; then + echo "Generating checksum for $binary..." + sha256sum "$binary" > "$binary.sha256" + cat "$binary.sha256" + fi + done + + echo "Checksum files generated:" + ls -la *.sha256 || echo "No checksum files found" + - name: 🚀 Create GitHub Release if: ${{ inputs.release }} uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2 with: body: ${{ steps.changelog.outputs.changelog }} generate_release_notes: true + files: | + clients/agent-runtime/dist/corvus-* env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/clients/agent-runtime/Dockerfile b/clients/agent-runtime/Dockerfile index 01a339bd1..8995aa105 100755 --- a/clients/agent-runtime/Dockerfile +++ b/clients/agent-runtime/Dockerfile @@ -41,8 +41,7 @@ default_temperature = 0.7 [gateway] port = 3000 -host = "[::]" -allow_public_bind = true +host = "127.0.0.1" EOF RUN chown -R 65534:65534 /corvus-data @@ -82,7 +81,7 @@ WORKDIR /corvus-data USER 65534:65534 EXPOSE 3000 ENTRYPOINT ["corvus"] -CMD ["gateway", "--port", "3000", "--host", "[::]"] +CMD ["gateway", "--port", "3000", "--host", "127.0.0.1"] # ── Stage 4: Production Runtime (Distroless) ───────────────── FROM gcr.io/distroless/cc-debian13:nonroot AS release @@ -104,4 +103,4 @@ WORKDIR /corvus-data USER 65534:65534 EXPOSE 3000 ENTRYPOINT ["corvus"] -CMD ["gateway", "--port", "3000", "--host", "[::]"] +CMD ["gateway", "--port", "3000", "--host", "127.0.0.1"] diff --git a/clients/web/README.md b/clients/web/README.md index 99324844e..c920de157 100644 --- a/clients/web/README.md +++ b/clients/web/README.md @@ -1,42 +1,43 @@ # Corvus Web Monorepo -Estructura multi-app para proyectos web de Corvus. +Monorepo para apps web de Corvus, incluyendo docs, marketing y futuros frontends. ## 📁 Estructura -``` -apps/web/ +```text +clients/web/ ├── apps/ -│ ├── docs/ # Documentación Starlight (actual) -│ ├── landing/ # Landing page (futuro) -│ └── dashboard/ # Frontend web (futuro) +│ ├── docs/ # Documentación (Astro + Starlight) +│ ├── marketing/ # Landing y páginas de marketing (Astro) +│ └── dashboard/ # Dashboard web (pendiente) ├── packages/ -│ └── shared/ # Componentes/utilidades compartidas -├── package.json # Workspace root -└── pnpm-workspace.yaml # Configuración pnpm workspace +│ └── shared/ # Utilidades compartidas +├── package.json +└── pnpm-workspace.yaml ``` ## 🚀 Apps ### docs -- **Framework**: Astro + Starlight -- **Puerto**: 4321 -- **Uso**: Documentación del proyecto -### landing (futuro) -- **Framework**: Astro/Vue/React (por definir) -- **Puerto**: 4322 -- **Uso**: Landing page marketing +- Framework: Astro + Starlight +- Puerto por defecto: 4321 + +### marketing + +- Framework: Astro +- URL configurable con `MARKETING_URL` (dev default: `http://localhost:9988`) +- Incluye script público de instalación en `/install` + +### dashboard -### dashboard (futuro) -- **Framework**: Vue/React (por definir) -- **Puerto**: 4323 -- **Uso**: Panel de administración +- Estado: placeholder +- Puerto por defecto: 4323 ## 🛠️ Comandos ```bash -# Instalar dependencias en todas las apps +# Instalar dependencias workspace pnpm install # Build de todas las apps @@ -44,37 +45,28 @@ pnpm build # Build individual pnpm build:docs -pnpm build:landing +pnpm build:marketing pnpm build:dashboard +# Compatibilidad (alias antiguo) +pnpm build:landing + # Development -pnpm dev # docs por defecto -pnpm dev:landing +pnpm dev +pnpm dev:marketing pnpm dev:dashboard -# Lint/Format +# Compatibilidad (alias antiguo) +pnpm dev:landing + +# Quality pnpm format pnpm check ``` -## 📦 Packages Compartidos - -Los paquetes en `packages/` pueden ser importados por cualquier app: - -```typescript -import { Button } from '@corvus/shared'; -``` - -## 🏗️ Agregar una nueva app - -1. Crear directorio en `apps//` -2. Agregar `package.json` con nombre `@corvus/` -3. Ejecutar `pnpm install` desde root -4. Agregar scripts en `package.json` root si es necesario -5. Actualizar `build.gradle.kts` para incluir la nueva app - -## 📝 Notas +## 📦 Añadir más proyectos web -- Cada app tiene su propio `node_modules` (a través de pnpm) -- Las dependencias compartidas se instalan en root y se symlinkan -- El build de Gradle construye todas las apps automáticamente +1. Crear `apps//` con su `package.json` +2. Ejecutar `pnpm install` en `clients/web` +3. Ajustar scripts en `clients/web/package.json` si aplica +4. Confirmar que `clients/web/build.gradle.kts` tenga el puerto/config correspondiente diff --git a/clients/web/apps/landing/README.md b/clients/web/apps/landing/README.md deleted file mode 100644 index 213bdd2a8..000000000 --- a/clients/web/apps/landing/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Landing Page - -Corvus landing page - coming soon. - -## Development Plan - -- **Framework**: Astro/Vue/React (TBD) -- **Features**: - - Hero section with product showcase - - Features grid - - Pricing section - - Testimonials - - CTA sections - - Footer with links - -## Getting Started - -```bash -# From web root -pnpm dev:landing -``` diff --git a/clients/web/apps/landing/package.json b/clients/web/apps/landing/package.json deleted file mode 100644 index 8908e4b6e..000000000 --- a/clients/web/apps/landing/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "@corvus/landing", - "version": "0.1.0", - "private": true, - "description": "Corvus Landing Page", - "type": "module", - "scripts": { - "dev": "echo 'Landing app not yet implemented' && exit 1", - "build": "echo 'Landing app not yet implemented' && exit 1", - "preview": "echo 'Landing app not yet implemented' && exit 1", - "format": "echo 'No files to format'", - "check": "echo 'No files to check'", - "clean": "rm -rf dist" - }, - "dependencies": {}, - "devDependencies": {} -} diff --git a/clients/web/apps/marketing/.env.example b/clients/web/apps/marketing/.env.example new file mode 100644 index 000000000..6a0b1f0ff --- /dev/null +++ b/clients/web/apps/marketing/.env.example @@ -0,0 +1,4 @@ +# Marketing canonical URL used by Astro config +# Dev example: http://localhost:9988 +# Prod example: https://profiletailors.com +MARKETING_URL=http://localhost:9988 diff --git a/clients/web/apps/marketing/README.md b/clients/web/apps/marketing/README.md new file mode 100644 index 000000000..422d0377a --- /dev/null +++ b/clients/web/apps/marketing/README.md @@ -0,0 +1,57 @@ +# Marketing (Astro) + +Sitio de marketing para Corvus. + +## Objetivo + +Este proyecto en `clients/web/apps/marketing` centraliza: + +- Landing principal +- Páginas de campañas +- Script público de instalación (`/install`) + +## Desarrollo + +```bash +# Desde clients/web +pnpm dev:marketing + +# Build estático +pnpm build:marketing + +# Validaciones +pnpm --filter @corvus/marketing run check +``` + +## Configuración de dominio y puerto + +- Dev default: `http://localhost:9988` +- Prod default: `https://profiletailors.com` +- El puerto de `dev/preview` se toma de `MARKETING_URL` si incluye puerto; si no, usa `9988` + +Variable soportada: + +```bash +# Forzar URL para cualquier entorno +MARKETING_URL=https://staging.profiletailors.com pnpm build:marketing +``` + +Puedes cargarla por entorno con archivos `.env`: + +```bash +# .env.development +MARKETING_URL=http://localhost:9988 + +# .env.production +MARKETING_URL=https://profiletailors.com +``` + +## Instalador + +Este proyecto publica el script wizard en: + +```bash +curl -fsSL https://profiletailors.com/install | bash +``` + +Archivo fuente: `public/install` diff --git a/clients/web/apps/marketing/astro.config.mjs b/clients/web/apps/marketing/astro.config.mjs new file mode 100644 index 000000000..fe7ed2578 --- /dev/null +++ b/clients/web/apps/marketing/astro.config.mjs @@ -0,0 +1,39 @@ +import { defineConfig } from "astro/config"; +import { loadEnv } from "vite"; +import { PORTS, getPortFromUrl, resolveSiteUrl } from "@corvus/shared/env"; + +const DEFAULT_DEV_URL = `http://localhost:${PORTS.MARKETING}`; +const DEFAULT_PROD_URL = "https://profiletailors.com"; + +export default defineConfig(({ command, mode }) => { + const env = loadEnv(mode, process.cwd(), ""); + const isProdLike = mode === "production" || command === "build"; + const marketingUrl = resolveSiteUrl({ + env, + primaryKey: "MARKETING_URL", + localDefault: DEFAULT_DEV_URL, + productionDefault: DEFAULT_PROD_URL, + genericKeys: ["SITE_URL"], + providerKeys: { + cloudflare: "CF_PAGES_URL", + vercel: "VERCEL_URL", + netlify: "URL", + }, + isProdLike, + }); + const resolvedPort = getPortFromUrl(marketingUrl, PORTS.MARKETING); + + return { + site: marketingUrl, + output: "static", + compressHTML: true, + server: { + host: true, + port: resolvedPort, + }, + preview: { + host: true, + port: resolvedPort, + }, + }; +}); diff --git a/clients/web/apps/marketing/package.json b/clients/web/apps/marketing/package.json new file mode 100644 index 000000000..c92575d45 --- /dev/null +++ b/clients/web/apps/marketing/package.json @@ -0,0 +1,27 @@ +{ + "name": "@corvus/marketing", + "version": "0.1.0", + "private": true, + "description": "Corvus Marketing Site built with Astro", + "type": "module", + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro build", + "preview": "astro preview", + "astro": "astro", + "format": "biome format --write src public package.json astro.config.mjs tsconfig.json README.md", + "check": "biome check src public package.json astro.config.mjs tsconfig.json README.md", + "clean": "rm -rf dist .astro" + }, + "dependencies": { + "@corvus/shared": "workspace:*", + "astro": "^5.17.2", + "sharp": "^0.34.5" + }, + "devDependencies": { + "@biomejs/biome": "2.3.15", + "typescript": "^5.9.3", + "vite": "^6.4.1" + } +} diff --git a/clients/web/apps/marketing/public/favicon.svg b/clients/web/apps/marketing/public/favicon.svg new file mode 100644 index 000000000..32425b389 --- /dev/null +++ b/clients/web/apps/marketing/public/favicon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/clients/web/apps/marketing/public/install b/clients/web/apps/marketing/public/install new file mode 100755 index 000000000..a8122bfb3 --- /dev/null +++ b/clients/web/apps/marketing/public/install @@ -0,0 +1,310 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' + +readonly CLI_PACKAGE="@corvus/cli" +readonly REPO_SLUG="dallay/corvus" +readonly DEFAULT_INSTALL_DIR="$HOME/.local/bin" + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +BLUE='\033[0;34m' +NC='\033[0m' + +INSTALL_METHOD="" + +has_cmd() { + command -v "$1" >/dev/null 2>&1 +} + +print_info() { + printf "%b\n" "${BLUE}$*${NC}" +} + +print_success() { + printf "%b\n" "${GREEN}$*${NC}" +} + +print_warn() { + printf "%b\n" "${YELLOW}$*${NC}" +} + +print_error() { + printf "%b\n" "${RED}$*${NC}" +} + +is_interactive() { + [ -t 0 ] && [ -t 1 ] +} + +confirm() { + local prompt="$1" + local default_answer="${2:-y}" + local answer="" + + if ! is_interactive; then + [ "$default_answer" = "y" ] + return + fi + + if [ "$default_answer" = "y" ]; then + read -r -p "$prompt [Y/n] " answer || true + answer="${answer:-y}" + else + read -r -p "$prompt [y/N] " answer || true + answer="${answer:-n}" + fi + + case "$answer" in + y|Y|yes|YES) + return 0 + ;; + *) + return 1 + ;; + esac +} + +require_supported_shell() { + if [ -z "${BASH_VERSION:-}" ]; then + print_error "This installer requires bash." + exit 1 + fi +} + +detect_binary_asset() { + local os="" + local arch="" + + os="$(uname -s | tr '[:upper:]' '[:lower:]')" + arch="$(uname -m)" + + case "$arch" in + x86_64|amd64) + arch="x64" + ;; + arm64|aarch64) + arch="arm64" + ;; + *) + print_error "Unsupported architecture: $arch" + return 1 + ;; + esac + + case "$os" in + darwin) + printf "corvus-darwin-%s" "$arch" + ;; + linux) + printf "corvus-linux-%s" "$arch" + ;; + *) + print_error "Unsupported OS: $os" + return 1 + ;; + esac +} + +resolve_latest_tag() { + local api_url="https://api.github.com/repos/${REPO_SLUG}/releases/latest" + local tag="" + + if ! has_cmd curl; then + print_error "curl is required to fetch release metadata." + return 1 + fi + + tag="$( + curl --proto '=https' --tlsv1.2 -fsSL "$api_url" \ + | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \ + | head -n 1 + )" + + if ! printf "%s" "$tag" | grep -Eq '^v[0-9A-Za-z._-]+$'; then + print_error "Could not resolve a valid release tag." + return 1 + fi + + printf "%s" "$tag" +} + +install_via_binary() { + local asset="" + local release_tag="" + local install_dir="${CORVUS_INSTALL_DIR:-$DEFAULT_INSTALL_DIR}" + local target_path="" + local download_url="" + local checksum_url="" + local expected_checksum="" + local computed_checksum="" + + asset="$(detect_binary_asset)" + release_tag="${CORVUS_VERSION:-latest}" + + if [ "$release_tag" = "latest" ]; then + release_tag="$(resolve_latest_tag)" + fi + + if ! printf "%s" "$release_tag" | grep -Eq '^v[0-9A-Za-z._-]+$'; then + print_error "Invalid CORVUS_VERSION value: $release_tag" + return 1 + fi + + mkdir -p "$install_dir" + + target_path="${install_dir}/corvus" + download_url="https://github.com/${REPO_SLUG}/releases/download/${release_tag}/${asset}" + checksum_url="${download_url}.sha256" + + print_info "Downloading ${download_url}" + + # Download binary + curl --proto '=https' --tlsv1.2 -fsSL "$download_url" -o "$target_path" + + # Verify checksum for security + print_info "Verifying checksum..." + if ! expected_checksum="$(curl --proto '=https' --tlsv1.2 -fsSL "$checksum_url" 2>/dev/null | awk '{print $1}')"; then + print_warn "Could not download checksum file. Skipping verification." + print_warn "For production use, consider verifying the binary manually." + elif [ -n "$expected_checksum" ]; then + if ! has_cmd sha256sum; then + print_warn "sha256sum not available. Skipping checksum verification." + else + computed_checksum="$(sha256sum "$target_path" | awk '{print $1}')" + if [ "$computed_checksum" != "$expected_checksum" ]; then + print_error "Checksum verification failed!" + print_error "Expected: $expected_checksum" + print_error "Computed: $computed_checksum" + rm -f "$target_path" + return 1 + fi + print_success "Checksum verified successfully" + fi + fi + + chmod 755 "$target_path" + + print_success "Corvus binary installed at ${target_path}" + + if ! printf "%s" ":$PATH:" | grep -q ":${install_dir}:"; then + print_warn "${install_dir} is not in PATH for this shell." + print_warn "Add it with: export PATH=\"${install_dir}:\$PATH\"" + fi +} + +install_via_package_manager() { + case "$INSTALL_METHOD" in + pnpm) + pnpm add --global "$CLI_PACKAGE" + ;; + npm) + npm install --global "$CLI_PACKAGE" + ;; + yarn) + yarn global add "$CLI_PACKAGE" + ;; + bun) + bun add --global "$CLI_PACKAGE" + ;; + *) + print_error "Unknown install method: $INSTALL_METHOD" + return 1 + ;; + esac +} + +select_install_method() { + local candidates=() + local choice="" + local index=1 + + if [ -n "${CORVUS_INSTALL_METHOD:-}" ]; then + INSTALL_METHOD="$CORVUS_INSTALL_METHOD" + return + fi + + has_cmd pnpm && candidates+=("pnpm") + has_cmd npm && candidates+=("npm") + has_cmd yarn && candidates+=("yarn") + has_cmd bun && candidates+=("bun") + + if [ "${#candidates[@]}" -eq 0 ]; then + INSTALL_METHOD="binary" + return + fi + + if ! is_interactive; then + INSTALL_METHOD="binary" + return + fi + + print_info "Choose installation method:" + for method in "${candidates[@]}"; do + printf " %s) %s\n" "$index" "$method" + index=$((index + 1)) + done + printf " %s) binary (direct download)\n" "$index" + + read -r -p "Method [1]: " choice || true + choice="${choice:-1}" + + if [ "$choice" -eq "$index" ] 2>/dev/null; then + INSTALL_METHOD="binary" + return + fi + + if [ "$choice" -ge 1 ] && [ "$choice" -lt "$index" ] 2>/dev/null; then + INSTALL_METHOD="${candidates[$((choice - 1))]}" + return + fi + + print_warn "Invalid selection, using ${candidates[0]}" + INSTALL_METHOD="${candidates[0]}" +} + +post_install_steps() { + if has_cmd corvus; then + print_success "Corvus CLI available: $(corvus --version 2>/dev/null || echo 'installed')" + + if is_interactive && confirm "Run interactive onboarding now?" "y"; then + corvus onboard --interactive || print_warn "Onboarding exited with a non-zero status." + else + print_info "You can run onboarding later with: corvus onboard --interactive" + fi + else + print_warn "corvus command not found in current PATH yet." + print_warn "Open a new terminal and run: corvus --help" + fi +} + +main() { + require_supported_shell + + print_info "Corvus installer" + print_info "Security note: this script uses HTTPS downloads and avoids eval/dynamic shell execution." + + select_install_method + + print_info "Using install method: ${INSTALL_METHOD}" + + case "$INSTALL_METHOD" in + binary) + install_via_binary + ;; + pnpm|npm|yarn|bun) + install_via_package_manager + ;; + *) + print_error "Unsupported method: $INSTALL_METHOD" + exit 1 + ;; + esac + + post_install_steps + + print_success "Done. Try: corvus status" +} + +main "$@" diff --git a/clients/web/apps/marketing/src/env.d.ts b/clients/web/apps/marketing/src/env.d.ts new file mode 100644 index 000000000..f964fe0cf --- /dev/null +++ b/clients/web/apps/marketing/src/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/clients/web/apps/marketing/src/layouts/MarketingLayout.astro b/clients/web/apps/marketing/src/layouts/MarketingLayout.astro new file mode 100644 index 000000000..ab14a45d6 --- /dev/null +++ b/clients/web/apps/marketing/src/layouts/MarketingLayout.astro @@ -0,0 +1,39 @@ +--- +import "../styles/global.css"; + +interface Props { + title?: string; + description?: string; +} + +const { + title = "Corvus | Autonomous AI Infrastructure", + description = + "Corvus is a lightweight autonomous AI runtime designed for secure operations, tiny footprint, and fast deployment.", +} = Astro.props; + +const canonical = new URL(Astro.url.pathname, Astro.site ?? "http://localhost:9988").toString(); +--- + + + + + + + + + + + + + + + + + {title} + + + + + + diff --git a/clients/web/apps/marketing/src/pages/index.astro b/clients/web/apps/marketing/src/pages/index.astro new file mode 100644 index 000000000..978be6f4b --- /dev/null +++ b/clients/web/apps/marketing/src/pages/index.astro @@ -0,0 +1,204 @@ +--- +import MarketingLayout from "../layouts/MarketingLayout.astro"; + +const featureCards = [ + { + title: "Secure by Default", + body: "Pairing, sandboxed tools, explicit allowlists and workspace scoping are enabled from day one.", + }, + { + title: "Tiny Runtime", + body: "Small Rust core with fast cold-start and low memory usage to run on edge hardware.", + }, + { + title: "Swappable Stack", + body: "Providers, channels, memory and tools are composable modules. Replace one layer without rewiring everything.", + }, + { + title: "Production Signals", + body: "Built-in doctor/status flows and deployment-friendly service commands for predictable operations.", + }, +]; + +const testimonials = [ + { + quote: + "We moved from heavy orchestration to Corvus in two sprints and cut infra cost without giving up reliability.", + author: "Platform Team, FinOps Startup", + }, + { + quote: + "The onboarding flow made rollout easy. New developers go from zero to first task in minutes.", + author: "Engineering Lead, Growth Studio", + }, + { + quote: + "Finally an AI runtime that behaves like software infrastructure, not a fragile demo stack.", + author: "CTO, B2B SaaS", + }, +]; +--- + + +
+ + + Corvus + + +
+ +
+
+

AUTONOMOUS INFRASTRUCTURE • RUST CORE • MULTI-CHANNEL

+

Ship AI operators that stay fast, secure, and online.

+

+ Corvus gives your team an operational runtime for agents: strict defaults, tiny footprint, + and a distribution model that works from laptops to edge boxes. +

+ + +
+ One-line installer + curl -fsSL https://profiletailors.com/install | bash + +
+ +
+
+ < 10ms + cold start target +
+
+ < 5MB + runtime memory profile +
+
+ 22+ + provider integrations +
+
+
+ +
+
+

PLATFORM

+

Designed for serious runtime work, not demos.

+
+
+ { + featureCards.map((item) => ( +
+

{item.title}

+

{item.body}

+
+ )) + } +
+
+ +
+
+

POSITIONING

+

Lean architecture with explicit tradeoffs.

+
+
+
+

Traditional agent stacks

+
    +
  • Large runtime overhead before first task
  • +
  • Security model bolted on later
  • +
  • Difficult to run on low-cost hardware
  • +
+
+
+

Corvus approach

+
    +
  • Small binary footprint with quick startup
  • +
  • Pairing, allowlists and scoped tools by default
  • +
  • Portable runtime for local, cloud, and edge
  • +
+
+
+
+ +
+
+

CUSTOMER SIGNALS

+

Teams adopt Corvus for operational confidence.

+
+
+ { + testimonials.map((item) => ( +
+

“{item.quote}”

+ {item.author} +
+ )) + } +
+
+ +
+

Launch your first production-ready agent today.

+

Use the installer wizard, pick your provider, and onboard in minutes.

+ +
+
+ + + + +
diff --git a/clients/web/apps/marketing/src/styles/global.css b/clients/web/apps/marketing/src/styles/global.css new file mode 100644 index 000000000..555a67d18 --- /dev/null +++ b/clients/web/apps/marketing/src/styles/global.css @@ -0,0 +1,421 @@ +@import url("https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Syne:wght@500;700;800&display=swap"); + +:root { + --bg: #04101b; + --panel: rgba(9, 24, 39, 0.85); + --panel-strong: #0d253b; + --text: #ecf5ff; + --muted: #a8bfd8; + --primary: #44e0b3; + --primary-strong: #20b88e; + --line: rgba(164, 201, 234, 0.22); + --ring: rgba(68, 224, 179, 0.5); +} + +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; + min-height: 100%; + background: radial-gradient(circle at 15% -10%, #15314d 0%, transparent 40%), + radial-gradient(circle at 80% 0%, #1f4255 0%, transparent 34%), var(--bg); + color: var(--text); + font-family: "Manrope", "Avenir Next", "Segoe UI", sans-serif; +} + +a { + color: inherit; +} + +main { + display: block; +} + +h1, +h2, +h3 { + margin: 0; + font-family: "Syne", "Trebuchet MS", sans-serif; + line-height: 1.1; +} + +p { + margin: 0; +} + +ul { + margin: 0; + padding-left: 1rem; + color: var(--muted); +} + +li + li { + margin-top: 0.55rem; +} + +.site-backdrop { + position: fixed; + inset: 0; + pointer-events: none; + z-index: -1; + background: linear-gradient(130deg, rgba(68, 224, 179, 0.08), transparent 45%), + linear-gradient(310deg, rgba(44, 127, 255, 0.1), transparent 40%); +} + +.shell { + width: min(1120px, calc(100% - 2.5rem)); + margin-inline: auto; +} + +.topbar { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + padding-top: 1.5rem; +} + +.brand { + display: inline-flex; + align-items: center; + gap: 0.65rem; + text-decoration: none; + font-weight: 700; +} + +.brand-mark { + width: 14px; + height: 14px; + border-radius: 999px; + background: radial-gradient(circle at 30% 30%, #7fffff, var(--primary)); + box-shadow: 0 0 18px rgba(68, 224, 179, 0.65); +} + +nav { + display: flex; + flex-wrap: wrap; + gap: 1rem; +} + +nav a { + text-decoration: none; + font-size: 0.95rem; + color: var(--muted); +} + +nav a:hover, +.inline-link:hover, +.back-link:hover { + color: var(--primary); +} + +.hero { + padding-top: 4.5rem; + padding-bottom: 4.5rem; +} + +.eyebrow { + font-size: 0.75rem; + letter-spacing: 0.16em; + color: var(--primary); + font-weight: 700; +} + +.hero h1, +.section h1 { + margin-top: 1rem; + font-size: clamp(2.2rem, 6vw, 4.45rem); + max-width: 17ch; +} + +.subtitle { + margin-top: 1.25rem; + font-size: clamp(1rem, 2vw, 1.2rem); + line-height: 1.65; + color: var(--muted); + max-width: 62ch; +} + +.hero-actions { + margin-top: 2rem; + display: flex; + flex-wrap: wrap; + gap: 0.85rem; +} + +.btn { + text-decoration: none; + border-radius: 999px; + padding: 0.78rem 1.2rem; + border: 1px solid transparent; + font-weight: 700; + font-size: 0.95rem; +} + +.btn-primary { + color: #062316; + background: linear-gradient(100deg, #9afadf, var(--primary)); +} + +.btn-primary:hover { + background: linear-gradient(100deg, #b7ffe9, #5af2ca); +} + +.btn-ghost { + border-color: var(--line); + color: var(--text); + background: rgba(8, 21, 34, 0.5); +} + +.btn-ghost:hover { + border-color: var(--ring); +} + +.command-card { + margin-top: 2rem; + border-radius: 1rem; + border: 1px solid var(--line); + background: linear-gradient(140deg, rgba(11, 35, 55, 0.88), rgba(6, 20, 32, 0.88)); + padding: 1rem; + display: grid; + grid-template-columns: 1fr auto; + gap: 0.75rem; + align-items: center; +} + +.command-card .label { + grid-column: span 2; + color: var(--muted); + font-size: 0.78rem; +} + +.command-card code { + display: block; + overflow-x: auto; + white-space: nowrap; + font-size: 0.93rem; + color: #cefce9; +} + +.copy-btn { + border: 1px solid var(--line); + border-radius: 0.8rem; + background: rgba(9, 28, 43, 0.95); + color: var(--text); + padding: 0.5rem 0.78rem; + font-weight: 700; + cursor: pointer; +} + +.copy-btn:hover { + border-color: var(--ring); +} + +.metric-grid { + margin-top: 2rem; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.8rem; +} + +.metric-grid article { + border: 1px solid var(--line); + border-radius: 1rem; + padding: 1.1rem; + background: var(--panel); +} + +.metric-grid strong { + display: block; + font-family: "Syne", sans-serif; + font-size: 1.4rem; +} + +.metric-grid span { + color: var(--muted); + font-size: 0.92rem; +} + +.section { + padding: 2rem 0 3.4rem; +} + +.section-head { + margin-bottom: 1.2rem; +} + +.section-head h2 { + margin-top: 0.5rem; + font-size: clamp(1.5rem, 3vw, 2.6rem); + max-width: 18ch; +} + +.feature-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.9rem; +} + +.feature-card { + padding: 1.2rem; + border-radius: 1rem; + border: 1px solid var(--line); + background: var(--panel); + backdrop-filter: blur(6px); +} + +.feature-card h2, +.feature-card h3 { + font-size: 1.18rem; +} + +.feature-card p { + margin-top: 0.6rem; + line-height: 1.6; + color: var(--muted); +} + +.inline-link { + margin-top: 0.8rem; + display: inline-block; + color: #8deed4; + text-decoration: none; + font-weight: 700; +} + +.comparison-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1rem; +} + +.comparison-grid article { + padding: 1.2rem; + border: 1px solid var(--line); + border-radius: 1rem; + background: var(--panel); +} + +.comparison-grid h2, +.comparison-grid h3 { + margin-bottom: 0.75rem; + font-size: 1.15rem; +} + +.testimonial-row { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.9rem; +} + +.testimonial-card { + border: 1px solid var(--line); + background: var(--panel); + border-radius: 1rem; + padding: 1.1rem; +} + +.testimonial-card p { + color: #d7ebfe; + line-height: 1.6; +} + +.testimonial-card span { + margin-top: 0.8rem; + display: inline-block; + color: var(--muted); + font-size: 0.85rem; +} + +.cta-band { + border: 1px solid var(--line); + border-radius: 1rem; + background: linear-gradient(145deg, rgba(14, 42, 61, 0.82), rgba(7, 24, 37, 0.92)); + padding: 1.6rem; +} + +.cta-band h2 { + font-size: clamp(1.5rem, 4vw, 2.45rem); +} + +.cta-band p { + margin-top: 0.8rem; + color: var(--muted); + line-height: 1.55; +} + +.footer { + padding: 1.8rem 0 2.5rem; + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.9rem; + color: var(--muted); + font-size: 0.9rem; +} + +.footer div { + display: flex; + gap: 0.8rem; +} + +.footer a, +.back-link { + color: var(--muted); + text-decoration: none; +} + +.back-link { + margin-bottom: 0.9rem; + display: inline-block; + font-weight: 700; +} + +@keyframes rise { + from { + opacity: 0; + transform: translateY(12px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@media (prefers-reduced-motion: no-preference) { + .hero, + .section, + .footer { + animation: rise 480ms ease both; + } +} + +@media (max-width: 960px) { + .feature-grid, + .comparison-grid, + .testimonial-row, + .metric-grid { + grid-template-columns: 1fr; + } + + .hero { + padding-top: 3rem; + } + + .command-card { + grid-template-columns: 1fr; + } + + .command-card .label { + grid-column: 1; + } + + .footer { + flex-direction: column; + align-items: flex-start; + } +} diff --git a/clients/web/apps/marketing/tsconfig.json b/clients/web/apps/marketing/tsconfig.json new file mode 100644 index 000000000..801d1ab12 --- /dev/null +++ b/clients/web/apps/marketing/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "astro/tsconfigs/strict", + "compilerOptions": { + "baseUrl": "." + } +} diff --git a/clients/web/build.gradle.kts b/clients/web/build.gradle.kts index b89fdf522..807993aab 100644 --- a/clients/web/build.gradle.kts +++ b/clients/web/build.gradle.kts @@ -62,7 +62,7 @@ data class WebAppConfig( // App configurations (extend as needed) val appConfigs = mapOf( "docs" to WebAppConfig("docs", "dist", 4321), - "landing" to WebAppConfig("landing", "dist", 4322), + "marketing" to WebAppConfig("marketing", "dist", 9988), "dashboard" to WebAppConfig("dashboard", "dist", 4323), ) diff --git a/clients/web/package.json b/clients/web/package.json index 0afebc25b..daaf7d261 100644 --- a/clients/web/package.json +++ b/clients/web/package.json @@ -2,16 +2,18 @@ "name": "@corvus/web-monorepo", "version": "0.1.0", "private": true, - "description": "Corvus Web Apps Monorepo - Landing, Docs, Dashboard", + "description": "Corvus Web Apps Monorepo - Marketing, Docs, Dashboard", "type": "module", "scripts": { "build": "pnpm -r run build", - "build:docs": "pnpm --filter docs run build", - "build:landing": "pnpm --filter landing run build", - "build:dashboard": "pnpm --filter dashboard run build", - "dev": "pnpm --filter docs run dev", - "dev:landing": "pnpm --filter landing run dev", - "dev:dashboard": "pnpm --filter dashboard run dev", + "build:docs": "pnpm --filter @corvus/docs run build", + "build:marketing": "pnpm --filter @corvus/marketing run build", + "build:landing": "pnpm run build:marketing", + "build:dashboard": "pnpm --filter @corvus/dashboard run build", + "dev": "pnpm --filter @corvus/docs run dev", + "dev:marketing": "pnpm --filter @corvus/marketing run dev", + "dev:landing": "pnpm run dev:marketing", + "dev:dashboard": "pnpm --filter @corvus/dashboard run dev", "format": "pnpm -r run format", "check": "pnpm -r run check", "clean": "pnpm -r run clean" diff --git a/clients/web/packages/shared/README.md b/clients/web/packages/shared/README.md index 1710149fc..ea3cba58c 100644 --- a/clients/web/packages/shared/README.md +++ b/clients/web/packages/shared/README.md @@ -2,23 +2,33 @@ Shared components, utilities, and styles for Corvus web applications. -## 📦 Contents +## Env Utilities -- `components/` - Reusable UI components -- `styles/` - Shared CSS/styling utilities -- `utils/` - Helper functions -- `types/` - Shared TypeScript types +This package exposes environment helpers under `@corvus/shared/env`. -## 🚀 Usage +Goals: -```typescript -import { Button, Card } from '@corvus/shared/components'; -import { formatDate } from '@corvus/shared/utils'; -import type { User } from '@corvus/shared/types'; +- deterministic env precedence +- provider-aware defaults (Cloudflare, Vercel, Netlify) +- URL normalization and protocol validation (`http`/`https` only) +- single source of truth for common ports + +Usage example: + +```js +import { PORTS, resolveSiteUrl } from "@corvus/shared/env"; + +const site = resolveSiteUrl({ + env, + primaryKey: "MARKETING_URL", + localDefault: `http://localhost:${PORTS.MARKETING}`, + productionDefault: "https://profiletailors.com", + isProdLike, +}); ``` -## 🏗️ Adding shared code +## Adding shared code -1. Add files to appropriate subdirectory -2. Export from `index.ts` -3. Run `pnpm build` to compile +1. Add files to package root or dedicated subfolders +2. Expose them through `package.json` exports +3. Keep APIs runtime-safe for Node + browser contexts diff --git a/clients/web/packages/shared/env.mjs b/clients/web/packages/shared/env.mjs new file mode 100644 index 000000000..68f993b25 --- /dev/null +++ b/clients/web/packages/shared/env.mjs @@ -0,0 +1,153 @@ +/** + * Shared environment helpers for web apps. + * + * Security defaults: + * - Reject non-http(s) URLs + * - Normalize host-only provider URLs to https + * - Trim trailing slash for stable canonical values + */ + +/** @typedef {'cloudflare' | 'vercel' | 'netlify' | 'local'} DeploymentProvider */ + +/** + * @typedef {Object.} EnvMap + */ + +/** + * @param {EnvMap} env + * @returns {DeploymentProvider} + */ +export function detectDeploymentProvider(env = {}) { + if (env.CF_PAGES || env.CF_PAGES_URL) { + return "cloudflare"; + } + if (env.VERCEL || env.VERCEL_URL) { + return "vercel"; + } + if (env.NETLIFY || env.URL || env.DEPLOY_URL) { + return "netlify"; + } + return "local"; +} + +/** + * Read an env key with optional prefixes. + * Order is deterministic and explicit to avoid hidden precedence bugs. + * + * @param {EnvMap} env + * @param {string} key + * @param {string[]} [prefixes] + * @returns {string | undefined} + */ +export function getEnv(env, key, prefixes = ["", "PUBLIC_", "VITE_"]) { + for (const prefix of prefixes) { + const candidate = env[`${prefix}${key}`]; + if (candidate && candidate.trim().length > 0) { + return candidate.trim(); + } + } + return undefined; +} + +/** + * @param {string} raw + * @returns {string} + */ +function ensureHttpProtocol(raw) { + const trimmed = raw.trim(); + if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) { + return trimmed; + } + return `https://${trimmed}`; +} + +/** + * @param {string} raw + * @returns {string} + */ +export function normalizeHttpUrl(raw) { + const withProtocol = ensureHttpProtocol(raw); + const parsed = new URL(withProtocol); + + if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { + throw new Error(`Unsupported URL protocol for site config: ${parsed.protocol}`); + } + + const normalizedPath = parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/$/, ""); + const normalized = `${parsed.protocol}//${parsed.host}${normalizedPath}`; + + return normalized; +} + +/** + * @param {Object} config + * @param {EnvMap} config.env + * @param {string} config.primaryKey + * @param {string} config.localDefault + * @param {string} config.productionDefault + * @param {string[]} [config.genericKeys] + * @param {Object} [config.providerKeys] + * @param {string} [config.providerKeys.cloudflare] + * @param {string} [config.providerKeys.vercel] + * @param {string} [config.providerKeys.netlify] + * @param {boolean} [config.isProdLike] + * @returns {string} + */ +export function resolveSiteUrl(config) { + const { + env, + primaryKey, + localDefault, + productionDefault, + genericKeys = ["SITE_URL"], + providerKeys, + isProdLike = false, + } = config; + + const explicit = getEnv(env, primaryKey); + if (explicit) { + return normalizeHttpUrl(explicit); + } + + const provider = detectDeploymentProvider(env); + if (providerKeys) { + const providerKey = providerKeys[provider]; + if (providerKey) { + const providerValue = getEnv(env, providerKey, [""]); + if (providerValue) { + return normalizeHttpUrl(providerValue); + } + } + } + + for (const key of genericKeys) { + const value = getEnv(env, key); + if (value) { + return normalizeHttpUrl(value); + } + } + + return normalizeHttpUrl(isProdLike ? productionDefault : localDefault); +} + +/** + * @param {string} siteUrl + * @param {number} fallbackPort + * @returns {number} + */ +export function getPortFromUrl(siteUrl, fallbackPort) { + const parsed = new URL(siteUrl); + if (parsed.port) { + const parsedPort = Number.parseInt(parsed.port, 10); + if (Number.isFinite(parsedPort)) { + return parsedPort; + } + } + return fallbackPort; +} + +export const PORTS = { + MARKETING: 9988, + DOCS: 4321, + DASHBOARD: 4323, +}; diff --git a/clients/web/packages/shared/package.json b/clients/web/packages/shared/package.json index bbd6a8a8e..f953dbe29 100644 --- a/clients/web/packages/shared/package.json +++ b/clients/web/packages/shared/package.json @@ -10,6 +10,9 @@ ".": { "import": "./dist/index.js", "types": "./dist/index.d.ts" + }, + "./env": { + "import": "./env.mjs" } }, "scripts": { diff --git a/clients/web/pnpm-lock.yaml b/clients/web/pnpm-lock.yaml index 007c06600..8c08dab24 100644 --- a/clients/web/pnpm-lock.yaml +++ b/clients/web/pnpm-lock.yaml @@ -36,7 +36,27 @@ importers: specifier: 2.1.11 version: 2.1.11 - apps/landing: {} + apps/marketing: + dependencies: + '@corvus/shared': + specifier: workspace:* + version: link:../../packages/shared + astro: + specifier: ^5.17.2 + version: 5.17.2(rollup@4.57.1)(typescript@5.9.3) + sharp: + specifier: ^0.34.5 + version: 0.34.5 + devDependencies: + '@biomejs/biome': + specifier: 2.3.15 + version: 2.3.15 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vite: + specifier: ^6.4.1 + version: 6.4.1 packages/shared: {} diff --git a/dev/config.template.toml b/dev/config.template.toml index f08c1cc8b..acb9e58da 100755 --- a/dev/config.template.toml +++ b/dev/config.template.toml @@ -8,5 +8,4 @@ default_temperature = 0.7 [gateway] port = 3000 -host = "[::]" -allow_public_bind = true +host = "127.0.0.1"