Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# corvus/.env.example
#
# Canonical list of environment variables used across this repository.
# - DO NOT store real secrets here. Use CI secrets or your local secret manager (direnv, .env.local, etc.).
# - This file is a documentation + example file. Copy it to .env for local development and fill in real values where required.
# - Frontend/public envs: the shared getEnv helper accepts keys with no prefix, PUBLIC_ or VITE_.

########################################
# Global / environment
########################################
# NODE_ENV - common runtime mode. optional. values: development|production
NODE_ENV=development

# Generic site fallback used by resolveSiteUrl() when provider-specific keys are missing
# Example: https://example.com or http://localhost:4321
SITE_URL=http://localhost:9988

########################################
# Local dev ports (sane defaults used by shared env helper)
########################################
# These are the PORTS mapping used across the web packages. Change only if you have port conflicts.
CHAT_PORT=4323
DOCS_PORT=4321
MARKETING_PORT=9988
PLUGINS_PORT=9990

########################################
# Web apps / Static sites
########################################
# Docs site
DOCS_URL=http://localhost:${DOCS_PORT}
# Marketing site - provider-aware. Local default uses MARKETING_PORT above.
MARKETING_URL=http://localhost:${MARKETING_PORT}
Comment on lines +31 to +33
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Shell variable interpolation may not work with all env loaders.

The ${DOCS_PORT} and ${MARKETING_PORT} syntax relies on shell interpolation, which won't work with many env loaders (e.g., Node.js dotenv doesn't expand variables by default). Consider using literal values or documenting this limitation.

Option 1: Use literal values
-DOCS_URL=http://localhost:${DOCS_PORT}
+DOCS_URL=http://localhost:4321
 # Marketing site - provider-aware. Local default uses MARKETING_PORT above.
-MARKETING_URL=http://localhost:${MARKETING_PORT}
+MARKETING_URL=http://localhost:9988
Option 2: Add a note about interpolation

Add a comment above these lines:

+# Note: ${VAR} interpolation requires shell expansion or dotenv-expand; use literal ports if your loader doesn't support it.
 DOCS_URL=http://localhost:${DOCS_PORT}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
DOCS_URL=http://localhost:${DOCS_PORT}
# Marketing site - provider-aware. Local default uses MARKETING_PORT above.
MARKETING_URL=http://localhost:${MARKETING_PORT}
DOCS_URL=http://localhost:4321
# Marketing site - provider-aware. Local default uses MARKETING_PORT above.
MARKETING_URL=http://localhost:9988
Suggested change
DOCS_URL=http://localhost:${DOCS_PORT}
# Marketing site - provider-aware. Local default uses MARKETING_PORT above.
MARKETING_URL=http://localhost:${MARKETING_PORT}
# Note: ${VAR} interpolation requires shell expansion or dotenv-expand; use literal ports if your loader doesn't support it.
DOCS_URL=http://localhost:${DOCS_PORT}
# Marketing site - provider-aware. Local default uses MARKETING_PORT above.
MARKETING_URL=http://localhost:${MARKETING_PORT}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.env.example around lines 31 - 33, The example uses shell-style
interpolation for DOCS_URL and MARKETING_URL (DOCS_URL, MARKETING_URL
referencing DOCS_PORT, MARKETING_PORT) which many env loaders won't expand;
either replace those entries with concrete default literals (e.g., use explicit
port numbers) or add a short comment above these lines explaining that
${DOCS_PORT} and ${MARKETING_PORT} require shell expansion (or recommend using
dotenv-expand) and showing the literal fallback format so users know to set full
URLs when using non-shell env loaders.


# Hosting provider fallbacks (set by provider automatically in their build environments)
# These are read by resolveSiteUrl({ providerKeys: { cloudflare: 'CF_PAGES_URL', vercel: 'VERCEL_URL', netlify: 'URL' }})
CF_PAGES_URL=
URL=
VERCEL_URL=

########################################
# Frontend public keys (safe for client; mark explicitly if client-side)
########################################
# Public keys that are intentionally exposed to browser code should use PUBLIC_ or VITE_ prefix
# Example (marketing analytics; safe to be public):
PUBLIC_AHREFS_KEY=your_ahrefs_public_key_here

########################################
# Agent runtime / backend (Corvus)
########################################
# CORVUS_API_KEY - API key for internal agent auth (keep secret)
CORVUS_API_KEY=your_corvus_api_key_here
CORVUS_GATEWAY_HOST=127.0.0.1
CORVUS_GATEWAY_PORT=4000
CORVUS_OPEN_SKILLS_ENABLED=false
CORVUS_WORKSPACE=default

########################################
# SurrealDB (primary app DB for agent runtime)
########################################
# Use a local dev SurrealDB for development. Production should be set via secrets.
CORVUS_SURREALDB_DATABASE=corvus_db
CORVUS_SURREALDB_NAMESPACE=corvus
CORVUS_SURREALDB_PASSWORD=your_local_dev_db_password_here
CORVUS_SURREALDB_TOKEN=
CORVUS_SURREALDB_URL=http://127.0.0.1:8000
CORVUS_SURREALDB_USERNAME=root

# Test-specific SurrealDB (used by CI/tests when present)
CORVUS_TEST_SURREALDB_DATABASE=corvus_test_db
CORVUS_TEST_SURREALDB_NAMESPACE=corvus_test
CORVUS_TEST_SURREALDB_URL=http://127.0.0.1:8001

########################################
# Third-party provider keys (examples/placeholders)
########################################
# Google APIs (if used)
GOOGLE_API_KEY=your_google_api_key_here

# Anthropic / OpenAI / LLM providers
ANTHROPIC_API_KEY=your_anthropic_api_key_here
OPENAI_API_KEY=your_openai_api_key_here

# GitHub / CI tokens
GH_TOKEN=your_gh_token_here
GITHUB_TOKEN=your_github_token_here

########################################
# CI, release & publishing secrets (store in GitHub Secrets / CI provider)
########################################
# GPG / signing used in release pipelines (example placeholders only)
SIGNING_IN_MEMORY_KEY=your_signing_key_base64_here
SIGNING_IN_MEMORY_KEY_PASSWORD=your_signing_key_password_here

# Maven Central
MAVEN_CENTRAL_USERNAME=your_maven_username
MAVEN_CENTRAL_PASSWORD=your_maven_password

# Cargo / crates.io
CARGO_REGISTRY_TOKEN=your_crates_io_token_here

# NPM / packages
NPM_TOKEN=your_npm_token_here

# DockerHub
DOCKERHUB_USERNAME=your_dockerhub_username_here
DOCKERHUB_TOKEN=your_dockerhub_token_here

# Code quality / telemetry
SONAR_TOKEN=
CODECOV_TOKEN=

########################################
# Gradle / build / repo config
########################################
# Project coordinates and build cache credentials (optional)
GROUP=com.profiletailors
VERSION=0.1.0
BUILD_CACHE_USER=
BUILD_CACHE_PWD=
PRIREPO_URL=
PRIREPO_USERNAME=
PRIREPO_PASSWORD=
ENABLE_LOCAL_CONFIG=true

########################################
# Optional / feature toggles & local helpers
########################################
# If you need a shorter local override for SITE_URL per app, set the specific one above (MARKETING_URL, DOCS_URL, ...)

# Example debug flags
DEBUG=true

########################################
# Notes
########################################
# - Copy this file to .env and fill in the secret values before running local services.
# - For CI and releases: add all SECRET values (signing keys, repository tokens, package tokens) to your
# repository's GitHub Secrets or your CI provider's secret store. Never commit real credentials to the repo.
# - Frontend/public variables: prefix with PUBLIC_ or VITE_ if they are safe to expose to the browser.
# - The repo's shared env helper will prefer provider-specific envs (CF_PAGES_URL, VERCEL_URL, URL) when present
# and fall back to the primary key (MARKETING_URL / DOCS_URL / SITE_URL) for site resolution.
15 changes: 7 additions & 8 deletions .github/config/changelog.json
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/mikepenz/release-changelog-builder-action/main/configs/json-schema.json",
"template": "## 📢 What's Changed\n\n#{{CHANGELOG}}\n<details>\n<summary>🔐 Uncategorized Changes</summary>\n\n#{{UNCATEGORIZED}}\n</details>\n\n## Contributors:\n- #{{CONTRIBUTORS}}",
"commit_template": "- #{{TITLE}} #{{MERGE_SHA}}",
"pr_template": "- #{{TITLE}} - #{{MERGE_SHA}} - ##{{NUMBER}}",
"template": "## 📢 What's Changed\n\n{{CHANGELOG}}\n\n<details>\n<summary>🔐 Uncategorized Changes</summary>\n\n{{UNCATEGORIZED}}\n</details>\n\n## Contributors:\n- {{CONTRIBUTORS}}",
"commit_template": "- {{TITLE}} ({{MERGE_SHA}})",
"pr_template": "- {{TITLE}} — #{{NUMBER}} (merge {{MERGE_SHA}})",
"empty_template": "- No changes in this release",
"exclude_merge_branches": ["hotfix/temp-*", "owner/qa", "owner/test"],
"tag_resolver": {
Expand Down Expand Up @@ -73,13 +72,13 @@
"on_property": "title",
"method": "regexr",
"pattern": "!!\\s*$",
"target": "$'pr|breaking-change"
"target": "pr|breaking-change"
},
{
"on_property": "title",
"method": "regexr",
"pattern": "\\.\\.\\s*$",
"target": "$'pr|changelog-ignore"
"pattern": "\\.\\.\\.\\s*$",
"target": "pr|changelog-ignore"
},
{
"on_property": "title",
Expand All @@ -89,7 +88,7 @@
}
],
"sort": {
"order": "ASC",
"order": "DESC",
"on_property": "mergedAt"
},
"max_tags_to_fetch": 200,
Expand Down
49 changes: 47 additions & 2 deletions clients/web/apps/docs/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,55 @@
import starlight from "@astrojs/starlight";
import { defineConfig } from "astro/config";
import { viewTransitions } from "astro-vtbot/starlight-view-transitions";
import { getPortFromUrl, PORTS, resolveSiteUrl } from "@corvus/shared/env";
import { loadEnv } from "vite";

const DEFAULT_DEV_URL = `http://localhost:${PORTS.DOCS}`;
const DEFAULT_PROD_URL = "https://docs.profiletailors.com";

const mode = process.env.NODE_ENV || "production";
const env = loadEnv(mode, process.cwd(), "");
const docsUrl = resolveSiteUrl({
env,
primaryKey: "DOCS_URL",
localDefault: DEFAULT_DEV_URL,
productionDefault: DEFAULT_PROD_URL,
genericKeys: ["SITE_URL"],
providerKeys: {
cloudflare: "CF_PAGES_URL",
vercel: "VERCEL_URL",
netlify: "URL",
},
isProdLike: mode === "production",
});
const resolvedPort = getPortFromUrl(docsUrl, PORTS.DOCS);

function computeBaseFromUrl(url) {
if (!url) return "/";
try {
const pathname = new URL(url).pathname || "/";
// If root, keep '/'
if (pathname === "/") return "/";
// Remove trailing slash for non-root paths
return pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
} catch (e) {
return "/";
}
}

const base = computeBaseFromUrl(docsUrl);

export default defineConfig({
site: "https://dallay.github.io",
base: "/corvus",
site: docsUrl,
base, // computed from the provider/site URL so subpath deployments work
server: {
host: true,
port: resolvedPort,
},
preview: {
host: true,
port: resolvedPort,
},
integrations: [
starlight({
title: "Corvus",
Expand Down
115 changes: 115 additions & 0 deletions clients/web/packages/shared/test/env.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { describe, it, expect } from "vitest";
import { resolveSiteUrl, getPortFromUrl, PORTS } from "../env.mjs";

const DEFAULT_DEV_URL = `http://localhost:${PORTS.DOCS}`;
const DEFAULT_PROD_URL = "https://docs.profiletailors.com";

describe("resolveSiteUrl and getPortFromUrl matrix", () => {
it("resolveSiteUrl_prefers_DOCS_URL_over_providers_and_falls_back_to_SITE_URL", () => {
// Explicit DOCS_URL should win
const explicit = resolveSiteUrl({
env: { DOCS_URL: "http://explicit.example/path" },
primaryKey: "DOCS_URL",
localDefault: DEFAULT_DEV_URL,
productionDefault: DEFAULT_PROD_URL,
genericKeys: ["SITE_URL"],
providerKeys: { cloudflare: "CF_PAGES_URL", vercel: "VERCEL_URL", netlify: "URL" },
isProdLike: false,
});
expect(explicit).toBe("http://explicit.example/path");

// Cloudflare provider should be selected when DOCS_URL missing
const cloudflare = resolveSiteUrl({
env: { CF_PAGES_URL: "docs.cloudflare.test/sub" },
primaryKey: "DOCS_URL",
localDefault: DEFAULT_DEV_URL,
productionDefault: DEFAULT_PROD_URL,
genericKeys: ["SITE_URL"],
providerKeys: { cloudflare: "CF_PAGES_URL", vercel: "VERCEL_URL", netlify: "URL" },
isProdLike: false,
});
expect(cloudflare).toBe("https://docs.cloudflare.test/sub");

// Vercel provider
const vercel = resolveSiteUrl({
env: { VERCEL_URL: "my-vercel.app/docs" },
primaryKey: "DOCS_URL",
localDefault: DEFAULT_DEV_URL,
productionDefault: DEFAULT_PROD_URL,
genericKeys: ["SITE_URL"],
providerKeys: { cloudflare: "CF_PAGES_URL", vercel: "VERCEL_URL", netlify: "URL" },
isProdLike: false,
});
expect(vercel).toBe("https://my-vercel.app/docs");

// Netlify provider (URL)
const netlify = resolveSiteUrl({
env: { URL: "https://netlify.example/base/" },
primaryKey: "DOCS_URL",
localDefault: DEFAULT_DEV_URL,
productionDefault: DEFAULT_PROD_URL,
genericKeys: ["SITE_URL"],
providerKeys: { cloudflare: "CF_PAGES_URL", vercel: "VERCEL_URL", netlify: "URL" },
isProdLike: false,
});
expect(netlify).toBe("https://netlify.example/base");

// Generic SITE_URL fallback
const generic = resolveSiteUrl({
env: { SITE_URL: "http://fallback.test:9001/fpath" },
primaryKey: "DOCS_URL",
localDefault: DEFAULT_DEV_URL,
productionDefault: DEFAULT_PROD_URL,
genericKeys: ["SITE_URL"],
providerKeys: { cloudflare: "CF_PAGES_URL", vercel: "VERCEL_URL", netlify: "URL" },
isProdLike: false,
});
expect(generic).toBe("http://fallback.test:9001/fpath");

// No envs -> dev fallback
const fallbackDev = resolveSiteUrl({
env: {},
primaryKey: "DOCS_URL",
localDefault: DEFAULT_DEV_URL,
productionDefault: DEFAULT_PROD_URL,
genericKeys: ["SITE_URL"],
providerKeys: { cloudflare: "CF_PAGES_URL", vercel: "VERCEL_URL", netlify: "URL" },
isProdLike: false,
});
expect(fallbackDev).toBe(DEFAULT_DEV_URL);

// No envs -> prod fallback when isProdLike
const fallbackProd = resolveSiteUrl({
env: {},
primaryKey: "DOCS_URL",
localDefault: DEFAULT_DEV_URL,
productionDefault: DEFAULT_PROD_URL,
genericKeys: ["SITE_URL"],
providerKeys: { cloudflare: "CF_PAGES_URL", vercel: "VERCEL_URL", netlify: "URL" },
isProdLike: true,
});
expect(fallbackProd).toBe(DEFAULT_PROD_URL);
});

it("getPortFromUrl_derives_port_from_docsUrl", () => {
// URL with explicit port
const withPort = "http://example.com:8080/path";
expect(getPortFromUrl(withPort, PORTS.DOCS)).toBe(8080);

// URL without port -> fallback
const withoutPort = "https://example.com/path";
expect(getPortFromUrl(withoutPort, PORTS.DOCS)).toBe(PORTS.DOCS);

// Derived from resolveSiteUrl result
const docsUrl = resolveSiteUrl({
env: { DOCS_URL: "http://localhost:12345/docs" },
primaryKey: "DOCS_URL",
localDefault: DEFAULT_DEV_URL,
productionDefault: DEFAULT_PROD_URL,
genericKeys: ["SITE_URL"],
providerKeys: { cloudflare: "CF_PAGES_URL", vercel: "VERCEL_URL", netlify: "URL" },
isProdLike: false,
});
expect(getPortFromUrl(docsUrl, PORTS.DOCS)).toBe(12345);
});
});
Loading