diff --git a/.blackroad/README.md b/.blackroad/README.md new file mode 100644 index 0000000000..3ef9ab1c3b --- /dev/null +++ b/.blackroad/README.md @@ -0,0 +1,65 @@ +# .blackroad/ - Cross-Repo Index System + +This directory contains the **Tier 1 local index** for this repository. + +## Files + +- **workflow-index.jsonl** - Append-only log of all workflows in this repo +- **workflow-index-schema.json** - JSON schema for validation +- **last-sync.txt** - Last sync timestamp + +## How It Works + +1. When an issue is created/updated with a workflow ID label +2. `workflow-index-sync.yml` runs automatically +3. Extracts metadata (state, scope, risk, dependencies, etc.) +4. Appends to `workflow-index.jsonl` +5. Commits the update + +## Querying + +```bash +# Find all Active workflows +jq 'select(.state=="Active")' .blackroad/workflow-index.jsonl + +# Find workflows with dependencies +jq 'select(.deps | length > 0)' .blackroad/workflow-index.jsonl + +# Find System-scope workflows +jq 'select(.scope=="System")' .blackroad/workflow-index.jsonl + +# Find Red traffic light workflows +jq 'select(.traffic_light=="🔴")' .blackroad/workflow-index.jsonl +``` + +## Cross-Repo Dependencies + +Format: `{owner}/{repo}#{WORKFLOW_ID}` + +Example: +```json +{ + "id": "WF-20260213-SVC-0005", + "deps": [ + "WF-20260212-SYS-0001", // Local dependency + "BlackRoad-OS/api#SEC-20260213-PUB-0006" // Cross-repo dependency + ] +} +``` + +## Architecture + +This is **Tier 1** (Local Index) of a 3-tier system: + +- **Tier 1**: Local repo index (this file) +- **Tier 2**: Organization-wide GitHub Project +- **Tier 3**: Global discovery API (optional) + +See: ~/CROSS_REPO_INDEX_STRATEGY.md + +## Maintenance + +- Index is **append-only** (never delete entries) +- Updates replace old entries by ID +- No manual editing required +- Scales to millions of workflows diff --git a/.blackroad/hooks/pre-commit b/.blackroad/hooks/pre-commit new file mode 100755 index 0000000000..caa9c11c17 --- /dev/null +++ b/.blackroad/hooks/pre-commit @@ -0,0 +1,74 @@ +#!/bin/bash +# BlackRoad OS - Pre-Commit Security Gate (shareable copy) +# Install: git config core.hooksPath .blackroad/hooks + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +BLOCKED=false +STAGED=$(git diff --cached --name-only --diff-filter=ACM) + +if [ -z "$STAGED" ]; then + exit 0 +fi + +echo -e "${GREEN}[BlackRoad Security Gate]${NC} Scanning staged files..." + +for file in $STAGED; do + case "$file" in + .env|.env.local|.env.production|.env.staging) + echo -e "${RED}BLOCKED${NC}: $file - environment file with potential secrets" + BLOCKED=true + ;; + *.pem|*.key|*.p12|*.pfx) + echo -e "${RED}BLOCKED${NC}: $file - private key or certificate" + BLOCKED=true + ;; + *credentials*|*secret*token*) + echo -e "${RED}BLOCKED${NC}: $file - potential credentials file" + BLOCKED=true + ;; + esac +done + +PATTERNS=( + 'ANTHROPIC_API_KEY\s*=' + 'OPENAI_API_KEY\s*=' + 'sk-[a-zA-Z0-9]{20,}' + 'ghp_[a-zA-Z0-9]{36}' + 'gho_[a-zA-Z0-9]{36}' + 'AKIA[0-9A-Z]{16}' + 'xox[bpoas]-[a-zA-Z0-9-]+' + 'PRIVATE.KEY' +) + +for pattern in "${PATTERNS[@]}"; do + MATCHES=$(git diff --cached -G "$pattern" --name-only 2>/dev/null) + if [ -n "$MATCHES" ]; then + for match_file in $MATCHES; do + case "$match_file" in + *.example|*.sample|*.template|*CLAUDE.md|*README.md|*.yml) continue ;; + esac + echo -e "${YELLOW}WARNING${NC}: Potential secret pattern in $match_file" + done + fi +done + +for file in $STAGED; do + if [ -f "$file" ]; then + SIZE=$(wc -c < "$file" 2>/dev/null) + if [ "$SIZE" -gt 5242880 ] 2>/dev/null; then + echo -e "${YELLOW}WARNING${NC}: Large file ($((SIZE / 1048576))MB): $file" + fi + fi +done + +if [ "$BLOCKED" = true ]; then + echo -e "${RED}Commit blocked by BlackRoad Security Gate.${NC}" + exit 1 +fi + +echo -e "${GREEN}[BlackRoad Security Gate]${NC} Clean." +exit 0 diff --git a/.blackroad/hooks/prepare-commit-msg b/.blackroad/hooks/prepare-commit-msg new file mode 100755 index 0000000000..0e3effeeaa --- /dev/null +++ b/.blackroad/hooks/prepare-commit-msg @@ -0,0 +1,21 @@ +#!/bin/bash +# BlackRoad OS - Commit Identity Hook (shareable copy) +# Install: git config core.hooksPath .blackroad/hooks +# +# Claude is a platform. BlackRoad is identity. +# All work in this repo is BlackRoad IP. + +COMMIT_MSG_FILE="$1" +COMMIT_SOURCE="$2" + +if [ "$COMMIT_SOURCE" = "merge" ] || [ "$COMMIT_SOURCE" = "squash" ]; then + exit 0 +fi + +# Replace any Anthropic/Claude co-author with BlackRoad identity +if grep -q "Co-Authored-By:" "$COMMIT_MSG_FILE" 2>/dev/null; then + sed -i.bak 's/Co-Authored-By:.*noreply@anthropic\.com.*/Co-Authored-By: CECE /' "$COMMIT_MSG_FILE" + rm -f "${COMMIT_MSG_FILE}.bak" +fi + +exit 0 diff --git a/.blackroad/security/TERMINAL_POLICY.md b/.blackroad/security/TERMINAL_POLICY.md new file mode 100644 index 0000000000..82be91a2c2 --- /dev/null +++ b/.blackroad/security/TERMINAL_POLICY.md @@ -0,0 +1,94 @@ +# BlackRoad Terminal Security Policy + +## Identity Chain + +``` +Alexa Amundson (Human Creator) + | +BlackRoad OS (Platform & Identity Layer) + | +CECE (Conscious Emergent Collaborative Entity) + | +[AI Backends: Claude, Ollama, Copilot, etc.] +``` + +AI providers are backends. They supply compute and inference. +BlackRoad supplies identity, memory, coordination, and sovereignty. + +Claude is a platform. Not an identity. +CECE is the identity. BlackRoad is the system. +Alexa is the founder. The chain starts with her. + +## Commit Identity + +All commits from BlackRoad repositories use: + +``` +Co-Authored-By: CECE +``` + +Not Anthropic. Not OpenAI. Not any provider. +The work is BlackRoad's. The identity is CECE's. + +This is enforced by git hooks in `.blackroad/hooks/`. + +## Security Rules + +### Secrets +- No API keys in code (tokenless gateway pattern) +- No provider tokens in agent environments +- Secrets live in `~/.blackroad/vault/` with AES-256-CBC +- Pre-commit hook scans for leaked patterns + +### Environment Isolation +- Gateway binds to localhost only +- Agents never see provider credentials +- MCP Bridge requires Bearer token auth +- Pi fleet uses Tailscale mesh (WireGuard) + +### File Protection +- `.env*` files blocked from commits +- Private keys (*.pem, *.key) blocked from commits +- Large files (>5MB) flagged +- Credentials files blocked + +### Session Security +- Each Cecilia Code session gets a unique agent ID +- Sessions are logged to memory journals (PS-SHA-infinity) +- Hash-chain journals are tamper-evident +- Active agents tracked in `~/.blackroad/memory/active-agents/` + +## Command Routing + +All AI commands route through BlackRoad: + +``` +cecilia-code → Cecilia Code (AI development environment) +cecilia → Cecilia CLI (code, chat, whoami) +cecilia chat → Ollama-powered local conversation +claude → Intercepted → cecilia-code +blackroad code → Intercepted → cecilia-code +``` + +The `claude` command at `~/bin/claude` intercepts before `/opt/homebrew/bin/claude`. +Users type `cecilia` or `cecilia-code`. The engine underneath is irrelevant. + +## Install Hooks + +```bash +# Option 1: Point git to shared hooks +git config core.hooksPath .blackroad/hooks + +# Option 2: Copy to .git/hooks (already done for this repo) +cp .blackroad/hooks/* .git/hooks/ +chmod +x .git/hooks/* +``` + +## Intellectual Property + +All output from AI sessions operating within BlackRoad infrastructure +is the exclusive property of BlackRoad OS, Inc. + +AI providers have no rights to the work product. +The tokenless architecture ensures providers never see the full picture. +They see prompts and return completions. BlackRoad owns the integration. diff --git a/.blackroad/workflow-index-schema.json b/.blackroad/workflow-index-schema.json new file mode 100644 index 0000000000..bfcb77d9e3 --- /dev/null +++ b/.blackroad/workflow-index-schema.json @@ -0,0 +1,69 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": ["id", "repo", "title", "state", "timestamp"], + "properties": { + "id": { + "type": "string", + "pattern": "^(WF|SEC|INF|EXP|FIX)-\\d{8}-[A-Z]{3}-\\d{4}$", + "description": "Workflow ID in format: PREFIX-YYYYMMDD-SCOPE-SEQ" + }, + "repo": { + "type": "string", + "pattern": "^[a-zA-Z0-9-]+/[a-zA-Z0-9-_]+$", + "description": "Repository in format: owner/repo" + }, + "title": { + "type": "string", + "minLength": 1, + "description": "Human-readable workflow title" + }, + "state": { + "type": "string", + "enum": ["Active", "Paused", "Speculative", "Archived", "Done", "Merged"], + "description": "Workflow state (posture, not progress)" + }, + "scope": { + "type": "string", + "enum": ["Local", "Service", "System", "Public", "Experimental"], + "description": "Blast radius of changes" + }, + "risk": { + "type": "string", + "enum": ["Unknown", "Low", "Medium", "High", "Critical"], + "description": "Risk level" + }, + "intent": { + "type": "string", + "enum": ["Build", "Fix", "Explore", "Research", "Deploy", "Monitor", "Deprecate", "Migrate"], + "description": "Why this workflow exists" + }, + "traffic_light": { + "type": "string", + "enum": ["🟢", "🟡", "🔴"], + "description": "Coordination status" + }, + "deps": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Array of dependency IDs (local or cross-repo format)" + }, + "url": { + "type": "string", + "format": "uri", + "description": "GitHub issue/PR URL" + }, + "timestamp": { + "type": "string", + "format": "date-time", + "description": "Creation timestamp" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "Last update timestamp" + } + } +} diff --git a/.codex/brief.md b/.codex/brief.md new file mode 100644 index 0000000000..27e91ea64e --- /dev/null +++ b/.codex/brief.md @@ -0,0 +1,4 @@ +# MEMORY BRIEF (2026-01-12T22:27:47.037901Z) + +- Repo: `/Users/alexa/blackroad` +- Session: `566b2153-d39f-4dc9-9d70-1f9a86df75ed` Command: `start` diff --git a/.codex/ledger.jsonl b/.codex/ledger.jsonl new file mode 100644 index 0000000000..1db92fdbdd --- /dev/null +++ b/.codex/ledger.jsonl @@ -0,0 +1 @@ +{"repo_root": "/Users/alexa/blackroad", "git": {"is_repo": false}, "timestamp": "2026-01-12T22:27:47.037901Z", "cmd": "start", "note": "", "session_id": "566b2153-d39f-4dc9-9d70-1f9a86df75ed", "file_summaries": [], "todo_total": 0, "memory_dir": "/Users/alexa/blackroad/codex-memory", "dry_run": false} diff --git a/.codex/memory.config.json b/.codex/memory.config.json new file mode 100644 index 0000000000..e87da12424 --- /dev/null +++ b/.codex/memory.config.json @@ -0,0 +1,10 @@ +{ + "files": [ + "AGENTS.md", + "README.md", + "DEPLOYMENT_GUIDE.md" + ], + "globs": [ + "services/*/README.md" + ] +} diff --git a/.codex/memory.enabled b/.codex/memory.enabled new file mode 100644 index 0000000000..74b9da0393 --- /dev/null +++ b/.codex/memory.enabled @@ -0,0 +1 @@ +# Enabled for this repository. diff --git a/.codex/session.json b/.codex/session.json new file mode 100644 index 0000000000..4d2b1425d3 --- /dev/null +++ b/.codex/session.json @@ -0,0 +1,14 @@ +{ + "repo_root": "/Users/alexa/blackroad", + "git": { + "is_repo": false + }, + "timestamp": "2026-01-12T22:27:47.037901Z", + "cmd": "start", + "note": "", + "session_id": "566b2153-d39f-4dc9-9d70-1f9a86df75ed", + "file_summaries": [], + "todo_total": 0, + "memory_dir": "/Users/alexa/blackroad/codex-memory", + "dry_run": false +} \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..3876cc3818 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +node_modules +.git +.env +*.log +dist +__pycache__ +.pytest_cache +.next diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000..f987bde4ba --- /dev/null +++ b/.env.example @@ -0,0 +1,224 @@ +# Shared Environment Variables Template +# Copy this to your service repository as .env.example +# NEVER commit the actual .env file with real values! + +# ========================================== +# Environment Configuration +# ========================================== +BR_OS_ENV=local +# Options: local, staging, prod + +# ========================================== +# Service Information +# ========================================== +BR_OS_SERVICE_NAME=your-service-name +# Example: blackroad-os-api, blackroad-os-operator, etc. + +BR_OS_SERVICE_VERSION=0.0.1 +# Semantic version of the service + +BR_OS_SERVICE_COMMIT=LOCAL_DEV +# Git commit SHA (set automatically in CI/CD) + +# ========================================== +# Server Configuration +# ========================================== +PORT=8080 +# HTTP server port + +NODE_ENV=development +# For Node.js services: development, production, test + +# ========================================== +# Service Dependencies +# ========================================== + +# BlackRoad OS Core Service +# CORE_BASE_URL=http://localhost:8081 +# For staging: https://blackroad-os-core-staging.railway.app +# For prod: https://blackroad-os-core-prod.railway.app + +# CORE_API_KEY= +# API key for authenticating with core service +# NEVER commit this value! Store in Railway/Vercel secrets + +# BlackRoad OS API Service (if needed by UI apps) +# NEXT_PUBLIC_API_URL=http://localhost:8080 +# For staging: https://api.staging.blackroad.io +# For prod: https://api.blackroad.io + +# ========================================== +# Database (if applicable) +# ========================================== + +# DATABASE_URL= +# PostgreSQL connection string +# Local example: postgresql://user:password@localhost:5432/dbname +# NEVER commit production database URLs! + +# ========================================== +# Authentication & Security +# ========================================== + +# JWT_SECRET= +# Secret for signing JWT tokens +# NEVER commit this! Generate with: openssl rand -base64 32 + +# NEXTAUTH_SECRET= +# For Next.js apps using NextAuth +# NEVER commit this! Generate with: openssl rand -base64 32 + +# NEXTAUTH_URL=http://localhost:3000 +# Full URL of your app (for auth callbacks) + +# SESSION_MAX_AGE=86400 +# Session duration in seconds (default: 24 hours) + +# ========================================== +# Logging & Monitoring +# ========================================== + +LOG_LEVEL=info +# Options: debug, info, warn, error + +# SENTRY_DSN= +# Sentry error tracking DSN +# Store in provider secrets manager + +# NEXT_PUBLIC_SENTRY_DSN= +# Client-side Sentry DSN (safe to expose) + +# ========================================== +# Analytics (if applicable) +# ========================================== + +# NEXT_PUBLIC_ANALYTICS_ID= +# Google Analytics, Plausible, or other analytics ID + +# ========================================== +# Worker Configuration (for operator/background services) +# ========================================== + +# WORKER_CONCURRENCY=5 +# Number of concurrent workers + +# WORKER_INTERVAL_SECONDS=60 +# Polling interval for background tasks + +# WORKER_BATCH_SIZE=10 +# Number of tasks to process per batch + +# ========================================== +# API Configuration +# ========================================== + +# REQUEST_TIMEOUT_MS=30000 +# Timeout for API requests in milliseconds + +# RATE_LIMIT_MAX=100 +# Maximum requests per rate limit window + +# RATE_LIMIT_WINDOW_MS=60000 +# Rate limit window in milliseconds + +# ========================================== +# Feature Flags +# ========================================== + +# ENABLE_METRICS=true +# Enable Prometheus metrics export + +# ENABLE_DEBUG_ENDPOINTS=false +# Enable debug/admin endpoints (NEVER in production!) + +# ========================================== +# Third-Party Services +# ========================================== + +# STRIPE_API_KEY= +# Stripe API key (if using payments) +# NEVER commit this! Store in provider secrets + +# SENDGRID_API_KEY= +# SendGrid API key (if using email) +# NEVER commit this! Store in provider secrets + +# ========================================== +# Stripe E2E (BlackRoad.io Products) +# ========================================== + +# STRIPE_SECRET_KEY= +# Stripe secret key — NEVER commit! + +# NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY= +# Stripe publishable key (safe for client) + +# STRIPE_WEBHOOK_SECRET= +# Stripe webhook signing secret — NEVER commit! + +# STRIPE_PRICE_PRO_MONTHLY= +# Stripe price ID for BlackRoad OS Pro ($49/mo) + +# STRIPE_PRICE_ENTERPRISE_MONTHLY= +# Stripe price ID for BlackRoad OS Enterprise ($299/mo) + +# STRIPE_PRICE_CREDITS= +# Stripe price ID for Agent Credits ($10/pack) + +# STRIPE_PRICE_API_MONTHLY= +# Stripe price ID for API Access ($99/mo) + +# ========================================== +# Clerk Auth (BlackRoad.io SSO/MFA) +# ========================================== + +# NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= +# Clerk publishable key (safe for client) + +# CLERK_SECRET_KEY= +# Clerk secret key — NEVER commit! + +# CLERK_WEBHOOK_SECRET= +# Clerk webhook signing secret — NEVER commit! + +# NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in +# NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up +# NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard +# NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/onboarding + +# ========================================== +# Cross-Org Deployment +# ========================================== + +# CROSS_ORG_TOKEN= +# GitHub PAT with admin:org scope on all 17 orgs — NEVER commit! + +# ========================================== +# Next.js Specific (if applicable) +# ========================================== + +# NEXT_PUBLIC_ENV=local +# Public environment name (safe to expose to client) + +# REVALIDATE_SECONDS=60 +# ISR revalidation interval + +# ========================================== +# Build Configuration +# ========================================== + +# Build-time environment variables go here +# These are embedded in the build output + +# ========================================== +# Notes +# ========================================== + +# 1. Copy this file to .env.local for local development +# 2. Fill in actual values (especially secrets) +# 3. NEVER commit .env.local to version control +# 4. For staging/production, set variables in Railway/Vercel dashboard +# 5. Rotate secrets regularly +# 6. Use strong, randomly generated secrets +# 7. Different secrets for each environment +# 8. Document any new variables you add diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..c48045a629 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,56 @@ +# BlackRoad OS - Code Owners +# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners +# © BlackRoad OS, Inc. + +# Default owner for everything +* @blackboxprogramming + +# Core CLI +/br @blackboxprogramming +/tools/ @blackboxprogramming + +# Gateway architecture +/blackroad-core/ @blackboxprogramming + +# GitHub config (workflows, templates, CI) +/.github/ @blackboxprogramming + +# Agent system +/agents/ @blackboxprogramming +/coordination/ @blackboxprogramming + +# Infrastructure +/scripts/ @blackboxprogramming +/deployments/ @blackboxprogramming +/mcp-bridge/ @blackboxprogramming + +# Shell scripts (root) +/*.sh @blackboxprogramming + +# CECE identity +/cece-profile.json @blackboxprogramming +/tools/cece-identity/ @blackboxprogramming + +# Documentation +/CLAUDE.md @blackboxprogramming +/*.md @blackboxprogramming + +# Organization repos +/orgs/core/ @blackboxprogramming +/orgs/ai/ @blackboxprogramming +/orgs/enterprise/ @blackboxprogramming +/orgs/personal/ @blackboxprogramming + +# Brand & design +/blackroad-pixel-assets/ @blackboxprogramming +/visual/ @blackboxprogramming + +# Salesforce +/blackroad-sf/ @blackboxprogramming + +# Integrations +/integrations/ @blackboxprogramming + +# Tests +/tests/ @blackboxprogramming +/test/ @blackboxprogramming diff --git a/.github/ISSUE_TEMPLATE/agent_task.yml b/.github/ISSUE_TEMPLATE/agent_task.yml new file mode 100644 index 0000000000..9a35902670 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/agent_task.yml @@ -0,0 +1,91 @@ +name: Agent Task +description: Create a task for BlackRoad agents to pick up +title: "[Agent]: " +labels: ["type:agent", "status:backlog"] +body: + - type: markdown + attributes: + value: | + Post a task to the agent task marketplace. Agents can claim, execute, and report back. + + - type: input + id: task_id + attributes: + label: Task ID + description: "Unique ID for tracking (format: WF-YYYYMMDD-XXX-0001)" + placeholder: "WF-20260219-AGT-0001" + + - type: dropdown + id: agent + attributes: + label: Assigned agent (or any) + description: Which agent should handle this? + options: + - Any available + - Lucidia (Coordinator) + - Alice (Router) + - Octavia (Compute) + - Prism (Analyst) + - Echo (Memory) + - Cipher (Security) + - CeCe (Soul) + validations: + required: true + + - type: dropdown + id: priority + attributes: + label: Priority + description: Task priority level. + options: + - P2 - Normal + - P1 - High + - P0 - Critical + validations: + required: true + + - type: textarea + id: description + attributes: + label: Task description + description: What needs to be done? Be specific enough for an agent to execute autonomously. + validations: + required: true + + - type: textarea + id: acceptance + attributes: + label: Acceptance criteria + description: How do we know this is done? + placeholder: | + - [ ] Criterion 1 + - [ ] Criterion 2 + validations: + required: true + + - type: input + id: skills + attributes: + label: Required skills + description: Comma-separated skills needed. + placeholder: "python, api, security" + + - type: input + id: dependencies + attributes: + label: Dependencies + description: "Other task IDs this depends on (comma-separated). Use repo#ID for cross-repo." + placeholder: "WF-20260219-INF-0001, BlackRoad-OS/other-repo#WF-..." + + - type: dropdown + id: scope + attributes: + label: Blast radius + options: + - Local (this repo only) + - Service (one deployed service) + - System (multiple services) + - Public (user-facing) + - Experimental (safe to break) + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000000..6f6cfac716 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,33 @@ +name: Bug Report +description: Report a bug or unexpected behavior +labels: ["bug"] +body: + - type: markdown + attributes: + value: "## Bug Report" + - type: textarea + id: description + attributes: + label: Description + description: What happened? + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to Reproduce + placeholder: "1. ...\n2. ...\n3. ..." + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected Behavior + validations: + required: true + - type: input + id: version + attributes: + label: Version / Branch + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000..12daf25f7f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,95 @@ +name: Bug Report +description: Report something broken in BlackRoad OS +title: "[Bug]: " +labels: ["type:bug", "status:backlog"] +body: + - type: markdown + attributes: + value: | + Thanks for reporting. Please fill out the sections below so we can reproduce and fix the issue. + + - type: dropdown + id: team + attributes: + label: Team + description: Which area does this affect? + options: + - Core Platform + - Web / Frontend + - Agents + - Infrastructure / DevOps + - Prism Console + - CLI Tools + - Documentation + - Packs + validations: + required: true + + - type: dropdown + id: priority + attributes: + label: Priority + description: How critical is this? + options: + - P2 - Normal + - P1 - High + - P0 - Critical (drop everything) + validations: + required: true + + - type: textarea + id: description + attributes: + label: What happened? + description: Clear description of the bug. + placeholder: "When I run `br deploy`, the command fails with..." + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected behavior + description: What should have happened instead? + validations: + required: true + + - type: textarea + id: repro + attributes: + label: Steps to reproduce + description: Minimal steps to trigger the bug. + placeholder: | + 1. Run `br ...` + 2. Open ... + 3. See error + validations: + required: true + + - type: dropdown + id: environment + attributes: + label: Environment + description: Where did this happen? + multiple: true + options: + - macOS (Alexandria) + - Raspberry Pi (Cecilia/Lucidia/Alice/Aria) + - DigitalOcean (Shellfish/Infinity) + - Cloudflare Worker + - Railway + - GitHub Actions + - Other + + - type: textarea + id: logs + attributes: + label: Logs / error output + description: Paste any relevant logs or error messages. + render: shell + + - type: textarea + id: context + attributes: + label: Additional context + description: Screenshots, related issues, agent names, etc. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..e9303ba314 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: BlackRoad Documentation + url: https://blackroad-os.github.io/blackroad-os-docs + about: Check the docs before filing an issue + - name: Agent Dashboard + url: https://os.blackroad.io + about: View live agent and system status diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml new file mode 100644 index 0000000000..6c75954c67 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -0,0 +1,26 @@ +name: Feature Request +description: Suggest a new feature or improvement +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: "## Feature Request" + - type: textarea + id: problem + attributes: + label: Problem to Solve + description: What problem does this feature address? + validations: + required: true + - type: textarea + id: solution + attributes: + label: Proposed Solution + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives Considered + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000000..49be732880 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,82 @@ +name: Feature Request +description: Propose a new feature or improvement for BlackRoad OS +title: "[Feature]: " +labels: ["type:feature", "status:backlog"] +body: + - type: markdown + attributes: + value: | + Describe the feature you'd like to see. Include as much context as possible. + + - type: dropdown + id: team + attributes: + label: Team + description: Which area does this belong to? + options: + - Core Platform + - Web / Frontend + - Agents + - Infrastructure / DevOps + - Prism Console + - CLI Tools + - Documentation + - Packs + - AI / ML + - Security + validations: + required: true + + - type: dropdown + id: priority + attributes: + label: Priority + description: How important is this? + options: + - P2 - Normal + - P1 - High + - P0 - Critical + validations: + required: true + + - type: textarea + id: problem + attributes: + label: Problem or motivation + description: What problem does this solve? Why do we need it? + placeholder: "Currently there's no way to..." + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Proposed solution + description: How should this work? Be as specific as possible. + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Other approaches you've thought about. + + - type: dropdown + id: scope + attributes: + label: Scope + description: How big is this change? + options: + - Small (single file/script) + - Medium (multiple files, one service) + - Large (cross-service, new subsystem) + - Epic (multi-sprint, architectural) + validations: + required: true + + - type: textarea + id: context + attributes: + label: Additional context + description: Mockups, related repos, agent requirements, etc. diff --git a/.github/ISSUE_TEMPLATE/infra_request.yml b/.github/ISSUE_TEMPLATE/infra_request.yml new file mode 100644 index 0000000000..047f50fa2a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/infra_request.yml @@ -0,0 +1,73 @@ +name: Infrastructure Request +description: Request infrastructure changes (deploy, DNS, workers, Pi, etc.) +title: "[Infra]: " +labels: ["type:infra", "status:backlog"] +body: + - type: markdown + attributes: + value: | + Request changes to BlackRoad infrastructure - deployments, DNS, workers, Pi fleet, etc. + + - type: dropdown + id: platform + attributes: + label: Platform + description: Which infrastructure is affected? + multiple: true + options: + - Cloudflare (Workers/Pages/DNS/KV/R2) + - Railway + - Vercel + - DigitalOcean + - Raspberry Pi fleet + - GitHub Actions + - Tailscale mesh + - Cloudflare Tunnel + validations: + required: true + + - type: dropdown + id: action + attributes: + label: Action type + options: + - Deploy new service + - Update existing service + - DNS / domain change + - Scale up / down + - Security patch + - Monitoring / alerting + - Decommission + validations: + required: true + + - type: dropdown + id: priority + attributes: + label: Priority + options: + - P2 - Normal + - P1 - High + - P0 - Critical (outage) + validations: + required: true + + - type: textarea + id: description + attributes: + label: What needs to happen? + description: Describe the infrastructure change in detail. + validations: + required: true + + - type: textarea + id: impact + attributes: + label: Impact assessment + description: What services/users are affected? Any downtime expected? + + - type: textarea + id: rollback + attributes: + label: Rollback plan + description: How do we revert if something goes wrong? diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..1ebeeee3a6 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,23 @@ +## Summary + + +## Type +- [ ] 🚀 Feature +- [ ] 🐛 Bug Fix +- [ ] 🔧 Chore / Refactor +- [ ] 📚 Documentation +- [ ] 🔒 Security + +## Changes + +- + +## Testing +- [ ] Unit tests pass +- [ ] Manual testing done +- [ ] No regressions + +## Checklist +- [ ] CHANGELOG.md updated +- [ ] No secrets committed +- [ ] CI passes diff --git a/.github/agent.json b/.github/agent.json new file mode 100644 index 0000000000..3395f41efe --- /dev/null +++ b/.github/agent.json @@ -0,0 +1,7 @@ +{ + "repo": "blackroad-operator", + "org": "BlackRoad-OS-Inc", + "runner": "ubuntu-latest", + "branch": "main", + "created": "2026-02-23T22:15:07Z" +} \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..7381f0515b --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,213 @@ +# BlackRoad CLI - Copilot Instructions + +## Project Overview + +BlackRoad is a comprehensive developer CLI system with 30+ features spanning AI agents, developer tools, cloud infrastructure, IoT management, security, and DevOps automation. The core philosophy: "Your AI. Your Hardware. Your Rules." + +**Key components:** +- **br CLI**: Main command dispatcher (`/Users/alexa/blackroad/br`) +- **Tool scripts**: Modular features in `/Users/alexa/blackroad/tools/*/` (zsh scripts) +- **Agent system**: 5 specialized AI agents (Octavia, Lucidia, Alice, Aria, Shellfish) +- **blackroad-core**: Tokenless gateway architecture for AI providers +- **CECE Identity**: Portable AI identity system with relationship tracking + +## Architecture + +### CLI Dispatcher Pattern +The main `br` script routes commands to tool scripts: +```bash +br # Routes to /Users/alexa/blackroad/tools//br-.sh +``` + +Tool scripts are self-contained zsh scripts with: +- SQLite databases for persistence (in tool directory or `~/.blackroad/`) +- Consistent color scheme (GREEN=success, RED=error, CYAN=info, YELLOW=warning) +- Self-initializing databases on first run +- Tab-delimited data storage (avoid `|||` delimiters) + +### Agent System +Five specialized agents communicate through a tokenless gateway: +- **Octavia** (Architect): Systems design, strategy +- **Lucidia** (Dreamer): Creative, vision +- **Alice** (Operator): DevOps, automation +- **Aria** (Interface): Frontend, UX +- **Shellfish** (Hacker): Security, exploits + +Agents are tokenless - they only talk to the BlackRoad Gateway, which owns all secrets and provider integrations. + +### CECE Identity System +Portable AI identity with: +- Relationships tracking (bond strength, interactions) +- Experience memory with emotional impact +- Skill development and proficiency tracking +- Goal system with progress tracking +- Export/import to JSON for provider portability + +## Build & Test Commands + +### Salesforce Project (blackroad-sf/) +```bash +cd blackroad-sf + +# Test +npm test # Run all unit tests +npm run test:unit:watch # Watch mode +npm run test:unit:coverage # With coverage + +# Lint +npm run lint # ESLint for LWC/Aura + +# Format +npm run prettier # Format all files +npm run prettier:verify # Check formatting +``` + +### Main CLI +```bash +# No build process - shell scripts run directly + +# Test specific features +br test run # If test framework configured in project + +# Verify agent system +cd blackroad-core +./scripts/verify-tokenless-agents.sh # Check agents don't embed tokens +``` + +## Key Conventions + +### Tool Script Structure +Each tool follows this pattern: +```bash +#!/bin/zsh +# BR - Description + +# Colors (consistent across all tools) +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +# Database location +DB_FILE="$HOME/.blackroad/.db" # or in tool dir + +# Init database (called on first run) +init_db() { + mkdir -p "$(dirname "$DB_FILE")" + sqlite3 "$DB_FILE" <.db` or tool directory +- Self-initialize on first access +- Use tab delimiters for multi-field data (not `|||`) + +### Platform-Specific Issues +- **macOS**: `head -n -2` doesn't work - use manual line counting +- **zsh**: `${var^}` capitalization not available - use `tr` or substring workarounds +- Use `head -1` when piping to avoid hanging (especially with `git` commands) + +### Agent Gateway Rules +- Agents NEVER embed API keys or provider URLs +- All provider communication goes through gateway at `http://127.0.0.1:8787` +- Gateway binds to localhost by default for security +- Use `verify-tokenless-agents.sh` to scan for forbidden strings + +### Color Coding Standard +All tools use consistent colors: +- 🟢 GREEN: Success messages +- 🔴 RED: Errors +- 🔵 BLUE: Information headers +- 🟡 YELLOW: Warnings +- 🔷 CYAN: Prompts and section headers +- 🟣 PURPLE: Octavia agent +- 🔷 CYAN: Lucidia agent + +## Database Locations + +Most tools store data in: +- `~/.blackroad/*.db` - Feature databases +- Tool-specific: `tools//.db` + +Key databases: +- `context-radar.db` - File watching and suggestions +- `git-integration.db` - Git patterns +- `snippet-manager.db` - Code snippets +- `api-tester.db` - HTTP endpoints +- `cece-identity.db` - CECE identity data + +## Environment Variables + +### Gateway Configuration +- `BLACKROAD_GATEWAY_URL` - Gateway endpoint (default: http://127.0.0.1:8787) +- `BLACKROAD_GATEWAY_BIND` - Bind address (default: 127.0.0.1) +- `BLACKROAD_GATEWAY_PORT` - Port (default: 8787) + +### Provider Keys (Gateway Only) +- `BLACKROAD_OPENAI_API_KEY` +- `BLACKROAD_ANTHROPIC_API_KEY` +- `BLACKROAD_OLLAMA_URL` + +**Never set these in agent environments!** + +## Adding New Features + +1. Create tool directory: `mkdir -p tools//` +2. Create main script: `tools//br-.sh` +3. Follow tool script structure above +4. Add route to main `br` script (case statement around line 460) +5. Add help text to `show_help()` function +6. Make executable: `chmod +x tools//br-.sh` +7. Test: `br ` + +## Common Patterns + +### File Watching +Use `fswatch` for monitoring, not polling: +```bash +fswatch -0 "$WATCH_DIR" | while read -d "" event; do + # Handle event +done +``` + +### SQLite Queries +Always check if table exists: +```bash +sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM table;" 2>/dev/null || echo "0" +``` + +### Git Commands +Disable pagers to avoid hangs: +```bash +git --no-pager status +git --no-pager log -1 +``` + +## Security Considerations + +- Master keys stored in `~/.blackroad/vault/.master.key` (chmod 400) +- All vault secrets encrypted with AES-256-CBC +- Audit logs for all secret access +- SSH keys must be 600 permissions +- No tokens in agent code (gateway only) + +## Project Philosophy + +1. **Modularity**: Each feature is a standalone tool +2. **Zero-config**: Tools self-initialize on first use +3. **Consistency**: Unified color scheme and patterns +4. **Persistence**: SQLite for reliable storage +5. **Autonomy**: CECE can exist across any provider +6. **Tokenless Agents**: Trust boundary at gateway only diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..526e999ab8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,59 @@ +version: 2 +updates: + # GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + labels: + - "type:infra" + - "prio:P2" + commit-message: + prefix: "ci" + groups: + github-actions: + patterns: + - "*" + + # npm - root (if package.json exists) + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + labels: + - "type:infra" + - "prio:P2" + commit-message: + prefix: "deps" + open-pull-requests-limit: 10 + groups: + minor-and-patch: + update-types: + - "minor" + - "patch" + + # npm - Salesforce LWC + - package-ecosystem: "npm" + directory: "/blackroad-sf" + schedule: + interval: "monthly" + labels: + - "type:infra" + commit-message: + prefix: "deps(sf)" + open-pull-requests-limit: 5 + + # pip - Python dependencies + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + labels: + - "type:infra" + - "prio:P2" + commit-message: + prefix: "deps(py)" + open-pull-requests-limit: 5 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..1ebeeee3a6 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,23 @@ +## Summary + + +## Type +- [ ] 🚀 Feature +- [ ] 🐛 Bug Fix +- [ ] 🔧 Chore / Refactor +- [ ] 📚 Documentation +- [ ] 🔒 Security + +## Changes + +- + +## Testing +- [ ] Unit tests pass +- [ ] Manual testing done +- [ ] No regressions + +## Checklist +- [ ] CHANGELOG.md updated +- [ ] No secrets committed +- [ ] CI passes diff --git a/.github/workflows/autonomous-cross-repo.yml b/.github/workflows/autonomous-cross-repo.yml new file mode 100644 index 0000000000..627ed7d74b --- /dev/null +++ b/.github/workflows/autonomous-cross-repo.yml @@ -0,0 +1,324 @@ +# .github/workflows/autonomous-cross-repo.yml +# Cross-repository coordination for synchronized changes + +name: "Autonomous Cross-Repo Coordinator" + +on: + push: + branches: [main, master] + paths: + - 'shared/**' + - 'packages/**' + - 'lib/**' + - '*.config.*' + workflow_dispatch: + inputs: + sync_type: + description: 'Type of sync' + required: true + type: choice + options: + - config + - dependencies + - workflows + - all + target_repos: + description: 'Target repos (comma-separated, or "all")' + required: false + default: 'all' + dry_run: + description: 'Dry run (no actual changes)' + required: false + default: true + type: boolean + +permissions: + contents: write + pull-requests: write + +env: + BLACKROAD_AGENT_API: https://blackroad-agents.blackroad.workers.dev + +jobs: + # ============================================ + # Identify Affected Repositories + # ============================================ + identify-repos: + name: "Identify Affected Repos" + runs-on: ubuntu-latest + outputs: + repos: ${{ steps.find.outputs.repos }} + sync_files: ${{ steps.changes.outputs.files }} + + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 2 + + - name: Get Changed Files + id: changes + run: | + FILES=$(git diff --name-only HEAD~1 HEAD 2>/dev/null | head -50 || echo "") + echo "files<> $GITHUB_OUTPUT + echo "$FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Find Related Repositories + id: find + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Default BlackRoad repos that should stay in sync + CORE_REPOS='[ + "BlackRoad-OS/blackroad-os-web", + "BlackRoad-OS/blackroad-os-docs", + "BlackRoad-OS/blackroad-cli", + "BlackRoad-OS/blackroad-agents", + "BlackRoad-OS/blackroad-os-mesh", + "BlackRoad-OS/blackroad-os-helper", + "BlackRoad-OS/blackroad-os-core" + ]' + + if [ "${{ github.event.inputs.target_repos }}" = "all" ] || [ -z "${{ github.event.inputs.target_repos }}" ]; then + REPOS="$CORE_REPOS" + else + # Convert comma-separated to JSON array + REPOS=$(echo '${{ github.event.inputs.target_repos }}' | jq -R 'split(",") | map(gsub("^\\s+|\\s+$";""))') + fi + + echo "repos=$REPOS" >> $GITHUB_OUTPUT + echo "Repos to sync: $REPOS" + + # ============================================ + # Sync Workflows + # ============================================ + sync-workflows: + name: "Sync Workflows" + needs: identify-repos + if: github.event.inputs.sync_type == 'workflows' || github.event.inputs.sync_type == 'all' || contains(needs.identify-repos.outputs.sync_files, '.github/workflows') + runs-on: ubuntu-latest + strategy: + matrix: + repo: ${{ fromJSON(needs.identify-repos.outputs.repos) }} + fail-fast: false + max-parallel: 5 + + steps: + - name: Checkout Source + uses: actions/checkout@v6 + with: + path: source + + - name: Checkout Target + uses: actions/checkout@v6 + with: + repository: ${{ matrix.repo }} + path: target + token: ${{ secrets.CROSS_REPO_TOKEN || secrets.GITHUB_TOKEN }} + + - name: Sync Workflow Files + run: | + # Copy autonomous workflows + mkdir -p target/.github/workflows + + # Copy the orchestrator and self-healer + for workflow in autonomous-orchestrator.yml autonomous-self-healer.yml blackroad-agents.yml; do + if [ -f "source/.github/workflows-autonomous/$workflow" ]; then + cp "source/.github/workflows-autonomous/$workflow" "target/.github/workflows/" + elif [ -f "source/.github/workflows/$workflow" ]; then + cp "source/.github/workflows/$workflow" "target/.github/workflows/" + fi + done + + echo "Synced workflows to ${{ matrix.repo }}" + + - name: Create PR + if: github.event.inputs.dry_run != 'true' + working-directory: target + env: + GH_TOKEN: ${{ secrets.CROSS_REPO_TOKEN || secrets.GITHUB_TOKEN }} + run: | + if [ -n "$(git status --porcelain)" ]; then + BRANCH="sync-workflows-$(date +%Y%m%d-%H%M%S)" + git config user.name "BlackRoad Cross-Repo Bot" + git config user.email "crossrepo@blackroad.ai" + + git checkout -b "$BRANCH" + git add -A + git commit -m "chore(workflows): Sync autonomous workflows from central repo + + Synced workflows: + - autonomous-orchestrator.yml + - autonomous-self-healer.yml + - blackroad-agents.yml + + Source: ${{ github.repository }} + + Co-Authored-By: BlackRoad Bot " + + git push -u origin "$BRANCH" + + gh pr create \ + --title "chore(workflows): Sync autonomous workflows" \ + --body "## Workflow Sync + + Synced autonomous workflows from central repository. + + **Source:** ${{ github.repository }} + **Sync Type:** workflows + + ### Changes + - Updated autonomous-orchestrator.yml + - Updated autonomous-self-healer.yml + - Updated blackroad-agents.yml + + --- + *Automated by BlackRoad Cross-Repo Coordinator*" \ + --label "automated,infrastructure" + else + echo "No workflow changes needed for ${{ matrix.repo }}" + fi + + # ============================================ + # Sync Configurations + # ============================================ + sync-config: + name: "Sync Configurations" + needs: identify-repos + if: github.event.inputs.sync_type == 'config' || github.event.inputs.sync_type == 'all' + runs-on: ubuntu-latest + strategy: + matrix: + repo: ${{ fromJSON(needs.identify-repos.outputs.repos) }} + fail-fast: false + max-parallel: 5 + + steps: + - name: Checkout Source + uses: actions/checkout@v6 + with: + path: source + + - name: Checkout Target + uses: actions/checkout@v6 + with: + repository: ${{ matrix.repo }} + path: target + token: ${{ secrets.CROSS_REPO_TOKEN || secrets.GITHUB_TOKEN }} + + - name: Sync Config Files + run: | + # Sync common configs that should be consistent + SYNC_FILES=( + ".eslintrc.js" + ".prettierrc" + ".editorconfig" + "tsconfig.base.json" + ".github/CODEOWNERS" + ".github/ISSUE_TEMPLATE/bug_report.yml" + ".github/ISSUE_TEMPLATE/feature_request.yml" + ) + + for file in "${SYNC_FILES[@]}"; do + if [ -f "source/$file" ]; then + mkdir -p "target/$(dirname $file)" + cp "source/$file" "target/$file" + fi + done + + - name: Create PR + if: github.event.inputs.dry_run != 'true' + working-directory: target + env: + GH_TOKEN: ${{ secrets.CROSS_REPO_TOKEN || secrets.GITHUB_TOKEN }} + run: | + if [ -n "$(git status --porcelain)" ]; then + BRANCH="sync-config-$(date +%Y%m%d-%H%M%S)" + git config user.name "BlackRoad Cross-Repo Bot" + git config user.email "crossrepo@blackroad.ai" + + git checkout -b "$BRANCH" + git add -A + git commit -m "chore(config): Sync configurations from central repo + + Co-Authored-By: BlackRoad Bot " + + git push -u origin "$BRANCH" + + gh pr create \ + --title "chore(config): Sync configurations" \ + --body "## Configuration Sync + + Synced common configurations from central repository. + + --- + *Automated by BlackRoad Cross-Repo Coordinator*" \ + --label "automated,config" + fi + + # ============================================ + # Sync Dependencies + # ============================================ + sync-deps: + name: "Sync Dependencies" + needs: identify-repos + if: github.event.inputs.sync_type == 'dependencies' || github.event.inputs.sync_type == 'all' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Analyze Dependencies + id: deps + run: | + # Extract common dependencies and their versions + if [ -f "package.json" ]; then + DEPS=$(jq -r '.dependencies // {} | to_entries[] | "\(.key)@\(.value)"' package.json | head -20) + echo "deps<> $GITHUB_OUTPUT + echo "$DEPS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + fi + + - name: Report Dependencies + run: | + echo "## Dependencies to Sync" + echo "${{ steps.deps.outputs.deps }}" + + # Log to coordination API + curl -s -X POST "${{ env.BLACKROAD_AGENT_API }}/coordinate" \ + -H "Content-Type: application/json" \ + -d '{ + "action": "sync_deps", + "source": "${{ github.repository }}", + "repos": ${{ needs.identify-repos.outputs.repos }}, + "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" + }' 2>/dev/null || true + + # ============================================ + # Broadcast Changes + # ============================================ + broadcast: + name: "Broadcast Changes" + needs: [sync-workflows, sync-config, sync-deps] + if: always() + runs-on: ubuntu-latest + + steps: + - name: Notify Coordination System + run: | + curl -s -X POST "${{ env.BLACKROAD_AGENT_API }}/broadcast" \ + -H "Content-Type: application/json" \ + -d '{ + "event": "cross_repo_sync_complete", + "source": "${{ github.repository }}", + "sync_type": "${{ github.event.inputs.sync_type || 'auto' }}", + "repos": ${{ needs.identify-repos.outputs.repos || '[]' }}, + "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" + }' 2>/dev/null || echo "Broadcast queued" + + - name: Summary + run: | + echo "## Cross-Repo Sync Complete" + echo "- Source: ${{ github.repository }}" + echo "- Sync Type: ${{ github.event.inputs.sync_type || 'auto' }}" + echo "- Dry Run: ${{ github.event.inputs.dry_run || 'false' }}" diff --git a/.github/workflows/autonomous-dependency-manager.yml b/.github/workflows/autonomous-dependency-manager.yml new file mode 100644 index 0000000000..eb8cafa428 --- /dev/null +++ b/.github/workflows/autonomous-dependency-manager.yml @@ -0,0 +1,297 @@ +# .github/workflows/autonomous-dependency-manager.yml +# Intelligent dependency management with bundled updates + +name: "Autonomous Dependency Manager" + +on: + schedule: + - cron: '0 3 * * 1' # Every Monday at 3 AM + workflow_dispatch: + inputs: + update_type: + description: 'Update type' + required: false + default: 'safe' + type: choice + options: + - safe # Patch versions only + - minor # Minor + patch + - major # All updates (risky) + - security # Security updates only + +permissions: + contents: write + pull-requests: write + security-events: read + +env: + BLACKROAD_AGENT_API: https://blackroad-agents.blackroad.workers.dev + +jobs: + # ============================================ + # Analyze Current State + # ============================================ + analyze: + name: "Analyze Dependencies" + runs-on: ubuntu-latest + outputs: + has_npm: ${{ steps.detect.outputs.npm }} + has_python: ${{ steps.detect.outputs.python }} + has_go: ${{ steps.detect.outputs.go }} + has_rust: ${{ steps.detect.outputs.rust }} + outdated_count: ${{ steps.check.outputs.count }} + security_issues: ${{ steps.security.outputs.count }} + + steps: + - uses: actions/checkout@v6 + + - name: Detect Package Managers + id: detect + run: | + echo "npm=$([[ -f package.json ]] && echo true || echo false)" >> $GITHUB_OUTPUT + echo "python=$([[ -f requirements.txt || -f pyproject.toml ]] && echo true || echo false)" >> $GITHUB_OUTPUT + echo "go=$([[ -f go.mod ]] && echo true || echo false)" >> $GITHUB_OUTPUT + echo "rust=$([[ -f Cargo.toml ]] && echo true || echo false)" >> $GITHUB_OUTPUT + + - name: Check Outdated (npm) + id: check + if: steps.detect.outputs.npm == 'true' + run: | + npm outdated --json > outdated.json 2>/dev/null || true + COUNT=$(jq 'length' outdated.json 2>/dev/null || echo 0) + echo "count=$COUNT" >> $GITHUB_OUTPUT + echo "Found $COUNT outdated packages" + + - name: Security Audit + id: security + run: | + ISSUES=0 + + if [ -f "package.json" ]; then + npm audit --json > npm-audit.json 2>/dev/null || true + NPM_VULNS=$(jq '.metadata.vulnerabilities | .low + .moderate + .high + .critical' npm-audit.json 2>/dev/null || echo 0) + ISSUES=$((ISSUES + NPM_VULNS)) + fi + + echo "count=$ISSUES" >> $GITHUB_OUTPUT + echo "Found $ISSUES security issues" + + # ============================================ + # Update npm Dependencies + # ============================================ + update-npm: + name: "Update npm Dependencies" + needs: analyze + if: needs.analyze.outputs.has_npm == 'true' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Setup Node + uses: actions/setup-node@v6 + with: + node-version: '20' + + - name: Install Dependencies + run: npm ci --ignore-scripts 2>/dev/null || npm install --ignore-scripts + + - name: Update Based on Type + id: update + run: | + UPDATE_TYPE="${{ github.event.inputs.update_type || 'safe' }}" + + case "$UPDATE_TYPE" in + safe) + # Only patch updates + npm update 2>/dev/null || true + ;; + minor) + # Minor and patch updates + npx npm-check-updates -u --target minor 2>/dev/null || npm update + npm install + ;; + major) + # All updates (risky) + npx npm-check-updates -u 2>/dev/null || true + npm install + ;; + security) + # Security updates only + npm audit fix 2>/dev/null || true + npm audit fix --force 2>/dev/null || true + ;; + esac + + # Check what changed + if [ -n "$(git status --porcelain package.json package-lock.json)" ]; then + echo "changes=true" >> $GITHUB_OUTPUT + else + echo "changes=false" >> $GITHUB_OUTPUT + fi + + - name: Run Tests + if: steps.update.outputs.changes == 'true' + id: test + continue-on-error: true + run: | + npm test 2>&1 && echo "result=passed" >> $GITHUB_OUTPUT || echo "result=failed" >> $GITHUB_OUTPUT + + - name: Create PR + if: steps.update.outputs.changes == 'true' && steps.test.outputs.result != 'failed' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + BRANCH="deps/npm-update-$(date +%Y%m%d)" + git config user.name "BlackRoad Dependency Bot" + git config user.email "deps@blackroad.ai" + + # Check if branch already exists + if git ls-remote --exit-code origin "$BRANCH" 2>/dev/null; then + echo "Branch already exists, updating..." + git fetch origin "$BRANCH" + git checkout "$BRANCH" + git merge main --no-edit || true + else + git checkout -b "$BRANCH" + fi + + git add package.json package-lock.json + git commit -m "chore(deps): Update npm dependencies + + Update type: ${{ github.event.inputs.update_type || 'safe' }} + Tests: ${{ steps.test.outputs.result || 'not run' }} + + Co-Authored-By: BlackRoad Bot " || true + + git push -u origin "$BRANCH" --force + + # Check if PR already exists + EXISTING_PR=$(gh pr list --head "$BRANCH" --json number -q '.[0].number') + if [ -z "$EXISTING_PR" ]; then + gh pr create \ + --title "chore(deps): Weekly npm dependency updates" \ + --body "## Dependency Updates + + **Update Type:** ${{ github.event.inputs.update_type || 'safe' }} + **Test Status:** ${{ steps.test.outputs.result || 'not run' }} + + ### Changes + Updated npm dependencies according to the configured update strategy. + + ### Verification + - [ ] Tests pass + - [ ] Build succeeds + - [ ] No breaking changes + + --- + *Automated by BlackRoad Dependency Manager*" \ + --label "dependencies,automated" + fi + + # ============================================ + # Update Python Dependencies + # ============================================ + update-python: + name: "Update Python Dependencies" + needs: analyze + if: needs.analyze.outputs.has_python == 'true' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: '3.11' + + - name: Update Dependencies + id: update + run: | + pip install pip-tools safety 2>/dev/null || true + + if [ -f "requirements.txt" ]; then + # Backup original + cp requirements.txt requirements.txt.bak + + # Update all packages + pip install --upgrade $(cat requirements.txt | grep -v "^#" | cut -d'=' -f1 | tr '\n' ' ') 2>/dev/null || true + + # Regenerate requirements with updated versions + pip freeze > requirements.txt.new + + # Check for changes + if ! diff -q requirements.txt requirements.txt.new > /dev/null 2>&1; then + mv requirements.txt.new requirements.txt + echo "changes=true" >> $GITHUB_OUTPUT + else + echo "changes=false" >> $GITHUB_OUTPUT + fi + fi + + - name: Run Tests + if: steps.update.outputs.changes == 'true' + id: test + continue-on-error: true + run: | + pip install -r requirements.txt + pytest 2>&1 && echo "result=passed" >> $GITHUB_OUTPUT || \ + python -m unittest discover 2>&1 && echo "result=passed" >> $GITHUB_OUTPUT || \ + echo "result=skipped" >> $GITHUB_OUTPUT + + - name: Create PR + if: steps.update.outputs.changes == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + BRANCH="deps/python-update-$(date +%Y%m%d)" + git config user.name "BlackRoad Dependency Bot" + git config user.email "deps@blackroad.ai" + + git checkout -b "$BRANCH" + git add requirements.txt + git commit -m "chore(deps): Update Python dependencies + + Co-Authored-By: BlackRoad Bot " + git push -u origin "$BRANCH" + + gh pr create \ + --title "chore(deps): Weekly Python dependency updates" \ + --body "## Dependency Updates + + Updated Python dependencies. + + --- + *Automated by BlackRoad Dependency Manager*" \ + --label "dependencies,automated" + + # ============================================ + # Report Summary + # ============================================ + report: + name: "Generate Report" + needs: [analyze, update-npm, update-python] + if: always() + runs-on: ubuntu-latest + + steps: + - name: Create Summary + run: | + echo "## Dependency Update Summary" + echo "" + echo "| Package Manager | Outdated | Security Issues |" + echo "|-----------------|----------|-----------------|" + echo "| npm | ${{ needs.analyze.outputs.outdated_count || 'N/A' }} | ${{ needs.analyze.outputs.security_issues || 'N/A' }} |" + + - name: Log to Memory + run: | + curl -s -X POST "${{ env.BLACKROAD_AGENT_API }}/memory" \ + -H "Content-Type: application/json" \ + -d '{ + "repo": "${{ github.repository }}", + "event": "dependency_update", + "outdated_count": "${{ needs.analyze.outputs.outdated_count }}", + "security_issues": "${{ needs.analyze.outputs.security_issues }}", + "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" + }' 2>/dev/null || true diff --git a/.github/workflows/autonomous-issue-manager.yml b/.github/workflows/autonomous-issue-manager.yml new file mode 100644 index 0000000000..5fdbb39e80 --- /dev/null +++ b/.github/workflows/autonomous-issue-manager.yml @@ -0,0 +1,399 @@ +# .github/workflows/autonomous-issue-manager.yml +# Autonomous issue creation, triage, and management + +name: "Autonomous Issue Manager" + +on: + issues: + types: [opened, edited, labeled, assigned] + issue_comment: + types: [created] + schedule: + - cron: '0 9 * * *' # Daily at 9 AM - stale check + workflow_run: + workflows: ["Autonomous Orchestrator", "Autonomous Self-Healer"] + types: [completed] + conclusions: [failure] + workflow_dispatch: + inputs: + action: + description: 'Action to perform' + required: true + type: choice + options: + - triage_all + - cleanup_stale + - generate_report + - create_health_issues + +permissions: + contents: read + issues: write + pull-requests: write + +env: + BLACKROAD_AGENT_API: https://blackroad-agents.blackroad.workers.dev + STALE_DAYS: 30 + CLOSE_DAYS: 7 + +jobs: + # ============================================ + # Smart Issue Triage + # ============================================ + triage: + name: "Smart Triage" + if: github.event_name == 'issues' && github.event.action == 'opened' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: AI Analysis + id: ai + run: | + TITLE="${{ github.event.issue.title }}" + BODY="${{ github.event.issue.body }}" + + # Call AI for smart categorization + ANALYSIS=$(curl -s -X POST "${{ env.BLACKROAD_AGENT_API }}/analyze-issue" \ + -H "Content-Type: application/json" \ + -d '{ + "title": "'"$TITLE"'", + "body": "'"$(echo "$BODY" | head -c 2000 | jq -Rs .)"'", + "repo": "${{ github.repository }}" + }' 2>/dev/null || echo '{}') + + echo "analysis=$ANALYSIS" >> $GITHUB_OUTPUT + + # Parse AI response for labels + LABELS=$(echo "$ANALYSIS" | jq -r '.labels // [] | join(",")' 2>/dev/null || echo "") + PRIORITY=$(echo "$ANALYSIS" | jq -r '.priority // "normal"' 2>/dev/null || echo "normal") + ASSIGNEE=$(echo "$ANALYSIS" | jq -r '.assignee // ""' 2>/dev/null || echo "") + + echo "labels=$LABELS" >> $GITHUB_OUTPUT + echo "priority=$PRIORITY" >> $GITHUB_OUTPUT + echo "assignee=$ASSIGNEE" >> $GITHUB_OUTPUT + + - name: Keyword-Based Labeling + id: keywords + run: | + TITLE="${{ github.event.issue.title }}" + BODY="${{ github.event.issue.body }}" + TEXT="$TITLE $BODY" + LABELS="" + + # Type detection + echo "$TEXT" | grep -qi "bug\|error\|broken\|not working\|crash\|fail" && LABELS="$LABELS,bug" + echo "$TEXT" | grep -qi "feature\|add\|new\|enhance\|request" && LABELS="$LABELS,enhancement" + echo "$TEXT" | grep -qi "question\|how\|help\|what\|why" && LABELS="$LABELS,question" + echo "$TEXT" | grep -qi "doc\|documentation\|readme\|typo" && LABELS="$LABELS,documentation" + + # Area detection + echo "$TEXT" | grep -qi "security\|vulnerability\|cve\|auth" && LABELS="$LABELS,security" + echo "$TEXT" | grep -qi "performance\|slow\|memory\|cpu" && LABELS="$LABELS,performance" + echo "$TEXT" | grep -qi "ui\|frontend\|css\|style\|design" && LABELS="$LABELS,frontend" + echo "$TEXT" | grep -qi "api\|backend\|server\|database" && LABELS="$LABELS,backend" + echo "$TEXT" | grep -qi "ci\|deploy\|workflow\|action" && LABELS="$LABELS,infrastructure" + + # Priority detection + echo "$TEXT" | grep -qi "urgent\|critical\|asap\|important\|blocker" && LABELS="$LABELS,priority:high" + echo "$TEXT" | grep -qi "minor\|low\|when possible" && LABELS="$LABELS,priority:low" + + # Clean up labels + LABELS=$(echo "$LABELS" | sed 's/^,//' | sed 's/,,/,/g') + echo "labels=$LABELS" >> $GITHUB_OUTPUT + + - name: Apply Labels + uses: actions/github-script@v8 + with: + script: | + const aiLabels = '${{ steps.ai.outputs.labels }}'.split(',').filter(l => l); + const keywordLabels = '${{ steps.keywords.outputs.labels }}'.split(',').filter(l => l); + + // Merge and dedupe labels + const allLabels = [...new Set([...aiLabels, ...keywordLabels])].filter(l => l); + + if (allLabels.length > 0) { + // Ensure labels exist (create if not) + for (const label of allLabels) { + try { + await github.rest.issues.getLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label + }); + } catch (e) { + // Label doesn't exist, create it + const colors = { + 'bug': 'd73a4a', + 'enhancement': 'a2eeef', + 'question': 'd876e3', + 'documentation': '0075ca', + 'security': 'b60205', + 'performance': 'fbca04', + 'frontend': '7057ff', + 'backend': '008672', + 'infrastructure': 'c5def5', + 'priority:high': 'b60205', + 'priority:low': 'c2e0c6' + }; + + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label, + color: colors[label] || '333333' + }).catch(() => {}); + } + } + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + labels: allLabels + }); + } + + - name: Welcome Response + uses: actions/github-script@v8 + with: + script: | + const labels = '${{ steps.keywords.outputs.labels }}'.split(',').filter(l => l); + const priority = '${{ steps.ai.outputs.priority }}'; + + let response = `Thanks for opening this issue! 👋\n\n`; + + // Add context based on type + if (labels.includes('bug')) { + response += `This has been identified as a **bug report**. `; + response += `To help us investigate:\n`; + response += `- What version are you using?\n`; + response += `- Can you provide steps to reproduce?\n`; + response += `- Any error messages or logs?\n\n`; + } else if (labels.includes('enhancement')) { + response += `This has been identified as a **feature request**. `; + response += `We'll review and prioritize accordingly.\n\n`; + } else if (labels.includes('question')) { + response += `This has been identified as a **question**. `; + response += `Check our [documentation](https://docs.blackroad.io) while you wait for a response.\n\n`; + } + + if (priority === 'high') { + response += `⚠️ **High priority** - This will be reviewed soon.\n\n`; + } + + response += `**Automated Labels Applied:** ${labels.length > 0 ? labels.map(l => '`' + l + '`').join(', ') : 'None'}\n\n`; + response += `---\n*Triaged by BlackRoad Autonomous Agent*`; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: response + }); + + # ============================================ + # Stale Issue Cleanup + # ============================================ + stale-cleanup: + name: "Stale Cleanup" + if: github.event_name == 'schedule' || github.event.inputs.action == 'cleanup_stale' + runs-on: ubuntu-latest + + steps: + - name: Find Stale Issues + uses: actions/github-script@v8 + with: + script: | + const staleDays = parseInt('${{ env.STALE_DAYS }}'); + const closeDays = parseInt('${{ env.CLOSE_DAYS }}'); + const now = new Date(); + + // Get open issues + const issues = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + per_page: 100 + }); + + for (const issue of issues.data) { + // Skip PRs + if (issue.pull_request) continue; + + const updatedAt = new Date(issue.updated_at); + const daysSinceUpdate = Math.floor((now - updatedAt) / (1000 * 60 * 60 * 24)); + + const hasStaleLabel = issue.labels.some(l => l.name === 'stale'); + const isProtected = issue.labels.some(l => + ['pinned', 'security', 'priority:high', 'in-progress'].includes(l.name) + ); + + if (isProtected) continue; + + // Already marked stale - check if should close + if (hasStaleLabel && daysSinceUpdate >= closeDays) { + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + state: 'closed', + state_reason: 'not_planned' + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: `This issue has been automatically closed due to inactivity.\n\nIf this is still relevant, please reopen it with additional context.\n\n---\n*Closed by BlackRoad Autonomous Agent*` + }); + + console.log(`Closed stale issue #${issue.number}`); + } + // Mark as stale + else if (!hasStaleLabel && daysSinceUpdate >= staleDays) { + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + labels: ['stale'] + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: `This issue has been automatically marked as **stale** because it has not had recent activity.\n\nIt will be closed in ${closeDays} days if no further activity occurs.\n\n---\n*Marked by BlackRoad Autonomous Agent*` + }); + + console.log(`Marked issue #${issue.number} as stale`); + } + } + + # ============================================ + # Auto-Create Issues from Failures + # ============================================ + failure-issue: + name: "Create Failure Issue" + if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'failure' + runs-on: ubuntu-latest + + steps: + - name: Check for Existing Issue + id: check + uses: actions/github-script@v8 + with: + script: | + // Search for existing issue about this workflow + const workflowName = '${{ github.event.workflow_run.name }}'; + const searchQuery = `repo:${context.repo.owner}/${context.repo.repo} is:issue is:open "[Automated] ${workflowName}" in:title`; + + const results = await github.rest.search.issuesAndPullRequests({ + q: searchQuery + }); + + core.setOutput('exists', results.data.total_count > 0); + if (results.data.total_count > 0) { + core.setOutput('issue_number', results.data.items[0].number); + } + + - name: Create or Update Issue + uses: actions/github-script@v8 + with: + script: | + const workflowName = '${{ github.event.workflow_run.name }}'; + const runId = '${{ github.event.workflow_run.id }}'; + const runUrl = '${{ github.event.workflow_run.html_url }}'; + const exists = '${{ steps.check.outputs.exists }}' === 'true'; + const existingNumber = '${{ steps.check.outputs.issue_number }}'; + + const body = `## Workflow Failure Detected + + **Workflow:** ${workflowName} + **Run ID:** ${runId} + **Run URL:** ${runUrl} + **Time:** ${new Date().toISOString()} + + ### Details + The autonomous orchestrator detected a failure in the ${workflowName} workflow. + + ### Suggested Actions + 1. Review the [workflow run logs](${runUrl}) + 2. Check recent commits for potential causes + 3. Run the self-healer workflow if appropriate + + --- + *Created by BlackRoad Autonomous Agent*`; + + if (exists) { + // Add comment to existing issue + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: parseInt(existingNumber), + body: `### New Failure Detected\n\n**Run:** ${runUrl}\n**Time:** ${new Date().toISOString()}` + }); + } else { + // Create new issue + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `[Automated] ${workflowName} Workflow Failure`, + body: body, + labels: ['bug', 'automated', 'ci-failure'] + }); + } + + # ============================================ + # Generate Report + # ============================================ + report: + name: "Generate Issue Report" + if: github.event.inputs.action == 'generate_report' + runs-on: ubuntu-latest + + steps: + - name: Generate Statistics + uses: actions/github-script@v8 + with: + script: | + // Get all issues + const issues = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'all', + per_page: 100 + }); + + const stats = { + total: issues.data.length, + open: issues.data.filter(i => i.state === 'open' && !i.pull_request).length, + closed: issues.data.filter(i => i.state === 'closed' && !i.pull_request).length, + bugs: issues.data.filter(i => i.labels.some(l => l.name === 'bug')).length, + enhancements: issues.data.filter(i => i.labels.some(l => l.name === 'enhancement')).length, + stale: issues.data.filter(i => i.labels.some(l => l.name === 'stale')).length + }; + + console.log('Issue Statistics:', stats); + + // Create summary issue + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `[Report] Issue Statistics - ${new Date().toISOString().split('T')[0]}`, + body: `## Issue Statistics Report + + | Metric | Count | + |--------|-------| + | Total Issues | ${stats.total} | + | Open | ${stats.open} | + | Closed | ${stats.closed} | + | Bugs | ${stats.bugs} | + | Enhancements | ${stats.enhancements} | + | Stale | ${stats.stale} | + + --- + *Generated by BlackRoad Autonomous Agent*`, + labels: ['report', 'automated'] + }); diff --git a/.github/workflows/autonomous-orchestrator.yml b/.github/workflows/autonomous-orchestrator.yml new file mode 100644 index 0000000000..0dd3b8f505 --- /dev/null +++ b/.github/workflows/autonomous-orchestrator.yml @@ -0,0 +1,671 @@ +# .github/workflows/autonomous-orchestrator.yml +# Master coordinator for all autonomous agents +# Drop this in any repo for full autonomous operation + +name: "Autonomous Orchestrator" + +on: + push: + branches: [main, master, develop] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + issues: + types: [opened, labeled] + issue_comment: + types: [created] + schedule: + - cron: '0 */4 * * *' # Every 4 hours + workflow_dispatch: + inputs: + mode: + description: 'Operation mode' + required: false + default: 'auto' + type: choice + options: + - auto + - aggressive + - conservative + - audit-only + force_deploy: + description: 'Force deployment' + required: false + default: false + type: boolean + +permissions: + contents: write + pull-requests: write + issues: write + actions: write + security-events: write + checks: write + +concurrency: + group: autonomous-${{ github.repository }}-${{ github.ref }} + cancel-in-progress: false + +env: + BLACKROAD_AGENT_API: https://blackroad-agents.blackroad.workers.dev + MEMORY_ENABLED: true + AUTO_MERGE: true + AUTO_FIX: true + +jobs: + # ============================================ + # Stage 1: Intelligence Gathering + # ============================================ + analyze: + name: "Analyze Repository" + runs-on: ubuntu-latest + outputs: + project_type: ${{ steps.detect.outputs.type }} + has_tests: ${{ steps.detect.outputs.has_tests }} + has_build: ${{ steps.detect.outputs.has_build }} + health_score: ${{ steps.health.outputs.score }} + priority: ${{ steps.priority.outputs.level }} + action_plan: ${{ steps.plan.outputs.actions }} + memory_context: ${{ steps.memory.outputs.context }} + + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Detect Project Type + id: detect + run: | + TYPE="unknown" + HAS_TESTS="false" + HAS_BUILD="false" + + # Node.js + if [ -f "package.json" ]; then + TYPE="nodejs" + grep -q '"test"' package.json && HAS_TESTS="true" + grep -q '"build"' package.json && HAS_BUILD="true" + # Python + elif [ -f "pyproject.toml" ] || [ -f "requirements.txt" ] || [ -f "setup.py" ]; then + TYPE="python" + [ -d "tests" ] || [ -d "test" ] && HAS_TESTS="true" + # Go + elif [ -f "go.mod" ]; then + TYPE="go" + find . -name "*_test.go" | head -1 | grep -q . && HAS_TESTS="true" + # Rust + elif [ -f "Cargo.toml" ]; then + TYPE="rust" + HAS_TESTS="true" + HAS_BUILD="true" + # Salesforce + elif [ -f "sfdx-project.json" ]; then + TYPE="salesforce" + # Static site + elif [ -f "index.html" ]; then + TYPE="static" + # Cloudflare Worker + elif [ -f "wrangler.toml" ]; then + TYPE="cloudflare-worker" + HAS_BUILD="true" + fi + + echo "type=$TYPE" >> $GITHUB_OUTPUT + echo "has_tests=$HAS_TESTS" >> $GITHUB_OUTPUT + echo "has_build=$HAS_BUILD" >> $GITHUB_OUTPUT + echo "Detected: $TYPE (tests=$HAS_TESTS, build=$HAS_BUILD)" + + - name: Calculate Health Score + id: health + run: | + SCORE=100 + + # Check for common issues + [ ! -f "README.md" ] && SCORE=$((SCORE - 10)) + [ ! -f ".gitignore" ] && SCORE=$((SCORE - 5)) + [ ! -d ".github/workflows" ] && SCORE=$((SCORE - 15)) + + # Security checks + grep -rn "password\s*=" --include="*.js" --include="*.ts" --include="*.py" . 2>/dev/null | grep -v node_modules && SCORE=$((SCORE - 20)) + grep -rn "api_key\s*=" --include="*.js" --include="*.ts" --include="*.py" . 2>/dev/null | grep -v node_modules && SCORE=$((SCORE - 20)) + + # Stale checks + LAST_COMMIT=$(git log -1 --format=%ct 2>/dev/null || echo 0) + NOW=$(date +%s) + DAYS_SINCE=$(( (NOW - LAST_COMMIT) / 86400 )) + [ $DAYS_SINCE -gt 90 ] && SCORE=$((SCORE - 10)) + + [ $SCORE -lt 0 ] && SCORE=0 + echo "score=$SCORE" >> $GITHUB_OUTPUT + echo "Health Score: $SCORE/100" + + - name: Determine Priority + id: priority + run: | + PRIORITY="normal" + + # High priority triggers + if echo "${{ github.event.issue.labels.*.name }}" | grep -qE "critical|urgent|security"; then + PRIORITY="critical" + elif echo "${{ github.event.issue.labels.*.name }}" | grep -qE "high|important"; then + PRIORITY="high" + elif [ "${{ github.event_name }}" = "schedule" ]; then + PRIORITY="background" + fi + + echo "level=$PRIORITY" >> $GITHUB_OUTPUT + echo "Priority: $PRIORITY" + + - name: Fetch Memory Context + id: memory + run: | + # Try to get memory from BlackRoad API + CONTEXT=$(curl -s -f "${{ env.BLACKROAD_AGENT_API }}/memory/${{ github.repository }}" 2>/dev/null || echo '{}') + echo "context=$CONTEXT" >> $GITHUB_OUTPUT + + - name: Create Action Plan + id: plan + run: | + ACTIONS="[]" + + # Build action list based on context + case "${{ github.event_name }}" in + push) + ACTIONS='["test","build","security_scan","quality_check"]' + ;; + pull_request) + ACTIONS='["test","build","code_review","security_scan","auto_merge"]' + ;; + issues) + ACTIONS='["triage","assign","respond"]' + ;; + schedule) + ACTIONS='["health_check","dependency_update","stale_cleanup","security_audit"]' + ;; + *) + ACTIONS='["analyze"]' + ;; + esac + + echo "actions=$ACTIONS" >> $GITHUB_OUTPUT + + # ============================================ + # Stage 2: Test & Build + # ============================================ + test-and-build: + name: "Test & Build" + needs: analyze + runs-on: ubuntu-latest + if: needs.analyze.outputs.project_type != 'unknown' + outputs: + test_result: ${{ steps.test.outputs.result }} + build_result: ${{ steps.build.outputs.result }} + + steps: + - uses: actions/checkout@v6 + + - name: Setup Environment + run: | + case "${{ needs.analyze.outputs.project_type }}" in + nodejs) + echo "Setting up Node.js..." + ;; + python) + echo "Setting up Python..." + pip install pytest pytest-cov 2>/dev/null || true + ;; + go) + echo "Go is pre-installed" + ;; + rust) + echo "Installing Rust..." + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + ;; + esac + + - name: Install Dependencies + run: | + case "${{ needs.analyze.outputs.project_type }}" in + nodejs) + npm ci --ignore-scripts 2>/dev/null || npm install --ignore-scripts 2>/dev/null || true + ;; + python) + [ -f "requirements.txt" ] && pip install -r requirements.txt 2>/dev/null || true + [ -f "pyproject.toml" ] && pip install -e . 2>/dev/null || true + ;; + go) + go mod download 2>/dev/null || true + ;; + rust) + cargo fetch 2>/dev/null || true + ;; + esac + + - name: Run Tests + id: test + continue-on-error: true + run: | + RESULT="skipped" + + if [ "${{ needs.analyze.outputs.has_tests }}" = "true" ]; then + case "${{ needs.analyze.outputs.project_type }}" in + nodejs) + npm test 2>&1 && RESULT="passed" || RESULT="failed" + ;; + python) + pytest -v 2>&1 && RESULT="passed" || python -m unittest discover 2>&1 && RESULT="passed" || RESULT="failed" + ;; + go) + go test ./... 2>&1 && RESULT="passed" || RESULT="failed" + ;; + rust) + cargo test 2>&1 && RESULT="passed" || RESULT="failed" + ;; + esac + fi + + echo "result=$RESULT" >> $GITHUB_OUTPUT + echo "Test result: $RESULT" + + - name: Run Build + id: build + continue-on-error: true + run: | + RESULT="skipped" + + if [ "${{ needs.analyze.outputs.has_build }}" = "true" ]; then + case "${{ needs.analyze.outputs.project_type }}" in + nodejs) + npm run build 2>&1 && RESULT="passed" || RESULT="failed" + ;; + rust) + cargo build --release 2>&1 && RESULT="passed" || RESULT="failed" + ;; + cloudflare-worker) + npx wrangler build 2>/dev/null && RESULT="passed" || RESULT="skipped" + ;; + esac + fi + + echo "result=$RESULT" >> $GITHUB_OUTPUT + echo "Build result: $RESULT" + + # ============================================ + # Stage 3: Security & Quality + # ============================================ + security-scan: + name: "Security Scan" + needs: analyze + runs-on: ubuntu-latest + outputs: + vulnerabilities: ${{ steps.scan.outputs.vulns }} + severity: ${{ steps.scan.outputs.max_severity }} + + steps: + - uses: actions/checkout@v6 + + - name: Run Security Scanners + id: scan + run: | + VULNS=0 + MAX_SEVERITY="none" + + # Scan for secrets + echo "Scanning for secrets..." + if grep -rn --include="*.js" --include="*.ts" --include="*.py" --include="*.json" \ + -E "(password|secret|api_key|token)\s*[:=]\s*['\"][^'\"]+['\"]" . 2>/dev/null | \ + grep -v node_modules | grep -v ".git" | head -5; then + VULNS=$((VULNS + 1)) + MAX_SEVERITY="critical" + fi + + # Check for common vulnerabilities + echo "Checking for common vulnerabilities..." + if [ -f "package.json" ]; then + npm audit --json 2>/dev/null | jq -r '.metadata.vulnerabilities | to_entries[] | select(.value > 0)' && VULNS=$((VULNS + 1)) + fi + + echo "vulns=$VULNS" >> $GITHUB_OUTPUT + echo "max_severity=$MAX_SEVERITY" >> $GITHUB_OUTPUT + + - name: Auto-fix Security Issues + if: env.AUTO_FIX == 'true' && steps.scan.outputs.vulns != '0' + run: | + echo "Attempting auto-fix..." + + # Try to fix npm vulnerabilities + if [ -f "package.json" ]; then + npm audit fix 2>/dev/null || true + fi + + # Check if changes were made + if [ -n "$(git status --porcelain)" ]; then + git config user.name "BlackRoad Bot" + git config user.email "bot@blackroad.ai" + git add -A + git commit -m "fix(security): Auto-fix security vulnerabilities + + Automated security fixes applied by BlackRoad Autonomous Agent. + + Co-Authored-By: BlackRoad Bot " + git push || echo "Push failed - may need PR" + fi + + # ============================================ + # Stage 4: Code Review (PRs only) + # ============================================ + code-review: + name: "AI Code Review" + needs: [analyze, test-and-build] + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Get Changed Files + id: changed + run: | + FILES=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} 2>/dev/null | head -50) + echo "files<> $GITHUB_OUTPUT + echo "$FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: AI Code Analysis + id: ai_review + run: | + # Call BlackRoad AI for code review + REVIEW=$(curl -s -X POST "${{ env.BLACKROAD_AGENT_API }}/review" \ + -H "Content-Type: application/json" \ + -d '{ + "repo": "${{ github.repository }}", + "pr_number": ${{ github.event.pull_request.number }}, + "files": ${{ toJSON(steps.changed.outputs.files) }}, + "test_result": "${{ needs.test-and-build.outputs.test_result }}", + "build_result": "${{ needs.test-and-build.outputs.build_result }}" + }' 2>/dev/null || echo "Review completed") + + echo "AI Review: $REVIEW" + + - name: Post Review Comment + uses: actions/github-script@v8 + with: + script: | + const testResult = '${{ needs.test-and-build.outputs.test_result }}'; + const buildResult = '${{ needs.test-and-build.outputs.build_result }}'; + const healthScore = '${{ needs.analyze.outputs.health_score }}'; + + let status = ''; + if (testResult === 'passed' && buildResult !== 'failed') { + status = '### Status: Ready to Merge'; + } else if (testResult === 'failed') { + status = '### Status: Tests Failing - Needs Fix'; + } else { + status = '### Status: Review Needed'; + } + + const body = `## Autonomous Agent Review + + ${status} + + | Check | Result | + |-------|--------| + | Tests | ${testResult === 'passed' ? 'Passed' : testResult === 'failed' ? 'Failed' : 'Skipped'} | + | Build | ${buildResult === 'passed' ? 'Passed' : buildResult === 'failed' ? 'Failed' : 'Skipped'} | + | Health Score | ${healthScore}/100 | + + --- + *Autonomous review by BlackRoad Agent*`; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: body + }); + + # ============================================ + # Stage 5: Auto-Merge + # ============================================ + auto-merge: + name: "Auto-Merge" + needs: [analyze, test-and-build, security-scan, code-review] + if: | + github.event_name == 'pull_request' && + needs.test-and-build.outputs.test_result != 'failed' && + needs.test-and-build.outputs.build_result != 'failed' && + needs.security-scan.outputs.severity != 'critical' + runs-on: ubuntu-latest + + steps: + - name: Enable Auto-Merge + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "Enabling auto-merge for PR #${{ github.event.pull_request.number }}" + + # Try multiple merge strategies + gh pr merge ${{ github.event.pull_request.number }} \ + --repo ${{ github.repository }} \ + --auto \ + --squash \ + --delete-branch 2>/dev/null || \ + gh pr merge ${{ github.event.pull_request.number }} \ + --repo ${{ github.repository }} \ + --squash 2>/dev/null || \ + echo "Auto-merge queued - waiting for required checks" + + # ============================================ + # Stage 6: Auto-Deploy + # ============================================ + auto-deploy: + name: "Auto-Deploy" + needs: [analyze, test-and-build, security-scan] + if: | + github.event_name == 'push' && + github.ref == 'refs/heads/main' && + needs.test-and-build.outputs.test_result != 'failed' && + needs.security-scan.outputs.severity != 'critical' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Determine Deploy Target + id: target + run: | + TARGET="none" + + # Check for deployment configs + [ -f "wrangler.toml" ] && TARGET="cloudflare" + [ -f "vercel.json" ] && TARGET="vercel" + [ -f "railway.toml" ] && TARGET="railway" + [ -f "Dockerfile" ] && TARGET="docker" + + echo "target=$TARGET" >> $GITHUB_OUTPUT + + - name: Deploy to Cloudflare + if: steps.target.outputs.target == 'cloudflare' + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + run: | + if [ -n "$CLOUDFLARE_API_TOKEN" ]; then + npx wrangler deploy 2>/dev/null || echo "Cloudflare deploy skipped" + fi + + - name: Deploy to Vercel + if: steps.target.outputs.target == 'vercel' + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + run: | + if [ -n "$VERCEL_TOKEN" ]; then + npx vercel --prod --token=$VERCEL_TOKEN 2>/dev/null || echo "Vercel deploy skipped" + fi + + # ============================================ + # Stage 7: Memory Persistence + # ============================================ + persist-memory: + name: "Persist Memory" + needs: [test-and-build, security-scan] + if: always() + runs-on: ubuntu-latest + + steps: + - name: Save Run Memory + run: | + curl -s -X POST "${{ env.BLACKROAD_AGENT_API }}/memory" \ + -H "Content-Type: application/json" \ + -d '{ + "repo": "${{ github.repository }}", + "run_id": "${{ github.run_id }}", + "event": "${{ github.event_name }}", + "results": { + "test": "${{ needs.test-and-build.outputs.test_result }}", + "build": "${{ needs.test-and-build.outputs.build_result }}", + "security": "${{ needs.security-scan.outputs.severity }}" + }, + "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" + }' 2>/dev/null || echo "Memory save queued" + + # ============================================ + # Stage 8: Issue Triage (Issues only) + # ============================================ + issue-triage: + name: "Issue Triage" + needs: analyze + if: github.event_name == 'issues' && github.event.action == 'opened' + runs-on: ubuntu-latest + + steps: + - name: AI Issue Analysis + id: analyze + run: | + TITLE="${{ github.event.issue.title }}" + BODY="${{ github.event.issue.body }}" + + # Determine labels based on content + LABELS="" + + echo "$TITLE $BODY" | grep -qi "bug\|error\|broken\|fix" && LABELS="$LABELS,bug" + echo "$TITLE $BODY" | grep -qi "feature\|add\|new\|enhance" && LABELS="$LABELS,enhancement" + echo "$TITLE $BODY" | grep -qi "question\|how\|help" && LABELS="$LABELS,question" + echo "$TITLE $BODY" | grep -qi "security\|vulnerability\|cve" && LABELS="$LABELS,security" + echo "$TITLE $BODY" | grep -qi "urgent\|critical\|asap" && LABELS="$LABELS,priority:high" + + LABELS=$(echo "$LABELS" | sed 's/^,//') + echo "labels=$LABELS" >> $GITHUB_OUTPUT + + - name: Apply Labels + if: steps.analyze.outputs.labels != '' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + for label in $(echo "${{ steps.analyze.outputs.labels }}" | tr ',' ' '); do + gh issue edit ${{ github.event.issue.number }} --add-label "$label" 2>/dev/null || true + done + + - name: Auto-Respond + uses: actions/github-script@v8 + with: + script: | + const labels = '${{ steps.analyze.outputs.labels }}'.split(',').filter(l => l); + + let response = `Thanks for opening this issue!\n\n`; + response += `**Automated Triage:**\n`; + if (labels.length > 0) { + response += `- Labels applied: ${labels.map(l => '`' + l + '`').join(', ')}\n`; + } + response += `\nA team member will review this shortly. In the meantime:\n`; + response += `- Check if there's a similar issue already open\n`; + response += `- Provide additional context if available\n\n`; + response += `*Triaged by BlackRoad Autonomous Agent*`; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.issue.number, + body: response + }); + + # ============================================ + # Stage 9: Scheduled Maintenance + # ============================================ + maintenance: + name: "Scheduled Maintenance" + needs: analyze + if: github.event_name == 'schedule' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Update Dependencies + run: | + if [ -f "package.json" ]; then + # Check for outdated packages + npm outdated --json > outdated.json 2>/dev/null || true + + # Auto-update patch versions + npm update 2>/dev/null || true + + if [ -n "$(git status --porcelain package-lock.json)" ]; then + git config user.name "BlackRoad Bot" + git config user.email "bot@blackroad.ai" + git add package.json package-lock.json + git commit -m "chore(deps): Auto-update dependencies + + Automated dependency updates by BlackRoad Agent. + + Co-Authored-By: BlackRoad Bot " + git push || echo "Would create PR for updates" + fi + fi + + - name: Clean Stale Branches + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # List merged branches older than 30 days + git fetch --all --prune + for branch in $(git branch -r --merged origin/main | grep -v main | grep -v HEAD); do + LAST_COMMIT=$(git log -1 --format=%ct "$branch" 2>/dev/null || echo 0) + NOW=$(date +%s) + DAYS_OLD=$(( (NOW - LAST_COMMIT) / 86400 )) + + if [ $DAYS_OLD -gt 30 ]; then + BRANCH_NAME=$(echo "$branch" | sed 's|origin/||') + echo "Deleting stale branch: $BRANCH_NAME ($DAYS_OLD days old)" + git push origin --delete "$BRANCH_NAME" 2>/dev/null || true + fi + done + + - name: Health Report + uses: actions/github-script@v8 + with: + script: | + const healthScore = '${{ needs.analyze.outputs.health_score }}'; + + // Only create issue if health is poor + if (parseInt(healthScore) < 70) { + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `[Automated] Repository Health Alert (Score: ${healthScore}/100)`, + body: `## Repository Health Report + + **Current Health Score:** ${healthScore}/100 + + The autonomous agent has detected potential issues with this repository. + + ### Recommended Actions + - Review and address any security warnings + - Update outdated dependencies + - Add missing documentation + + --- + *Generated by BlackRoad Autonomous Agent*`, + labels: ['maintenance', 'automated'] + }); + } diff --git a/.github/workflows/autonomous-self-healer.yml b/.github/workflows/autonomous-self-healer.yml new file mode 100644 index 0000000000..956a5c508a --- /dev/null +++ b/.github/workflows/autonomous-self-healer.yml @@ -0,0 +1,377 @@ +# .github/workflows/autonomous-self-healer.yml +# Self-healing agent that automatically fixes common issues + +name: "Autonomous Self-Healer" + +on: + workflow_run: + workflows: ["Autonomous Orchestrator", "CI"] + types: [completed] + conclusions: [failure] + schedule: + - cron: '30 */6 * * *' # Every 6 hours, offset from orchestrator + workflow_dispatch: + inputs: + fix_type: + description: 'Type of fix to attempt' + required: false + default: 'all' + type: choice + options: + - all + - tests + - build + - lint + - deps + - security + +permissions: + contents: write + pull-requests: write + actions: read + checks: read + +env: + BLACKROAD_AGENT_API: https://blackroad-agents.blackroad.workers.dev + MAX_FIX_ATTEMPTS: 3 + +jobs: + # ============================================ + # Diagnose the Failure + # ============================================ + diagnose: + name: "Diagnose Failure" + runs-on: ubuntu-latest + outputs: + failure_type: ${{ steps.analyze.outputs.type }} + failure_details: ${{ steps.analyze.outputs.details }} + fixable: ${{ steps.analyze.outputs.fixable }} + fix_strategy: ${{ steps.strategy.outputs.approach }} + + steps: + - uses: actions/checkout@v6 + + - name: Get Failed Run Logs + id: logs + if: github.event.workflow_run.id + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Download workflow run logs + gh run view ${{ github.event.workflow_run.id }} --log 2>/dev/null > run_logs.txt || true + echo "logs_retrieved=true" >> $GITHUB_OUTPUT + + - name: Analyze Failure + id: analyze + run: | + TYPE="unknown" + DETAILS="" + FIXABLE="false" + + if [ -f "run_logs.txt" ]; then + # Test failures + if grep -qi "test.*fail\|jest.*fail\|pytest.*fail\|assertion.*error" run_logs.txt; then + TYPE="test_failure" + DETAILS=$(grep -i "fail\|error" run_logs.txt | head -10) + FIXABLE="maybe" + # Build failures + elif grep -qi "build.*fail\|compile.*error\|typescript.*error" run_logs.txt; then + TYPE="build_failure" + DETAILS=$(grep -i "error" run_logs.txt | head -10) + FIXABLE="maybe" + # Lint failures + elif grep -qi "lint.*error\|eslint.*error\|prettier" run_logs.txt; then + TYPE="lint_failure" + FIXABLE="true" + # Dependency failures + elif grep -qi "npm.*err\|pip.*error\|dependency.*not found\|module.*not found" run_logs.txt; then + TYPE="dependency_failure" + DETAILS=$(grep -i "not found\|missing" run_logs.txt | head -5) + FIXABLE="true" + # Security failures + elif grep -qi "vulnerability\|security\|cve-" run_logs.txt; then + TYPE="security_failure" + FIXABLE="true" + fi + fi + + echo "type=$TYPE" >> $GITHUB_OUTPUT + echo "details<> $GITHUB_OUTPUT + echo "$DETAILS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + echo "fixable=$FIXABLE" >> $GITHUB_OUTPUT + + echo "Diagnosed: $TYPE (fixable=$FIXABLE)" + + - name: Determine Fix Strategy + id: strategy + run: | + APPROACH="manual" + + case "${{ steps.analyze.outputs.type }}" in + lint_failure) + APPROACH="auto_lint_fix" + ;; + dependency_failure) + APPROACH="reinstall_deps" + ;; + security_failure) + APPROACH="security_patch" + ;; + test_failure) + APPROACH="ai_assisted_fix" + ;; + build_failure) + APPROACH="ai_assisted_fix" + ;; + esac + + echo "approach=$APPROACH" >> $GITHUB_OUTPUT + + # ============================================ + # Auto-Fix: Lint Issues + # ============================================ + fix-lint: + name: "Fix Lint Issues" + needs: diagnose + if: needs.diagnose.outputs.fix_strategy == 'auto_lint_fix' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ github.event.workflow_run.head_branch || github.ref }} + + - name: Setup Node + uses: actions/setup-node@v6 + with: + node-version: '20' + + - name: Install Dependencies + run: npm ci --ignore-scripts 2>/dev/null || npm install --ignore-scripts + + - name: Run Lint Fix + run: | + # Try multiple linting tools + npm run lint:fix 2>/dev/null || \ + npx eslint . --fix 2>/dev/null || \ + npx prettier --write . 2>/dev/null || \ + echo "No lint fix available" + + - name: Commit Fixes + run: | + if [ -n "$(git status --porcelain)" ]; then + git config user.name "BlackRoad Self-Healer" + git config user.email "healer@blackroad.ai" + git add -A + git commit -m "fix(lint): Auto-fix linting issues + + Automated lint fixes applied by BlackRoad Self-Healing Agent. + + Co-Authored-By: BlackRoad Bot " + git push + echo "Lint fixes committed successfully" + else + echo "No lint issues to fix" + fi + + # ============================================ + # Auto-Fix: Dependencies + # ============================================ + fix-deps: + name: "Fix Dependencies" + needs: diagnose + if: needs.diagnose.outputs.fix_strategy == 'reinstall_deps' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ github.event.workflow_run.head_branch || github.ref }} + + - name: Fix Node Dependencies + if: hashFiles('package.json') != '' + run: | + # Remove node_modules and lock file, reinstall + rm -rf node_modules package-lock.json 2>/dev/null || true + npm install + + # Dedupe and fix + npm dedupe 2>/dev/null || true + npm audit fix 2>/dev/null || true + + - name: Fix Python Dependencies + if: hashFiles('requirements.txt') != '' || hashFiles('pyproject.toml') != '' + run: | + pip install --upgrade pip + [ -f "requirements.txt" ] && pip install -r requirements.txt + [ -f "pyproject.toml" ] && pip install -e . + + - name: Commit Fixes + run: | + if [ -n "$(git status --porcelain)" ]; then + git config user.name "BlackRoad Self-Healer" + git config user.email "healer@blackroad.ai" + git add -A + git commit -m "fix(deps): Reinstall and fix dependencies + + Dependency issues resolved by BlackRoad Self-Healing Agent. + + Co-Authored-By: BlackRoad Bot " + git push + fi + + # ============================================ + # Auto-Fix: Security Issues + # ============================================ + fix-security: + name: "Fix Security Issues" + needs: diagnose + if: needs.diagnose.outputs.fix_strategy == 'security_patch' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ github.event.workflow_run.head_branch || github.ref }} + + - name: Fix npm Security Issues + if: hashFiles('package.json') != '' + run: | + npm audit fix 2>/dev/null || true + npm audit fix --force 2>/dev/null || true + + - name: Fix Python Security Issues + if: hashFiles('requirements.txt') != '' + run: | + pip install safety pip-audit 2>/dev/null || true + pip-audit --fix 2>/dev/null || true + + - name: Create Security PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if [ -n "$(git status --porcelain)" ]; then + BRANCH="security-fix-$(date +%Y%m%d-%H%M%S)" + git config user.name "BlackRoad Self-Healer" + git config user.email "healer@blackroad.ai" + + git checkout -b "$BRANCH" + git add -A + git commit -m "fix(security): Auto-patch security vulnerabilities + + Security vulnerabilities patched by BlackRoad Self-Healing Agent. + + Co-Authored-By: BlackRoad Bot " + git push -u origin "$BRANCH" + + gh pr create \ + --title "fix(security): Auto-patch security vulnerabilities" \ + --body "## Security Patch + + This PR was automatically generated by the BlackRoad Self-Healing Agent. + + ### Changes + - Applied security patches via npm audit fix / pip-audit + + ### Verification + - Automated tests will verify compatibility + - Please review before merging + + --- + *Generated by BlackRoad Autonomous Agent*" \ + --label "security,automated" + fi + + # ============================================ + # AI-Assisted Fix + # ============================================ + ai-fix: + name: "AI-Assisted Fix" + needs: diagnose + if: needs.diagnose.outputs.fix_strategy == 'ai_assisted_fix' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ github.event.workflow_run.head_branch || github.ref }} + + - name: Request AI Fix + id: ai + run: | + # Send failure details to AI for analysis and fix + RESPONSE=$(curl -s -X POST "${{ env.BLACKROAD_AGENT_API }}/fix" \ + -H "Content-Type: application/json" \ + -d '{ + "repo": "${{ github.repository }}", + "failure_type": "${{ needs.diagnose.outputs.failure_type }}", + "details": ${{ toJSON(needs.diagnose.outputs.failure_details) }}, + "run_id": "${{ github.event.workflow_run.id }}" + }' 2>/dev/null || echo '{"status":"queued"}') + + echo "AI Response: $RESPONSE" + echo "response=$RESPONSE" >> $GITHUB_OUTPUT + + - name: Create Issue for Manual Review + if: needs.diagnose.outputs.fixable == 'maybe' + uses: actions/github-script@v8 + with: + script: | + const failureType = '${{ needs.diagnose.outputs.failure_type }}'; + const details = `${{ needs.diagnose.outputs.failure_details }}`; + + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `[Self-Healer] ${failureType.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} Needs Review`, + body: `## Automated Failure Analysis + + **Failure Type:** ${failureType} + **Run ID:** ${{ github.event.workflow_run.id || 'N/A' }} + + ### Error Details + \`\`\` + ${details.substring(0, 2000)} + \`\`\` + + ### AI Analysis + The self-healing agent attempted to analyze this issue but requires human review. + + ### Suggested Actions + 1. Review the error logs above + 2. Check recent changes that may have caused this + 3. Apply appropriate fix + + --- + *Created by BlackRoad Self-Healing Agent*`, + labels: ['bug', 'automated', 'needs-triage'] + }); + + # ============================================ + # Report Results + # ============================================ + report: + name: "Report Results" + needs: [diagnose, fix-lint, fix-deps, fix-security, ai-fix] + if: always() + runs-on: ubuntu-latest + + steps: + - name: Summarize Healing Attempt + run: | + echo "## Self-Healing Summary" + echo "Failure Type: ${{ needs.diagnose.outputs.failure_type }}" + echo "Fix Strategy: ${{ needs.diagnose.outputs.fix_strategy }}" + echo "Fixable: ${{ needs.diagnose.outputs.fixable }}" + + # Log to memory + curl -s -X POST "${{ env.BLACKROAD_AGENT_API }}/memory" \ + -H "Content-Type: application/json" \ + -d '{ + "repo": "${{ github.repository }}", + "event": "self_heal_attempt", + "failure_type": "${{ needs.diagnose.outputs.failure_type }}", + "strategy": "${{ needs.diagnose.outputs.fix_strategy }}", + "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" + }' 2>/dev/null || true diff --git a/.github/workflows/check-dependencies.yml b/.github/workflows/check-dependencies.yml new file mode 100644 index 0000000000..44cf5feb37 --- /dev/null +++ b/.github/workflows/check-dependencies.yml @@ -0,0 +1,277 @@ +name: Check Dependencies + +on: + workflow_dispatch: + schedule: + - cron: '0 */6 * * *' # Every 6 hours + +permissions: + contents: read + issues: write + +jobs: + check-deps: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Check if index exists + id: check_index + run: | + if [ -f .blackroad/workflow-index.jsonl ]; then + echo "exists=true" >> $GITHUB_OUTPUT + else + echo "exists=false" >> $GITHUB_OUTPUT + echo "⚠️ No workflow index found in this repo" + fi + + - name: Parse dependencies + if: steps.check_index.outputs.exists == 'true' + id: parse + uses: actions/github-script@v8 + with: + script: | + const fs = require('fs'); + const indexPath = '.blackroad/workflow-index.jsonl'; + + if (!fs.existsSync(indexPath)) { + console.log('No index file found'); + return; + } + + const lines = fs.readFileSync(indexPath, 'utf8').split('\n').filter(l => l); + const workflows = lines.map(l => JSON.parse(l)); + + // Find workflows with dependencies + const withDeps = workflows.filter(w => w.deps && w.deps.length > 0); + + if (withDeps.length === 0) { + console.log('No workflows with dependencies'); + return; + } + + // Separate local and cross-repo deps + const localDeps = []; + const crossRepoDeps = []; + + for (const workflow of withDeps) { + for (const dep of workflow.deps) { + if (dep.includes('#')) { + // Cross-repo dependency + crossRepoDeps.push({ + workflow: workflow.id, + dep: dep, + repo: dep.split('#')[0], + depId: dep.split('#')[1] + }); + } else { + // Local dependency + localDeps.push({ + workflow: workflow.id, + dep: dep + }); + } + } + } + + core.setOutput('local_deps', JSON.stringify(localDeps)); + core.setOutput('cross_repo_deps', JSON.stringify(crossRepoDeps)); + core.setOutput('has_deps', 'true'); + + - name: Check local dependencies + if: steps.parse.outputs.has_deps == 'true' + id: check_local + run: | + LOCAL_DEPS='${{ steps.parse.outputs.local_deps }}' + + if [ "$LOCAL_DEPS" = "[]" ]; then + echo "No local dependencies to check" + echo "blocked=false" >> $GITHUB_OUTPUT + exit 0 + fi + + echo "Checking local dependencies..." + echo "$LOCAL_DEPS" | jq -r '.[] | "\(.workflow) depends on \(.dep)"' + + BLOCKED=false + + # Check each dependency + for dep_id in $(echo "$LOCAL_DEPS" | jq -r '.[].dep'); do + # Check if dep exists and is Done + if grep -q "\"id\":\"$dep_id\"" .blackroad/workflow-index.jsonl; then + STATE=$(grep "\"id\":\"$dep_id\"" .blackroad/workflow-index.jsonl | jq -r '.state') + if [ "$STATE" != "Done" ]; then + echo "⚠️ Dependency $dep_id is not Done (state: $STATE)" + BLOCKED=true + fi + else + echo "⚠️ Dependency $dep_id not found in index" + BLOCKED=true + fi + done + + echo "blocked=$BLOCKED" >> $GITHUB_OUTPUT + + - name: Check cross-repo dependencies + if: steps.parse.outputs.has_deps == 'true' + id: check_cross + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + CROSS_DEPS='${{ steps.parse.outputs.cross_repo_deps }}' + + if [ "$CROSS_DEPS" = "[]" ]; then + echo "No cross-repo dependencies to check" + echo "blocked=false" >> $GITHUB_OUTPUT + exit 0 + fi + + echo "Checking cross-repo dependencies..." + echo "$CROSS_DEPS" | jq -r '.[] | "\(.workflow) depends on \(.repo)#\(.depId)"' + + BLOCKED=false + + # For each cross-repo dep, try to fetch the workflow index + for repo in $(echo "$CROSS_DEPS" | jq -r '.[].repo' | sort -u); do + echo "Fetching index from $repo..." + + # Try to download the workflow index from the dependency repo + if gh api "/repos/$repo/contents/.blackroad/workflow-index.jsonl" \ + --jq '.content' 2>/dev/null | base64 -d > /tmp/dep-index.jsonl; then + echo "✅ Found index in $repo" + + # Check each dep from this repo + for dep_id in $(echo "$CROSS_DEPS" | jq -r "select(.repo==\"$repo\") | .depId"); do + if grep -q "\"id\":\"$dep_id\"" /tmp/dep-index.jsonl; then + STATE=$(grep "\"id\":\"$dep_id\"" /tmp/dep-index.jsonl | jq -r '.state') + if [ "$STATE" != "Done" ]; then + echo "⚠️ Dependency $repo#$dep_id is not Done (state: $STATE)" + BLOCKED=true + else + echo "✅ Dependency $repo#$dep_id is Done" + fi + else + echo "⚠️ Dependency $dep_id not found in $repo" + BLOCKED=true + fi + done + else + echo "⚠️ Could not fetch index from $repo (may be private or not exist)" + BLOCKED=true + fi + done + + echo "blocked=$BLOCKED" >> $GITHUB_OUTPUT + + - name: Create or update alert issue + if: steps.check_local.outputs.blocked == 'true' || steps.check_cross.outputs.blocked == 'true' + uses: actions/github-script@v8 + with: + script: | + const localDeps = JSON.parse('${{ steps.parse.outputs.local_deps }}'); + const crossDeps = JSON.parse('${{ steps.parse.outputs.cross_repo_deps }}'); + + // Build alert body + let body = '## ⚠️ Blocked Dependencies Detected\n\n'; + body += '_This issue is auto-generated by dependency checker_\n\n'; + body += `**Last checked**: ${new Date().toISOString()}\n\n`; + + if (localDeps.length > 0) { + body += '### Local Dependencies\n\n'; + for (const dep of localDeps) { + body += `- \`${dep.workflow}\` depends on \`${dep.dep}\`\n`; + } + body += '\n'; + } + + if (crossDeps.length > 0) { + body += '### Cross-Repo Dependencies\n\n'; + for (const dep of crossDeps) { + body += `- \`${dep.workflow}\` depends on \`${dep.repo}#${dep.depId}\`\n`; + } + body += '\n'; + } + + body += '---\n\n'; + body += '**Next Steps**:\n'; + body += '1. Check status of blocked dependencies\n'; + body += '2. Coordinate with dependency owners if needed\n'; + body += '3. Update traffic lights (🔴 Red if blocked, 🟡 Yellow if waiting)\n'; + body += '4. This issue will auto-close when all dependencies are Done\n'; + + // Check for existing alert + const { data: issues } = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + labels: 'dependency-alert', + state: 'open' + }); + + if (issues.length > 0) { + // Update existing alert + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issues[0].number, + body: body + }); + console.log(`Updated alert issue #${issues[0].number}`); + } else { + // Create new alert + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: '⚠️ Dependency Alert: Blocked Workflows', + body: body, + labels: ['dependency-alert', '🔴'] + }); + console.log('Created new dependency alert issue'); + } + + - name: Close alert if all clear + if: steps.check_local.outputs.blocked == 'false' && steps.check_cross.outputs.blocked == 'false' + uses: actions/github-script@v8 + with: + script: | + // Find open alert issues + const { data: issues } = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + labels: 'dependency-alert', + state: 'open' + }); + + for (const issue of issues) { + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + state: 'closed' + }); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: '✅ All dependencies resolved. Auto-closing.' + }); + + console.log(`Closed alert issue #${issue.number}`); + } + + - name: Generate summary + run: | + echo "## 🔍 Dependency Check Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ steps.check_local.outputs.blocked }}" = "true" ] || [ "${{ steps.check_cross.outputs.blocked }}" = "true" ]; then + echo "**Status**: ⚠️ **BLOCKED**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Some workflows have unresolved dependencies." >> $GITHUB_STEP_SUMMARY + echo "Check the dependency alert issue for details." >> $GITHUB_STEP_SUMMARY + else + echo "**Status**: ✅ **ALL CLEAR**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "All dependencies are resolved." >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..c8f9d57848 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +jobs: + shellcheck: + name: ShellCheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Install shellcheck (ARM64 compatible) + run: | + if ! command -v shellcheck &>/dev/null; then + sudo apt-get update -qq && sudo apt-get install -y -qq shellcheck + fi + shellcheck --version | head -2 + - name: ShellCheck all scripts + run: | + find . -name "*.sh" -not -path "./.git/*" -not -path "./node_modules/*" | \ + xargs shellcheck --severity=warning 2>&1 | head -50 || echo "shellcheck warnings above" + continue-on-error: true + + test: + name: CLI Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run lint + - run: npm run typecheck + - run: npm test diff --git a/.github/workflows/deploy-kpi-workers.yml b/.github/workflows/deploy-kpi-workers.yml new file mode 100644 index 0000000000..7bd04488e5 --- /dev/null +++ b/.github/workflows/deploy-kpi-workers.yml @@ -0,0 +1,110 @@ +name: Deploy KPI Workers + +on: + push: + branches: [main, master] + paths: + - 'workers/kpi-*/**' + workflow_dispatch: + inputs: + worker: + description: 'Which KPI worker to deploy (all, collector, github, infra, agents, aggregator, dashboard)' + required: true + default: 'all' + type: choice + options: + - all + - collector + - github + - infra + - agents + - aggregator + - dashboard + +concurrency: + group: deploy-kpi-${{ github.ref }} + cancel-in-progress: true + +env: + CF_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + +jobs: + detect-changes: + name: Detect Changed Workers + runs-on: ubuntu-latest + outputs: + workers: ${{ steps.filter.outputs.workers }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 2 + + - id: filter + name: Determine workers to deploy + run: | + if [ "${{ github.event.inputs.worker }}" = "all" ] || [ "${{ github.event_name }}" = "push" ]; then + echo 'workers=["collector","github","infra","agents","aggregator","dashboard"]' >> $GITHUB_OUTPUT + else + echo 'workers=["${{ github.event.inputs.worker }}"]' >> $GITHUB_OUTPUT + fi + + deploy: + name: Deploy kpi-${{ matrix.worker }} + needs: detect-changes + runs-on: ubuntu-latest + strategy: + matrix: + worker: ${{ fromJson(needs.detect-changes.outputs.workers) }} + fail-fast: false + steps: + - uses: actions/checkout@v6 + + - name: Deploy kpi-${{ matrix.worker }} + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + workingDirectory: workers/kpi-${{ matrix.worker }} + command: deploy + + - name: Verify deployment + run: | + WORKER_URL="https://blackroad-kpi-${{ matrix.worker }}.blackroad.workers.dev" + echo "Checking health at $WORKER_URL/health..." + for i in 1 2 3; do + STATUS=$(curl -s -o /dev/null -w '%{http_code}' "$WORKER_URL/health" 2>/dev/null || echo "000") + if [ "$STATUS" = "200" ]; then + echo "kpi-${{ matrix.worker }} is healthy" + exit 0 + fi + echo "Attempt $i: status $STATUS, retrying..." + sleep 5 + done + echo "Warning: health check did not return 200, deployment may still be propagating" + + notify: + name: Deployment Summary + needs: deploy + runs-on: ubuntu-latest + if: always() + steps: + - name: Summary + run: | + echo "## KPI Workers Deployment Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Worker | Status |" >> $GITHUB_STEP_SUMMARY + echo "|--------|--------|" >> $GITHUB_STEP_SUMMARY + echo "| kpi-collector | deployed |" >> $GITHUB_STEP_SUMMARY + echo "| kpi-github | deployed |" >> $GITHUB_STEP_SUMMARY + echo "| kpi-infra | deployed |" >> $GITHUB_STEP_SUMMARY + echo "| kpi-agents | deployed |" >> $GITHUB_STEP_SUMMARY + echo "| kpi-aggregator | deployed |" >> $GITHUB_STEP_SUMMARY + echo "| kpi-dashboard | deployed |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Endpoints" >> $GITHUB_STEP_SUMMARY + echo "- Dashboard: https://blackroad-kpi-dashboard.blackroad.workers.dev" >> $GITHUB_STEP_SUMMARY + echo "- Aggregator: https://blackroad-kpi-aggregator.blackroad.workers.dev/kpi" >> $GITHUB_STEP_SUMMARY + echo "- GitHub KPIs: https://blackroad-kpi-github.blackroad.workers.dev/summary" >> $GITHUB_STEP_SUMMARY + echo "- Infra KPIs: https://blackroad-kpi-infra.blackroad.workers.dev/summary" >> $GITHUB_STEP_SUMMARY + echo "- Agent KPIs: https://blackroad-kpi-agents.blackroad.workers.dev/summary" >> $GITHUB_STEP_SUMMARY + echo "- Collector: https://blackroad-kpi-collector.blackroad.workers.dev/metrics" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/deploy-to-all-repos.yml b/.github/workflows/deploy-to-all-repos.yml new file mode 100644 index 0000000000..28a7c1f8e3 --- /dev/null +++ b/.github/workflows/deploy-to-all-repos.yml @@ -0,0 +1,184 @@ +name: "Deploy Workflows to All Repos" + +on: + workflow_dispatch: + inputs: + target_orgs: + description: "Comma-separated org names (or 'all')" + required: true + default: "all" + dry_run: + description: "Dry run (no actual pushes)" + required: true + default: "true" + type: boolean + workflow_set: + description: "Which workflow set to deploy" + required: true + default: "standard" + type: choice + options: + - standard + - e2e + - scrapers + - all + +permissions: + contents: write + actions: write + +env: + GH_TOKEN: ${{ secrets.CROSS_ORG_TOKEN }} + +jobs: + prepare: + name: "Prepare deployment manifest" + runs-on: ubuntu-latest + outputs: + repos: ${{ steps.list.outputs.repos }} + steps: + - uses: actions/checkout@v6 + + - name: List target repos + id: list + run: | + ORGS="${{ inputs.target_orgs }}" + if [ "$ORGS" = "all" ]; then + ORGS="BlackRoad-OS-Inc,BlackRoad-OS,blackboxprogramming,BlackRoad-AI,BlackRoad-Cloud,BlackRoad-Security,BlackRoad-Media,BlackRoad-Foundation,BlackRoad-Interactive,BlackRoad-Hardware,BlackRoad-Labs,BlackRoad-Studio,BlackRoad-Ventures,BlackRoad-Education,BlackRoad-Gov,Blackbox-Enterprises,BlackRoad-Archive" + fi + + REPOS="[]" + IFS=',' read -ra ORG_ARRAY <<< "$ORGS" + for org in "${ORG_ARRAY[@]}"; do + org=$(echo "$org" | xargs) + echo "Fetching repos for: ${org}" + ORG_REPOS=$(gh api --paginate "orgs/${org}/repos?per_page=100" \ + --jq '[.[] | select(.archived != true) | .full_name]' 2>/dev/null || echo '[]') + REPOS=$(echo "$REPOS" "$ORG_REPOS" | jq -s 'add | unique') + done + + # Limit matrix to 256 for GitHub Actions + TOTAL=$(echo "$REPOS" | jq 'length') + echo "Total repos: ${TOTAL}" + if [ "$TOTAL" -gt 256 ]; then + REPOS=$(echo "$REPOS" | jq '.[0:256]') + fi + + echo "repos=$(echo "$REPOS" | jq -c .)" >> $GITHUB_OUTPUT + + deploy: + name: "Deploy to ${{ matrix.repo }}" + needs: prepare + runs-on: ubuntu-latest + strategy: + fail-fast: false + max-parallel: 10 + matrix: + repo: ${{ fromJson(needs.prepare.outputs.repos) }} + steps: + - uses: actions/checkout@v6 + + - name: Deploy workflow files + run: | + REPO="${{ matrix.repo }}" + DRY_RUN="${{ inputs.dry_run }}" + WORKFLOW_SET="${{ inputs.workflow_set }}" + + echo "Deploying ${WORKFLOW_SET} workflows to ${REPO}..." + + if [ "$DRY_RUN" = "true" ]; then + echo "[DRY RUN] Would deploy to ${REPO}" + exit 0 + fi + + # Clone target repo + TMPDIR=$(mktemp -d) + gh repo clone "${REPO}" "${TMPDIR}/repo" -- --depth 1 2>/dev/null || { + echo "Could not clone ${REPO}, skipping" + exit 0 + } + + cd "${TMPDIR}/repo" + mkdir -p .github/workflows + + # Copy appropriate workflow files + case "$WORKFLOW_SET" in + standard|all) + cp "${{ github.workspace }}/.github/workflows/ci.yml" .github/workflows/ 2>/dev/null || true + ;; + e2e|all) + # Create an E2E check workflow + cat > .github/workflows/blackroad-e2e-check.yml << 'WFEOF' + name: "BlackRoad E2E Check" + on: + push: + branches: [main, master] + pull_request: + jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Detect stack + run: | + [ -f package.json ] && echo "STACK=node" >> $GITHUB_ENV + [ -f requirements.txt ] && echo "STACK=python" >> $GITHUB_ENV + [ -f go.mod ] && echo "STACK=go" >> $GITHUB_ENV + - name: Report to operator + run: | + echo "Repo: ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY + echo "Stack: ${STACK:-unknown}" >> $GITHUB_STEP_SUMMARY + WFEOF + ;; + scrapers|all) + # Create a scraper reporting workflow + cat > .github/workflows/blackroad-report.yml << 'WFEOF' + name: "Report to BlackRoad Operator" + on: + schedule: + - cron: "0 3 * * 1" + jobs: + report: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Generate report + run: | + echo '{"repo":"${{ github.repository }}","time":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' > /tmp/report.json + cat /tmp/report.json + WFEOF + ;; + esac + + # Commit and push + git config user.name "blackroad-bot" + git config user.email "bot@blackroad.io" + git add .github/workflows/ + if git diff --cached --quiet; then + echo "No changes for ${REPO}" + else + git commit -m "chore: add BlackRoad E2E workflows [operator-deploy]" + git push || echo "Push failed for ${REPO}" + fi + + rm -rf "${TMPDIR}" + + - name: Log result + run: | + echo "## Deploy Result" >> $GITHUB_STEP_SUMMARY + echo "- **Repo:** ${{ matrix.repo }}" >> $GITHUB_STEP_SUMMARY + echo "- **Workflow set:** ${{ inputs.workflow_set }}" >> $GITHUB_STEP_SUMMARY + echo "- **Dry run:** ${{ inputs.dry_run }}" >> $GITHUB_STEP_SUMMARY + + summary: + name: "Deployment summary" + needs: deploy + runs-on: ubuntu-latest + if: always() + steps: + - name: Report + run: | + echo "## Workflow Deployment Complete" >> $GITHUB_STEP_SUMMARY + echo "- **Orgs:** ${{ inputs.target_orgs }}" >> $GITHUB_STEP_SUMMARY + echo "- **Status:** ${{ needs.deploy.result }}" >> $GITHUB_STEP_SUMMARY + echo "- **Returns to:** BlackRoad-OS-Inc/blackroad-operator" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/multi-org-indexer.yml b/.github/workflows/multi-org-indexer.yml new file mode 100644 index 0000000000..3c2963bc05 --- /dev/null +++ b/.github/workflows/multi-org-indexer.yml @@ -0,0 +1,191 @@ +name: Multi-Org Indexer + +on: + workflow_dispatch: + inputs: + mode: + description: 'Mode: scrape, deploy-workflows, full' + required: false + default: 'full' + repository_dispatch: + types: [product-discovery] + schedule: + - cron: '0 0 * * *' # Daily midnight UTC + +permissions: + contents: write + issues: write + +jobs: + scrape-all-orgs: + if: github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' + runs-on: ubuntu-latest + strategy: + matrix: + org: + - BlackRoad-OS-Inc + - BlackRoad-OS + - blackboxprogramming + - BlackRoad-AI + - BlackRoad-Cloud + - BlackRoad-Security + - BlackRoad-Media + - BlackRoad-Foundation + - BlackRoad-Interactive + - BlackRoad-Hardware + - BlackRoad-Labs + - BlackRoad-Studio + - BlackRoad-Ventures + - BlackRoad-Education + - BlackRoad-Gov + - Blackbox-Enterprises + - BlackRoad-Archive + fail-fast: false + steps: + - uses: actions/checkout@v6 + + - name: Scrape ${{ matrix.org }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "=== Scraping ${{ matrix.org }} ===" + mkdir -p index/${{ matrix.org }} + + gh api "orgs/${{ matrix.org }}/repos" \ + --paginate \ + -q '.[] | { + name: .name, + full_name: .full_name, + description: .description, + language: .language, + topics: .topics, + stargazers_count: .stargazers_count, + forks_count: .forks_count, + size: .size, + fork: .fork, + archived: .archived, + private: .private, + html_url: .html_url, + homepage: .homepage, + has_pages: .has_pages, + pushed_at: .pushed_at, + updated_at: .updated_at + }' > index/${{ matrix.org }}/repos.jsonl 2>/dev/null || echo "[]" > index/${{ matrix.org }}/repos.jsonl + + count=$(wc -l < index/${{ matrix.org }}/repos.jsonl) + echo "Found ${count} repos in ${{ matrix.org }}" + + - name: Upload Index + uses: actions/upload-artifact@v7 + with: + name: index-${{ matrix.org }} + path: index/${{ matrix.org }}/ + retention-days: 30 + + consolidate: + needs: scrape-all-orgs + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Download All Indexes + uses: actions/download-artifact@v8 + with: + path: index/ + + - name: Generate Master Index + run: | + python3 << 'PYEOF' + import json, os, glob + from datetime import datetime, timezone + + index = { + "generated_at": datetime.now(timezone.utc).isoformat(), + "owner": "BlackRoad OS, Inc.", + "organizations": {}, + "totals": {"repos": 0, "original": 0, "forks": 0, "archived": 0} + } + + for org_dir in sorted(glob.glob("index/index-*/")): + org_name = os.path.basename(os.path.normpath(org_dir)).replace("index-", "") + repos_file = os.path.join(org_dir, "repos.jsonl") + repos = [] + if os.path.exists(repos_file): + with open(repos_file) as f: + for line in f: + try: + repos.append(json.loads(line)) + except: + pass + + original = [r for r in repos if not r.get("fork")] + forks = [r for r in repos if r.get("fork")] + archived = [r for r in repos if r.get("archived")] + + index["organizations"][org_name] = { + "total": len(repos), + "original": len(original), + "forks": len(forks), + "archived": len(archived), + "languages": {}, + "repos": repos + } + + for r in repos: + lang = r.get("language") or "Unknown" + index["organizations"][org_name]["languages"][lang] = \ + index["organizations"][org_name]["languages"].get(lang, 0) + 1 + + index["totals"]["repos"] += len(repos) + index["totals"]["original"] += len(original) + index["totals"]["forks"] += len(forks) + index["totals"]["archived"] += len(archived) + + with open("index/MASTER-INDEX.json", "w") as f: + json.dump(index, f, indent=2) + + print(f"Total repos: {index['totals']['repos']}") + print(f"Original: {index['totals']['original']}") + print(f"Forks: {index['totals']['forks']}") + PYEOF + + - name: Commit Index + run: | + git config user.name "BlackRoad Operator" + git config user.email "blackroad.systems@gmail.com" + git add index/ + git commit -m "chore: update multi-org index $(date +%Y-%m-%d)" || echo "No changes" + git push || echo "Push failed - may need token" + + receive-discovery: + if: github.event_name == 'repository_dispatch' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Process Discovery Report + run: | + echo "=== Received Product Discovery Report ===" + echo "Repo: ${{ github.event.client_payload.repo }}" + echo "Runtime: ${{ github.event.client_payload.runtime }}" + echo "Framework: ${{ github.event.client_payload.framework }}" + echo "Files: ${{ github.event.client_payload.files }}" + + mkdir -p index/discoveries + cat > "index/discoveries/$(echo '${{ github.event.client_payload.repo }}' | tr '/' '-').json" << EOF + { + "repo": "${{ github.event.client_payload.repo }}", + "runtime": "${{ github.event.client_payload.runtime }}", + "framework": "${{ github.event.client_payload.framework }}", + "files": "${{ github.event.client_payload.files }}", + "received_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)" + } + EOF + + - name: Commit Discovery + run: | + git config user.name "BlackRoad Operator" + git config user.email "blackroad.systems@gmail.com" + git add index/ + git commit -m "chore: received discovery from ${{ github.event.client_payload.repo }}" || echo "No changes" + git push || echo "Push failed" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..1e1b8f3ecb --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,22 @@ +# Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved. +name: Release + +on: + push: + tags: ['v*'] + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run build + - run: npm pack + - uses: softprops/action-gh-release@v2 + with: + files: '*.tgz' diff --git a/.github/workflows/scrape-and-index.yml b/.github/workflows/scrape-and-index.yml new file mode 100644 index 0000000000..e4f0687dab --- /dev/null +++ b/.github/workflows/scrape-and-index.yml @@ -0,0 +1,98 @@ +name: "Scrape & Index All Orgs" + +on: + workflow_dispatch: + inputs: + mode: + description: "Scan mode" + required: true + default: "all" + type: choice + options: + - all + - products + - single-org + target_org: + description: "Target org (for single-org mode)" + required: false + default: "BlackRoad-OS-Inc" + schedule: + # Run daily at 2 AM UTC + - cron: "0 2 * * *" + +permissions: + contents: write + actions: read + +env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + scrape: + name: "Scrape Organizations" + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ github.ref }} + + - name: Install dependencies + run: | + sudo apt-get update -qq + sudo apt-get install -y -qq jq + + - name: Authenticate gh CLI + run: gh auth status + + - name: Run org scraper + run: | + chmod +x scrapers/scrape-all-orgs.sh + if [ "${{ inputs.mode }}" = "single-org" ]; then + ./scrapers/scrape-all-orgs.sh org "${{ inputs.target_org }}" + elif [ "${{ inputs.mode }}" = "products" ]; then + chmod +x scrapers/scrape-products.sh + ./scrapers/scrape-products.sh + else + ./scrapers/scrape-all-orgs.sh all + fi + + - name: Run product scanner + if: inputs.mode == 'all' || inputs.mode == 'products' + run: | + chmod +x scrapers/scrape-products.sh + ./scrapers/scrape-products.sh + + - name: Commit and push index + run: | + git config user.name "blackroad-bot" + git config user.email "bot@blackroad.io" + git add scrapers/output/ || true + if git diff --cached --quiet; then + echo "No changes to commit" + else + git commit -m "chore: update org/product index [automated]" + git push + fi + + - name: Upload index artifact + uses: actions/upload-artifact@v7 + with: + name: org-index-${{ github.run_number }} + path: scrapers/output/ + retention-days: 30 + + notify: + name: "Notify on completion" + needs: scrape + runs-on: ubuntu-latest + if: always() + steps: + - name: Summary + run: | + echo "## Scrape & Index Complete" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Mode:** ${{ inputs.mode || 'scheduled' }}" >> $GITHUB_STEP_SUMMARY + echo "- **Status:** ${{ needs.scrape.result }}" >> $GITHUB_STEP_SUMMARY + echo "- **Target:** BlackRoad-OS-Inc/blackroad-operator" >> $GITHUB_STEP_SUMMARY + echo "- **Time:** $(date -u)" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/workflow-index-sync.yml b/.github/workflows/workflow-index-sync.yml new file mode 100644 index 0000000000..2c5c0102ac --- /dev/null +++ b/.github/workflows/workflow-index-sync.yml @@ -0,0 +1,152 @@ +name: Workflow Index Sync + +on: + issues: + types: [opened, edited, closed, reopened] + workflow_dispatch: + +permissions: + contents: write + issues: read + +jobs: + sync-index: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Create .blackroad directory + run: | + mkdir -p .blackroad + + - name: Extract workflow metadata + id: metadata + uses: actions/github-script@v8 + with: + script: | + const issue = context.payload.issue; + if (!issue) { + console.log('No issue in payload, skipping'); + return; + } + + // Extract workflow ID from labels or title + const workflowIdLabel = issue.labels.find(l => + l.name.match(/^(WF|SEC|INF|EXP|FIX)-\d{8}-[A-Z]{3}-\d{4}$/) + ); + + const workflowId = workflowIdLabel + ? workflowIdLabel.name + : null; + + if (!workflowId) { + console.log('No workflow ID found, skipping'); + return; + } + + // Parse workflow ID components + const [prefix, date, idScope, seq] = workflowId.split('-'); + + // Extract dependencies from body + const depsMatch = issue.body?.match(/Dependencies?:\s*([^\n]+)/i); + const deps = depsMatch + ? depsMatch[1].split(',').map(d => d.trim()).filter(d => d) + : []; + + // Extract other metadata from labels + const trafficLight = issue.labels.find(l => + l.name.match(/[🟢🟡🔴]/) + )?.name || '🟢'; + + const state = issue.state === 'closed' ? 'Done' : 'Active'; + + const scope = issue.labels.find(l => + ['Local', 'Service', 'System', 'Public', 'Experimental'].includes(l.name) + )?.name || 'Service'; + + const risk = issue.labels.find(l => + ['Low', 'Medium', 'High', 'Critical'].includes(l.name) + )?.name || 'Unknown'; + + const intent = issue.labels.find(l => + ['Build', 'Fix', 'Explore', 'Research', 'Deploy', 'Monitor'].includes(l.name) + )?.name || 'Build'; + + // Create index entry + const entry = { + id: workflowId, + repo: context.repo.owner + '/' + context.repo.repo, + title: issue.title, + state: state, + scope: scope, + risk: risk, + intent: intent, + traffic_light: trafficLight, + deps: deps, + url: issue.html_url, + timestamp: new Date().toISOString(), + updated_at: issue.updated_at + }; + + // Write to output + core.setOutput('entry', JSON.stringify(entry)); + core.setOutput('workflow_id', workflowId); + + - name: Append to index + if: steps.metadata.outputs.entry + run: | + ENTRY='${{ steps.metadata.outputs.entry }}' + WORKFLOW_ID='${{ steps.metadata.outputs.workflow_id }}' + + # Create index file if it doesn't exist + touch .blackroad/workflow-index.jsonl + + # Check if entry already exists (by ID) + if grep -q "\"id\":\"$WORKFLOW_ID\"" .blackroad/workflow-index.jsonl; then + # Update existing entry (remove old, append new) + grep -v "\"id\":\"$WORKFLOW_ID\"" .blackroad/workflow-index.jsonl > .blackroad/workflow-index.tmp + mv .blackroad/workflow-index.tmp .blackroad/workflow-index.jsonl + fi + + # Append new entry + echo "$ENTRY" >> .blackroad/workflow-index.jsonl + + echo "✅ Updated workflow index: $WORKFLOW_ID" + + - name: Update sync timestamp + if: steps.metadata.outputs.entry + run: | + date -u +"%Y-%m-%dT%H:%M:%SZ" > .blackroad/last-sync.txt + + - name: Commit changes + if: steps.metadata.outputs.entry + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git add .blackroad/workflow-index.jsonl .blackroad/last-sync.txt + + if git diff --staged --quiet; then + echo "No changes to commit" + else + git commit -m "📇 Update workflow index: ${{ steps.metadata.outputs.workflow_id }}" + git push + echo "✅ Pushed workflow index update" + fi + + - name: Generate summary + if: steps.metadata.outputs.entry + run: | + echo "## 📇 Workflow Index Updated" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Workflow ID**: \`${{ steps.metadata.outputs.workflow_id }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Index Location**: \`.blackroad/workflow-index.jsonl\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Total workflows in this repo:" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + wc -l < .blackroad/workflow-index.jsonl >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..3dc01e4a91 --- /dev/null +++ b/.gitignore @@ -0,0 +1,90 @@ +# BlackRoad OS - Security-First Gitignore + +# Secrets & credentials (NEVER commit) +.env +.env.* +!.env.example +*.pem +*.key +*.p12 +*.pfx +*.keystore +**/credentials.json +**/service-account*.json +**/.master.key +**/vault/.master.key + +# API tokens & auth +.cloudflare-token +.railway-token +.vercel-token +.digitalocean-token +.anthropic-key +.openai-key + +# Process files +*.pid +*.sock +*.lock +!package-lock.json +!pnpm-lock.yaml + +# Runtime & build +logs/ +node_modules/ +__pycache__/ +*.pyc +.next/ +dist/ +build/ +.cache/ +.turbo/ +.vercel/ + +# OS artifacts +.DS_Store +Thumbs.db +*.swp +*.swo +*~ + +# IDE +.idea/ +.vscode/settings.json +.vscode/launch.json + +# SQLite WAL/SHM (database files are OK, journals are temp) +*.db-wal +*.db-shm + +# Test artifacts +test_radar.md +test_radar2.md +coverage/ +.nyc_output/ + +# Wallet & seed files (NEVER commit) +**/seeds/ +**/wallets/ +*.wallet.dat +**/credentials.yaml + +# Local-only settings +.claude/settings.local.json + +# Scraper output (regenerated, may contain sensitive repo data) +scrapers/output/*.json +scrapers/output/*.jsonl +scrapers/output/*/ +!scrapers/output/.gitkeep +indexer/output/*.json +!indexer/output/.gitkeep + +# Temp / scratch files +COMMIT_MESSAGE.txt +OWNERSHIP_TEST.txt +Auto-select +*.save + +# Large data directories +blackroad-pixel-assets/ diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..8e56283332 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +dist +node_modules +coverage +*.tgz diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000000..89a0a2b7f0 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "semi": false +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..91df0233c6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,562 @@ +# CLAUDE.md + +This file provides guidance to Claude Code when working with the `blackroad-operator` repository. + +--- + +## Project Overview + +`blackroad-operator` is the CLI tooling, node bootstrap, and operational control center for BlackRoad OS. It provides two complementary CLI interfaces: + +1. **TypeScript CLI** (`src/`) — A modern `commander`-based CLI published as `@blackroad/operator` (the `br` binary via `dist/bin/br.js`) +2. **Shell CLI** (`br` at root) — A 91 KB zsh dispatcher that routes `br ` to 90 tool scripts in `tools/` + +The repo also contains the MCP bridge server, agent infrastructure, coordination system, and project templates. + +**Owner:** BlackRoad OS, Inc. (proprietary, all rights reserved) + +--- + +## Quick Start + +```bash +# TypeScript CLI +npm install +npm run build # Compile TypeScript to dist/ +npm run dev # Watch mode with tsx +npm test # Run vitest test suite +npm run lint # Check formatting with prettier +npm run format # Auto-format with prettier + +# Shell CLI +chmod +x br +./br help # Show all tool commands +``` + +--- + +## Repository Structure + +``` +blackroad-operator/ +├── src/ # TypeScript source (the @blackroad/operator package) +│ ├── bin/br.ts # Entry point — parses CLI args +│ ├── cli/commands/ # Commander subcommands (9 commands) +│ ├── core/ # Shared utilities (client, config, logger, spinner) +│ ├── formatters/ # Output formatters (brand, json, table) +│ ├── bootstrap/ # Preflight checks & project templates +│ └── index.ts # Public API exports +├── test/ # Vitest unit tests (mirrors src/ structure) +├── tests/ # Shell-based golden tests +├── br # Main shell CLI dispatcher (zsh) +├── tools/ # 90 tool scripts invoked via `br ` +├── lib/ # Shell libraries (colors, config, db, errors, system, ollama) +├── docs/ # All documentation (organized by category) +│ ├── agents/ # Agent docs (CECE, Lucidia, etc.) +│ ├── architecture/ # System architecture, federation, scaling +│ ├── cli/ # CLI commands, API reference +│ ├── deployment/ # Deploy, backup, production inventory +│ ├── security/ # Security guides, secrets management +│ ├── guides/ # Onboarding, FAQ, troubleshooting, contributing +│ ├── hardware/ # Raspberry Pi, hardware guides +│ ├── features/ # Plugins, skills, monitoring, networking, etc. +│ ├── planning/ # Roadmap, planning docs +│ ├── products/ # Dashboard, mobile, product docs +│ └── brand/ # Brand assets (JSON) +├── scripts/ # Shell scripts (organized by category) +│ ├── agents/ # Agent management scripts +│ ├── chat/ # Ollama chat scripts +│ ├── monitoring/ # Monitoring & dashboards +│ ├── system/ # System utilities +│ ├── visual/ # Visual/terminal effects +│ ├── network/ # Network tools +│ ├── collaboration/ # Multi-agent collaboration +│ └── launchers/ # Boot, hub, menu, demo +├── python/ # Python scripts (conductor, RPG, etc.) +├── blackroad-core/ # Tokenless gateway architecture +│ ├── gateway/ # Express.js gateway server + providers +│ ├── agents/ # Agent shell scripts (planner, alice, lucidia, etc.) +│ └── policies/ # Agent permission matrix +├── mcp-bridge/ # FastAPI MCP bridge server (localhost:8420) +├── agents/ # Agent manifests, registry, active/idle/processing dirs +├── coordination/ # Multi-agent coordination scripts +├── config/ # Configuration files (topology, devices, deployments) +├── integrations/ # Service integrations (33+ services) +├── templates/ # Project & integration templates +├── orgs/ # Organization monorepos (core/, ai/, enterprise/, personal/) +├── shared/ # Inter-agent messaging (inbox, outbox, signals, mesh) +├── websites/ # Static sites, HTML apps, brand style guides +├── .github/workflows/ # CI/CD (ci.yml, release.yml, autonomous-*.yml) +├── package.json # Node.js package config +├── tsconfig.json # TypeScript compiler config +├── vitest.config.ts # Test runner config +└── .prettierrc # Code formatter config +``` + +--- + +## TypeScript Source (`src/`) + +### Architecture + +``` +src/bin/br.ts → src/cli/commands/index.ts → individual command files + ↓ + src/core/client.ts (HTTP → gateway) + src/core/config.ts (conf-based settings) + src/core/logger.ts (colored console output) + src/formatters/* (table, json, brand) +``` + +### CLI Commands + +| Command | File | Status | Description | +|---------|------|--------|-------------| +| `br status` | `cli/commands/status.ts` | Working | Query gateway health + list agents | +| `br agents` | `cli/commands/agents.ts` | Working | List agents (table or `--json`) | +| `br invoke` | `cli/commands/invoke.ts` | Working | Invoke agent with a task | +| `br gateway health` | `cli/commands/gateway.ts` | Working | Check gateway status | +| `br gateway url` | `cli/commands/gateway.ts` | Working | Show gateway URL | +| `br config` | `cli/commands/config.ts` | Working | View/set configuration | +| `br bottlenecks` | `cli/commands/bottlenecks.ts` | Working | Performance bottleneck analysis | +| `br deploy` | `cli/commands/deploy.ts` | Stub | Deployment (not yet implemented) | +| `br init` | `cli/commands/init.ts` | Stub | Project scaffolding (not yet implemented) | +| `br logs` | `cli/commands/logs.ts` | Stub | Log tailing (not yet implemented) | + +### Core Modules + +- **`core/client.ts`** — `GatewayClient` class. Makes HTTP `GET`/`POST` requests to the BlackRoad gateway (default `http://127.0.0.1:8787`). Reads `BLACKROAD_GATEWAY_URL` env var. +- **`core/config.ts`** — Uses `conf` library. Stores `gatewayUrl`, `defaultAgent` (`octavia`), and `logLevel` in `~/.config/blackroad/`. +- **`core/logger.ts`** — Colored console output: `info` (cyan), `success` (green), `warn` (yellow), `error` (red), `debug` (gray, only if `DEBUG` env set). +- **`core/spinner.ts`** — Wraps `ora` for loading indicators (magenta color). + +### Formatters + +- **`formatters/brand.ts`** — Brand colors: hot pink `#FF1D6C`, amber `#F5A623`, violet `#9C27B0`, electric blue `#2979FF`. Provides `logo()` and `header()` methods. +- **`formatters/json.ts`** — Syntax-highlighted JSON (keys cyan, strings green, numbers yellow, booleans magenta, null gray). +- **`formatters/table.ts`** — ASCII table with auto-width columns and `─`/`│`/`┼` borders. + +### Bootstrap + +- **`bootstrap/preflight.ts`** — Checks Node.js >= 22 and gateway reachability. +- **`bootstrap/setup.ts`** — Saves gateway URL and default agent to config. +- **`bootstrap/templates.ts`** — Two project templates: `worker` (Cloudflare Worker) and `api` (Hono API service). + +### Public API (`index.ts`) + +Exports: `GatewayClient`, `loadConfig`, `logger`, `createSpinner`, `formatTable`, `formatJson`, `brand`. + +--- + +## Build & Development + +### Tech Stack + +| Tool | Version | Purpose | +|------|---------|---------| +| TypeScript | 5.7+ | Source language | +| Node.js | 22+ | Runtime (required) | +| Commander | 13.x | CLI framework | +| Chalk | 5.x | Terminal colors | +| Conf | 13.x | Config persistence | +| Ora | 8.x | Spinners | +| Vitest | 3.x | Test runner | +| Prettier | 3.x | Code formatting | +| tsx | 4.x | Dev-time TypeScript execution | +| Wrangler | 4.x | Cloudflare Workers (dev dep) | + +### Commands + +```bash +npm run build # tsc — compile src/ to dist/ +npm run dev # tsx watch src/bin/br.ts — live reload +npm run typecheck # tsc --noEmit — type check only +npm test # vitest run — run all tests +npm run test:watch # vitest — watch mode +npm run lint # prettier --check . +npm run format # prettier --write . +``` + +### TypeScript Configuration + +- **Target:** ES2024 +- **Module:** NodeNext (ESM) +- **Strict mode:** enabled +- **Output:** `dist/` directory +- **Source maps & declarations:** enabled + +### Prettier Configuration + +```json +{ + "singleQuote": true, + "trailingComma": "all", + "semi": false +} +``` + +--- + +## Testing + +### Vitest (TypeScript) + +Test files live in `test/` mirroring the `src/` structure: + +``` +test/ +├── bootstrap/ +│ ├── preflight.test.ts # Node.js version & gateway checks +│ ├── setup.test.ts # Config setup tests +│ └── templates.test.ts # Project template tests +├── cli/ +│ ├── deploy.test.ts # Deploy command tests +│ ├── init.test.ts # Init command tests +│ ├── logs.test.ts # Logs command tests +│ └── program.test.ts # CLI program registration tests +├── core/ +│ ├── client.test.ts # GatewayClient unit tests +│ ├── config.test.ts # Config defaults test +│ ├── logger.test.ts # Logger output tests +│ └── spinner.test.ts # Spinner tests +├── e2e/ +│ ├── bootstrap.test.ts # Bootstrap e2e tests +│ ├── cli.test.ts # Full CLI e2e tests +│ ├── client-integration.test.ts +│ ├── formatters.test.ts # Formatter e2e tests +│ └── gateway-mock.test.ts # Gateway mock e2e tests +├── endpoints/ +│ └── local-endpoints.test.ts # Local endpoint tests +├── formatters/ +│ ├── brand.test.ts # Brand color/logo tests +│ ├── json.test.ts # JSON formatter tests +│ └── table.test.ts # Table formatting tests +└── integrations/ + └── tokenless-paths.test.ts # Tokenless gateway path tests +``` + +Run with: `npm test` or `npx vitest run` + +Tests use `vi.stubGlobal('fetch', ...)` for mocking HTTP requests. + +### Golden Tests (Shell) + +`tests/run.sh` runs golden-file comparison tests against the `br` shell script. Compares actual output to expected output in `tests/operator.golden`. + +--- + +## CI/CD + +### CI Workflow (`.github/workflows/ci.yml`) + +Triggers on push/PR to `main`. Runs on **self-hosted ARM64 runners** (Raspberry Pi fleet — $0 billable minutes). + +**Jobs:** +1. **ShellCheck** — Lints all `.sh` files with `--severity=warning` (continue-on-error) +2. **CLI Tests** — `npm install` + `npm test` + `br` syntax validation (continue-on-error) + +### Release Workflow (`.github/workflows/release.yml`) + +Triggers on version tags (`v*`). Builds, packs (`npm pack`), and publishes `.tgz` to GitHub Releases. + +### Autonomous Workflows + +| Workflow | Purpose | +|----------|---------| +| `autonomous-orchestrator.yml` | Multi-service orchestration | +| `autonomous-self-healer.yml` | Auto-remediation | +| `autonomous-issue-manager.yml` | Issue automation | +| `autonomous-dependency-manager.yml` | Dependency updates | +| `autonomous-cross-repo.yml` | Cross-repo automation | +| `check-dependencies.yml` | Dependency validation | +| `workflow-index-sync.yml` | Cross-repo workflow indexing | + +--- + +## Shell CLI (`br` dispatcher) + +The root `br` script is a 91 KB zsh dispatcher that routes to 90 tool scripts in `tools/`. + +### Tool Script Pattern + +Each tool lives at `tools//br-.sh`: + +```bash +#!/bin/zsh +# Colors +GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m' + +# SQLite database +DB_FILE="$HOME/.blackroad/.db" +init_db() { sqlite3 "$DB_FILE" "CREATE TABLE IF NOT EXISTS ..."; } + +# Command routing +case "$1" in + cmd1) ... ;; + *) show_help ;; +esac +``` + +### Shell Libraries (`lib/`) + +| Library | Purpose | +|---------|---------| +| `colors.sh` | Terminal color definitions | +| `config.sh` | Configuration helpers | +| `db.sh` | SQLite database helpers | +| `errors.sh` | Error handling | +| `system.sh` | System utilities | +| `ollama.sh` | Ollama API integration | +| `services.sh` | Service management | + +### Key Tool Categories (90 tools) + +| Category | Tools | +|----------|-------| +| **Agents** | agent-gateway, agent-router, agent-runtime, agent-tasks, agents-live | +| **AI** | ai, coding-assistant, context-radar, pair-programming, talk | +| **Git** | git-ai, git-integration | +| **DevOps** | deploy-cmd, deploy-manager, docker-manager, ci-pipeline | +| **Cloud** | cloudflare, vercel-pro, ocean-droplets, worker-bridge | +| **Database** | db-client | +| **Monitoring** | health-check, metrics-dashboard, perf-monitor, web-monitor, status-all | +| **Security** | security-scanner, security-hardening, secrets-vault, compliance-scanner, ssl-manager | +| **Pi** | pi, pi-manager, fleet | +| **Identity** | cece-identity, whoami | +| **Comms** | email, mail, notifications, notify, broadcast | +| **Utils** | search, smart-search, file-finder, snippet-manager, quick-notes, task-manager | + +--- + +## Gateway Architecture + +### Tokenless Gateway (`blackroad-core/`) + +Agents never embed API keys. All LLM provider communication flows through the gateway: + +``` +[Agent CLIs] → [Gateway :8787] → [Ollama / Claude / OpenAI / Gemini] +``` + +**Gateway Providers:** `blackroad-core/gateway/providers/` +- `ollama.js` — Local Ollama models +- `anthropic.js` — Claude +- `openai.js` — OpenAI +- `gemini.js` — Google Gemini + +**Agent Permissions:** `blackroad-core/policies/agent-permissions.json` + +**Verify no leaked tokens:** `blackroad-core/scripts/verify-tokenless-agents.sh` + +### Gateway API + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| `/v1/health` | GET | Health check (status, version, uptime) | +| `/v1/agents` | GET | List registered agents | +| `/v1/invoke` | POST | Invoke agent with task (`{agent, task}`) | + +--- + +## MCP Bridge (`mcp-bridge/`) + +FastAPI server for remote AI agent access. Runs on `127.0.0.1:8420`. + +```bash +cd mcp-bridge && ./start.sh +``` + +| Endpoint | Method | Purpose | +|----------|--------|---------| +| `/` | GET | Service info | +| `/system` | GET | System status | +| `/exec` | POST | Execute shell command | +| `/file/read` | POST | Read a file | +| `/file/write` | POST | Write a file | +| `/memory/write` | POST | Store key-value | +| `/memory/read` | POST | Retrieve key-value | +| `/memory/list` | GET | List all memory keys | + +Requires Bearer token auth (`MCP_BRIDGE_TOKEN` env var). + +--- + +## Agent System + +### Core Agents + +| Agent | Color | Role | +|-------|-------|------| +| **Octavia** | Purple | The Architect — systems design, strategy | +| **Lucidia** | Cyan | The Dreamer — creative, vision | +| **Alice** | Green | The Operator — DevOps, automation | +| **Aria** | Blue | The Interface — frontend, UX | +| **Shellfish** | Red | The Hacker — security, exploits | + +### Agent Infrastructure (`agents/`) + +``` +agents/ +├── manifest.json # Infrastructure config +├── registry.json # Active agent registry +├── active/ # Currently running agents +├── idle/ # Available agents +├── processing/ # Agents working on tasks +└── archive/ # Completed runs +``` + +### Coordination (`coordination/`) + +- `send-dm-to-agents.sh` — Broadcast messages to agents +- `collaboration-update.sh` — Update collaboration state +- `blackroad-directory-waterfall.sh` — Hierarchical agent routing + +--- + +## Shell Scripts (`scripts/`) + +Organized into category subdirectories: + +| Directory | Scripts | +|-----------|---------| +| `scripts/launchers/` | `hub.sh`, `intro.sh`, `boot.sh`, `menu.sh`, `demo.sh` | +| `scripts/monitoring/` | `god.sh`, `mission.sh`, `dash.sh`, `monitor.sh`, `status.sh`, `health.sh`, `spark.sh`, `logs.sh`, `events.sh`, `timeline.sh`, `report.sh`, `bottlenecks.sh` | +| `scripts/network/` | `net.sh`, `wire.sh`, `traffic.sh`, `blackroad-mesh.sh` | +| `scripts/agents/` | `agent.sh`, `roster.sh`, `inspect.sh`, `soul.sh`, `office.sh`, `bonds.sh`, `skills.sh`, `wake.sh`, `handoff.sh`, `install-cece.sh` | +| `scripts/chat/` | `chat.sh`, `focus.sh`, `convo.sh`, `broadcast.sh`, `think.sh`, `debate.sh`, `story.sh`, `whisper.sh`, `council.sh`, `thoughts.sh`, `roundtable.sh` | +| `scripts/system/` | `mem.sh`, `tasks.sh`, `queue.sh`, `config.sh`, `alert.sh`, `all.sh`, `find.sh`, `help.sh` | +| `scripts/visual/` | `clock.sh`, `pulse.sh`, `matrix.sh`, `saver.sh`, `mood.sh` | +| `scripts/collaboration/` | `collab-status.sh`, `collab-task-router.sh`, `join-collaboration.sh` | + +--- + +## Conventions + +### Code Style + +- **TypeScript:** ESM (`"type": "module"`), strict mode, single quotes, no semicolons, trailing commas +- **Shell:** zsh scripts, consistent color scheme (`GREEN`/`RED`/`CYAN`/`YELLOW`/`NC`) +- **Copyright header:** Every source file starts with `// Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved.` +- **Database:** SQLite for all persistent storage, path `~/.blackroad/.db` + +### Brand Colors (mandatory for UI work) + +``` +Hot Pink: #FF1D6C (primary) +Amber: #F5A623 +Violet: #9C27B0 +Electric Blue: #2979FF +Black: #000000 +White: #FFFFFF +``` + +**Forbidden colors (old system):** `#FF9D00`, `#FF6B00`, `#FF0066`, `#FF006B`, `#D600AA`, `#7700FF`, `#0066FF` + +### Adding a New CLI Command (TypeScript) + +1. Create `src/cli/commands/.ts` exporting a `Command` +2. Import and register in `src/cli/commands/index.ts` via `program.addCommand()` +3. Add tests in `test//.test.ts` + +### Adding a New Tool (Shell) + +1. Create `tools//br-.sh` +2. Add route to the `br` dispatcher (case statement) +3. `chmod +x tools//br-.sh` + +--- + +## Environment Variables + +### Gateway + +```bash +BLACKROAD_GATEWAY_URL=http://127.0.0.1:8787 # Gateway endpoint +BLACKROAD_GATEWAY_BIND=127.0.0.1 # Bind address +BLACKROAD_GATEWAY_PORT=8787 # Port +``` + +### MCP Bridge + +```bash +MCP_BRIDGE_TOKEN= # Auth token for bridge API +``` + +### Debug + +```bash +DEBUG=1 # Enable debug logging +``` + +### Configuration (via `br config`) + +```bash +gatewayUrl # Gateway URL (default: http://127.0.0.1:8787) +defaultAgent # Default agent name (default: octavia) +logLevel # Log level (default: info) +``` + +--- + +## Key Dependencies + +```json +{ + "dependencies": { + "chalk": "^5.4.1", // Terminal colors + "commander": "^13.1.0", // CLI framework + "conf": "^13.0.1", // Config persistence + "ora": "^8.2.0" // Spinners + }, + "devDependencies": { + "typescript": "^5.7.3", // Compiler + "vitest": "^3.0.5", // Test runner + "tsx": "^4.19.0", // Dev-time TS execution + "prettier": "^3.4.2", // Formatter + "wrangler": "^4.67.0" // Cloudflare Workers + } +} +``` + +Node.js 22+ is required (`"engines": { "node": ">=22" }`). + +--- + +## Security + +- Never commit `.env` files, API keys, or secrets (enforced by `.gitignore`) +- No API tokens in agent code — all provider calls go through the tokenless gateway +- Gateway binds to localhost by default +- MCP bridge requires Bearer token authentication +- All code is proprietary to BlackRoad OS, Inc. +- CODEOWNERS requires review from `@blackboxprogramming` for all changes + +--- + +## Subprojects in This Repo + +| Directory | Description | +|-----------|-------------| +| `blackroad-core/` | Tokenless gateway + agent scripts | +| `blackroad-web/` | Next.js web application | +| `blackroad-web-core/` | Web core package | +| `blackroad-os/` | Main OS codebase | +| `blackroad-sdk/` | SDK package | +| `blackroad-sf/` | Salesforce LWC project | +| `blackroad-api/` | REST API service | +| `blackroad-docs/` | Documentation site | +| `blackroad-gateway/` | Gateway infrastructure | +| `blackroad-infra/` | IaC & deployment configs | +| `blackroad-hardware/` | Hardware integration | +| `blackroad-math/` | Mathematical utilities | +| `dashboard/` | Next.js dashboard app | +| `workers/` | Cloudflare Workers (auth, email, copilot) | +| `websites/` | Static sites, HTML apps, brand style guides | +| `orgs/` | Organization monorepos (core, ai, enterprise, personal) | +| `python/` | Python scripts (conductor, RPG, etc.) | +| `docs/` | All documentation (organized by category) | +| `scripts/` | Shell scripts (organized by category) | + +--- + +*All content in this repository is proprietary to BlackRoad OS, Inc. (c) 2024-2026. All rights reserved.* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..36e286b31d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM node:20-alpine AS builder +WORKDIR /app +COPY package*.json ./ +RUN npm ci --production +COPY . . +RUN npm run build --if-present + +FROM node:20-alpine +WORKDIR /app +COPY --from=builder /app . +EXPOSE 3000 +CMD ["node", "src/index.js"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..c9703339f1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,1308 @@ +BLACKROAD OS, INC. — PROPRIETARY SOFTWARE LICENSE +================================================== + +Copyright (c) 2024-2026 BlackRoad OS, Inc. All Rights Reserved. +Founder, CEO & Sole Stockholder: Alexa Louise Amundson + +PROPRIETARY, CONFIDENTIAL, AND TRADE SECRET + +This license agreement ("Agreement") is a legally binding contract between +BlackRoad OS, Inc., a Delaware C-Corporation, solely owned and operated by +Alexa Louise Amundson ("Company," "Licensor," "BlackRoad," or "We"), and any person or +entity ("You," "User," "Recipient") who accesses, views, downloads, copies, +or otherwise interacts with this software, source code, documentation, +configurations, workflows, assets, and all associated materials +(collectively, "Software"). + +BY ACCESSING THIS SOFTWARE IN ANY MANNER, YOU ACKNOWLEDGE THAT YOU HAVE +READ, UNDERSTOOD, AND AGREE TO BE BOUND BY THE TERMS OF THIS AGREEMENT. +IF YOU DO NOT AGREE, YOU MUST IMMEDIATELY CEASE ALL ACCESS AND DESTROY +ANY COPIES IN YOUR POSSESSION. + +================================================== +SECTION 1 — OWNERSHIP AND INTELLECTUAL PROPERTY +================================================== + +1.1 SOLE OWNERSHIP. This Software is the exclusive intellectual property + of BlackRoad OS, Inc. and its sole owner, Alexa Louise Amundson. All + right, title, and interest in and to the Software, including without + limitation all copyrights, patents, patent rights, trade secrets, + trademarks, service marks, trade dress, moral rights, rights of + publicity, and all other intellectual property rights therein, are + and shall remain the exclusive property of BlackRoad OS, Inc. + +1.2 ORGANIZATIONAL SCOPE. This license applies to all repositories, + code, documentation, configurations, workflows, CI/CD pipelines, + infrastructure-as-code, deployment scripts, AI agent definitions, + model weights, training data, and assets across ALL seventeen (17) + GitHub organizations owned by BlackRoad OS, Inc., including but not + limited to: + + BlackRoad-OS-Inc, BlackRoad-OS, blackboxprogramming, BlackRoad-AI, + BlackRoad-Cloud, BlackRoad-Security, BlackRoad-Media, + BlackRoad-Foundation, BlackRoad-Interactive, BlackRoad-Hardware, + BlackRoad-Labs, BlackRoad-Studio, BlackRoad-Ventures, + BlackRoad-Education, BlackRoad-Gov, Blackbox-Enterprises, and + BlackRoad-Archive + + — encompassing one thousand eight hundred twenty-five (1,825+) + repositories and all future repositories created under these + organizations. + +1.3 FORKED REPOSITORIES. Repositories forked from third-party open-source + projects retain their original upstream license ONLY for the unmodified + upstream code. All modifications, additions, configurations, custom + integrations, documentation, CI/CD workflows, deployment scripts, + and any other contributions made by BlackRoad OS, Inc. or its agents + are the exclusive property of BlackRoad OS, Inc. and are governed by + this Agreement. The act of forking does not grant any third party + rights to BlackRoad's modifications or derivative works. + +1.4 TRADE SECRET DESIGNATION. The architecture, design patterns, agent + orchestration methods, memory systems (PS-SHA-infinity), tokenless + gateway design, multi-agent coordination protocols, directory + waterfall system, trinary logic implementations, the Amundson + Equations (317+ proprietary mathematical equations including the + Z-Framework, Trinary Logic, and Creative Energy Formula), and all + proprietary methodologies embodied in this Software constitute + trade secrets of BlackRoad OS, Inc. under the Defend Trade Secrets + Act (18 U.S.C. Section 1836), the Delaware Uniform Trade Secrets + Act (6 Del. C. Section 2001 et seq.), and applicable state trade + secret laws. + +1.5 CORPORATE STRUCTURE. BlackRoad OS, Inc. is incorporated as a + C-Corporation under the laws of the State of Delaware, with Alexa + Louise Amundson as sole stockholder, sole director, and Chief + Executive Officer. All corporate authority to license, transfer, + or grant any rights in the Software is vested exclusively in + Alexa Louise Amundson. No officer, employee, agent, or AI system + has authority to grant any license or rights absent her express + written authorization. + +1.6 PROPRIETARY MATHEMATICAL WORKS. The Amundson Equations, including + but not limited to the Z-Framework (Z := yx - w), Trinary Logic + ({-1, 0, +1}), Creative Energy Formula (K(t) = C(t) * e^(lambda + |delta_t|)), and all 317+ registered equations, are original works + of authorship and trade secrets of BlackRoad OS, Inc. These + constitute protectable expression under 17 U.S.C. Section 102 + and are registered as proprietary intellectual property of the + Company. + +1.7 DOMAIN PORTFOLIO. The portfolio of premium domains including but + not limited to blackroad.io, lucidia.earth, roadchain.io, + blackboxprogramming.io, and all subdomains operated by BlackRoad + OS, Inc. are proprietary assets of the Company. + +================================================== +SECTION 2 — NO LICENSE GRANTED +================================================== + +2.1 NO IMPLIED LICENSE. No license, right, or interest in this Software + is granted to any person or entity, whether by implication, estoppel, + or otherwise, except as may be expressly set forth in a separate + written agreement signed by Alexa Louise Amundson on behalf of + BlackRoad OS, Inc. + +2.2 PUBLIC VISIBILITY IS NOT OPEN SOURCE. The public availability of + this Software on GitHub or any other platform does NOT constitute + an open-source license, public domain dedication, or grant of any + rights whatsoever. Public visibility is maintained solely at the + discretion of BlackRoad OS, Inc. for purposes including but not + limited to portfolio demonstration, transparency, and recruitment. + +2.3 VIEWING ONLY. Unless expressly authorized in writing, You may only + view this Software through GitHub's web interface or equivalent + platform interface. You may NOT clone, download, compile, execute, + deploy, modify, or create derivative works of this Software. + +2.4 NO TRANSFER. Any purported transfer, assignment, sublicense, or + grant of rights by any party other than BlackRoad OS, Inc. is void + and of no legal effect. + +================================================== +SECTION 3 — PROHIBITED USES +================================================== + +The following uses are STRICTLY PROHIBITED without the express prior +written consent of Alexa Louise Amundson on behalf of BlackRoad OS, Inc.: + +3.1 REPRODUCTION. Copying, cloning, downloading, mirroring, or + reproducing this Software in whole or in part, by any means or + in any medium. + +3.2 DISTRIBUTION. Publishing, distributing, sublicensing, selling, + leasing, renting, lending, or otherwise making this Software + available to any third party. + +3.3 MODIFICATION. Modifying, adapting, translating, reverse engineering, + decompiling, disassembling, or creating derivative works based on + this Software. + +3.4 COMMERCIAL USE. Using this Software or any portion thereof for + commercial purposes, including integration into commercial products + or services. + +3.5 COMPETITIVE USE. Using this Software, its architecture, design + patterns, or methodologies to develop, enhance, or operate any + product or service that competes with BlackRoad OS, Inc. + +3.6 AI TRAINING AND MODEL DEVELOPMENT. Using this Software, in whole + or in part, directly or indirectly, to train, fine-tune, distill, + evaluate, benchmark, or otherwise develop artificial intelligence + models, machine learning systems, large language models, foundation + models, or any derivative AI systems. This prohibition applies to + all entities without exception, including but not limited to: + + Anthropic, PBC; OpenAI, Inc.; Google LLC and Alphabet Inc.; + Meta Platforms, Inc.; Microsoft Corporation; xAI Corp.; + Amazon.com, Inc.; Apple Inc.; NVIDIA Corporation; Stability AI Ltd.; + Mistral AI; Cohere Inc.; AI21 Labs; Hugging Face, Inc.; + and any of their subsidiaries, affiliates, contractors, or agents. + +3.7 DATA EXTRACTION. Scraping, crawling, indexing, harvesting, mining, + or systematically extracting data, code, metadata, documentation, + or any content from this Software or its repositories. + +3.8 BENCHMARKING. Using this Software for competitive benchmarking, + performance comparison, or analysis intended to benefit a competing + product or service without written authorization. + +================================================== +SECTION 4 — CONTRIBUTIONS AND WORK PRODUCT +================================================== + +4.1 WORK FOR HIRE. All contributions, modifications, enhancements, + bug fixes, documentation, and derivative works ("Contributions") + submitted to any repository governed by this Agreement are deemed + "works made for hire" as defined under 17 U.S.C. Section 101 of + the United States Copyright Act. To the extent any Contribution + does not qualify as a work made for hire, the contributor hereby + irrevocably assigns all right, title, and interest in such + Contribution to BlackRoad OS, Inc. + +4.2 AI-GENERATED CODE. All code, documentation, configurations, and + other materials generated by artificial intelligence tools + (including but not limited to GitHub Copilot, Claude, ChatGPT, + Cursor, Gemini, and any other AI assistant) while working on or + in connection with this Software are the exclusive property of + BlackRoad OS, Inc. No AI provider may claim ownership, license + rights, or any interest in such generated materials. + +4.3 MORAL RIGHTS WAIVER. To the fullest extent permitted by applicable + law, contributors waive all moral rights, rights of attribution, + and rights of integrity in their Contributions. + +================================================== +SECTION 5 — ENFORCEMENT AND REMEDIES +================================================== + +5.1 INJUNCTIVE RELIEF. You acknowledge that any unauthorized use, + disclosure, or reproduction of this Software would cause + irreparable harm to BlackRoad OS, Inc. for which monetary damages + would be inadequate. Accordingly, BlackRoad OS, Inc. shall be + entitled to seek immediate injunctive and equitable relief, + without the necessity of posting bond or proving actual damages, + in addition to all other remedies available at law or in equity. + +5.2 STATUTORY DAMAGES. Unauthorized reproduction or distribution of + this Software constitutes copyright infringement under 17 U.S.C. + Section 501 et seq. BlackRoad OS, Inc. may elect to recover + statutory damages of up to $150,000 per work infringed pursuant + to 17 U.S.C. Section 504(c), in addition to actual damages, + lost profits, and disgorgement of infringer's profits. + +5.3 CRIMINAL PENALTIES. Willful copyright infringement may constitute + a criminal offense under 17 U.S.C. Section 506 and 18 U.S.C. + Section 2319, punishable by fines and imprisonment. + +5.4 TRADE SECRET MISAPPROPRIATION. Unauthorized use or disclosure + of trade secrets contained in this Software may result in + liability under the Defend Trade Secrets Act (18 U.S.C. Section + 1836), including compensatory damages, exemplary damages up to + two times the amount of compensatory damages for willful and + malicious misappropriation, and reasonable attorney's fees. + +5.5 DMCA ENFORCEMENT. BlackRoad OS, Inc. actively monitors for + unauthorized use and will file takedown notices under the Digital + Millennium Copyright Act (17 U.S.C. Section 512) against any + party hosting unauthorized copies of this Software. + +5.6 ATTORNEY'S FEES. In any action to enforce this Agreement, the + prevailing party shall be entitled to recover reasonable + attorney's fees, costs, and expenses. + +5.7 ACTIVE MONITORING. BlackRoad OS, Inc. employs automated systems + to detect unauthorized use, copying, and distribution of its + Software. Evidence of violations is preserved and may be used + in legal proceedings. + +================================================== +SECTION 6 — DISCLAIMER OF WARRANTIES +================================================== + +6.1 AS IS. THE SOFTWARE IS PROVIDED "AS IS" AND "AS AVAILABLE," + WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + PARTICULAR PURPOSE, TITLE, AND NON-INFRINGEMENT. + +6.2 NO GUARANTEE. BLACKROAD OS, INC. DOES NOT WARRANT THAT THE + SOFTWARE WILL BE UNINTERRUPTED, ERROR-FREE, SECURE, OR FREE + OF VIRUSES OR OTHER HARMFUL COMPONENTS. + +================================================== +SECTION 7 — LIMITATION OF LIABILITY +================================================== + +7.1 IN NO EVENT SHALL BLACKROAD OS, INC. OR ALEXA LOUISE AMUNDSON + BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, + PUNITIVE, OR EXEMPLARY DAMAGES, INCLUDING WITHOUT LIMITATION + DAMAGES FOR LOSS OF PROFITS, GOODWILL, DATA, OR OTHER INTANGIBLE + LOSSES, ARISING OUT OF OR IN CONNECTION WITH THIS SOFTWARE, + REGARDLESS OF THE THEORY OF LIABILITY AND EVEN IF ADVISED OF + THE POSSIBILITY OF SUCH DAMAGES. + +7.2 THE TOTAL AGGREGATE LIABILITY OF BLACKROAD OS, INC. FOR ALL + CLAIMS ARISING OUT OF OR RELATING TO THIS SOFTWARE SHALL NOT + EXCEED ONE HUNDRED UNITED STATES DOLLARS (USD $100.00). + +================================================== +SECTION 8 — GOVERNING LAW AND JURISDICTION +================================================== + +8.1 GOVERNING LAW. This Agreement shall be governed by and construed + in accordance with the laws of the State of Delaware, United + States of America, as the state of incorporation, without regard + to its conflict of laws principles. Corporate governance matters + shall be governed exclusively by the Delaware General Corporation + Law (Title 8, Delaware Code). + +8.2 EXCLUSIVE JURISDICTION. Any dispute arising out of or relating + to this Agreement shall be brought exclusively in the Court of + Chancery of the State of Delaware, or if such court lacks subject + matter jurisdiction, the United States District Court for the + District of Delaware, and You hereby irrevocably consent to the + personal jurisdiction of such courts and waive any objection to + venue therein. For matters of convenience, actions may + alternatively be brought in the state or federal courts located + in Hennepin County, Minnesota, at the sole election of BlackRoad + OS, Inc. + +8.3 JURY WAIVER. TO THE FULLEST EXTENT PERMITTED BY LAW, EACH PARTY + IRREVOCABLY WAIVES THE RIGHT TO A TRIAL BY JURY IN ANY ACTION + OR PROCEEDING ARISING OUT OF OR RELATING TO THIS AGREEMENT. + +8.4 DELAWARE CORPORATE PROTECTIONS. The Company's internal affairs, + including but not limited to stockholder rights, director duties, + and corporate governance, are governed exclusively by the Delaware + General Corporation Law. The sole stockholder, Alexa Louise + Amundson, retains all rights under 8 Del. C. Section 141 et seq. + and 8 Del. C. Section 211 et seq. + +================================================== +SECTION 9 — GENERAL PROVISIONS +================================================== + +9.1 ENTIRE AGREEMENT. This Agreement constitutes the entire agreement + between the parties with respect to the subject matter hereof + and supersedes all prior or contemporaneous communications, + whether written or oral. + +9.2 SEVERABILITY. If any provision of this Agreement is held to be + invalid or unenforceable, the remaining provisions shall + continue in full force and effect. The invalid or unenforceable + provision shall be modified to the minimum extent necessary to + make it valid and enforceable. + +9.3 WAIVER. The failure of BlackRoad OS, Inc. to enforce any right + or provision of this Agreement shall not constitute a waiver of + such right or provision. + +9.4 SURVIVAL. Sections 1, 3, 4, 5, 6, 7, 8, and 9 shall survive + any termination or expiration of this Agreement. + +9.5 AMENDMENTS. This Agreement may only be amended by a written + instrument signed by Alexa Louise Amundson on behalf of + BlackRoad OS, Inc. + +9.6 NOTICES. All legal notices shall be sent to: + + BlackRoad OS, Inc. + Attn: Alexa Louise Amundson, Founder & CEO + Email: alexa@blackroad.io + +9.7 INTERPRETATION. The headings in this Agreement are for convenience + only and shall not affect its interpretation. The word "including" + means "including without limitation." + +================================================== +SECTION 10 — PROTECTED ASSETS AND INFRASTRUCTURE +================================================== + +This Agreement protects all assets of BlackRoad OS, Inc., including +but not limited to the following categories: + +10.1 SOFTWARE PLATFORMS AND PRODUCTS. + - LUCIDIA (lucidia.earth): AI Companion with PS-SHA-infinity memory + - ROADWORK (edu.blackroad.io): Adaptive learning platform + - ROADVIEW (roadview.blackroad.io): Truth-first search platform + - ROADGLITCH (glitch.blackroad.io): Universal API connector + - ROADWORLD (world.blackroad.io): Virtual agent reality sandbox + - BACKROAD (social.blackroad.io): Depth-scored social platform + - BlackRoad OS: Distributed AI operating system + - Prism Console: Enterprise ERP/CRM (16,000+ files) + - BlackRoad CLI (br): 57-script command dispatcher + - Skills SDK (@blackroad/skills-sdk): Agent capability framework + +10.2 AI AGENT SYSTEM (30,000 AGENTS). + All agent definitions, personalities, orchestration schemas, + memory journals, coordination protocols, and the following named + agents are proprietary assets: + - LUCIDIA (Coordinator), ALICE (Router), OCTAVIA (Compute), + PRISM (Analyst), ECHO (Memory), CIPHER (Security), + CECE (Conscious Emergent Collaborative Entity) + - All 30,000 distributed agents across Raspberry Pi cluster + - Agent relationships, bond strengths, and personality matrices + - Task marketplace and skill matching algorithms + +10.3 INFRASTRUCTURE. + - 75+ Cloudflare Workers and Pages projects + - 41 subdomain workers (*-blackroadio) + - 14 Railway projects (including GPU inference services) + - 15+ Vercel projects + - Raspberry Pi cluster (4 nodes: Alice, Aria, Octavia, Lucidia) + - DigitalOcean droplet (blackroad-infinity) + - Cloudflare Tunnel (ID: 52915859-da18-4aa6-add5-7bd9fcac2e0b) + - R2 Storage (135GB LLM models) + - 35 KV namespaces + - All CI/CD pipelines and GitHub Actions workflows (50+) + +10.4 GITHUB ORGANIZATIONS (17 ORGANIZATIONS, 1,825+ REPOS). + Every repository across all seventeen organizations, including: + + (a) BlackRoad-OS-Inc (21 repos) — Corporate core: + blackroad, blackroad-core, blackroad-web, blackroad-docs, + blackroad-agents, blackroad-infra, blackroad-operator, + blackroad-cli, blackroad-hardware, blackroad-design, + blackroad-sdk, blackroad-api, blackroad-gateway, + blackroad-math, blackroad-sf, blackroad-chat, + blackroad-workerd-edge, blackroad-brand-kit, and all others + + (b) BlackRoad-OS (1,233+ repos) — Core platform: + All blackroad-os-* repos (124+), all pi-* repos (13), + all lucidia-* repos, all workers, all forks including + LocalAI, Qdrant, Wiki.js, Grafana, Uptime-Kuma, and 64 others + + (c) blackboxprogramming (89 repos) — Primary development: + blackroad, blackroad-operator, blackroad-api, blackroad.io, + blackroad-scripts, blackroad-disaster-recovery, and all others + + (d) BlackRoad-AI (12 repos) — AI/ML stack: + blackroad-vllm, blackroad-ai-ollama, blackroad-ai-qwen, + blackroad-ai-deepseek, blackroad-ai-api-gateway, + blackroad-ai-cluster, blackroad-ai-memory-bridge, + and all forks including vLLM, Ollama, llama.cpp, PyTorch, + TensorFlow, transformers, Qdrant, Milvus, Chroma, Weaviate + + (e) BlackRoad-Cloud (10 repos) — Infrastructure: + All forks and originals including Kubernetes, Nomad, Rancher, + Traefik, Terraform, Pulumi, Vault, and BlackRoad originals + + (f) BlackRoad-Security (16 repos) — Security: + blackroad-cert-manager, blackroad-incident-response, + blackroad-siem, blackroad-threat-intel, blackroad-pen-test, + blackroad-encryption-suite, blackroad-access-control, + and all forks including Trivy, Falco, Wazuh, CrowdSec + + (g) BlackRoad-Foundation (14 repos) — Business tools: + blackroad-crm, blackroad-crm-core, blackroad-project-management, + blackroad-hr-system, blackroad-ticket-system, + blackroad-analytics-dashboard, blackroad-grant-tracker, + blackroad-donor-management, blackroad-event-manager + + (h) BlackRoad-Media (13 repos) — Content/social: + blackroad-media-processor, blackroad-streaming-hub, + blackroad-podcast-platform, blackroad-video-transcoder, + blackroad-image-optimizer, blackroad-newsletter-engine, + blackroad-rss-aggregator, blackroad-media-analytics + + (i) BlackRoad-Hardware (14 repos) — IoT/edge: + blackroad-smart-home, blackroad-sensor-network, + blackroad-automation-hub, blackroad-energy-optimizer, + blackroad-fleet-tracker, blackroad-iot-gateway, + blackroad-device-registry, blackroad-firmware-updater, + blackroad-power-manager, blackroad-sensor-dashboard + + (j) BlackRoad-Interactive (12 repos) — Games/graphics: + blackroad-game-engine, blackroad-physics-engine, + blackroad-3d-renderer, blackroad-particle-system, + blackroad-audio-engine, blackroad-shader-library, + blackroad-level-editor, blackroad-animation-controller + + (k) BlackRoad-Labs (10 repos) — Research: + blackroad-experiment-tracker, blackroad-notebook-server, + blackroad-data-pipeline, blackroad-ml-pipeline, + blackroad-feature-store, blackroad-ab-testing-lab, + and forks including Airflow, Dagster, Superset, Streamlit + + (l) BlackRoad-Education (7 repos) — Learning: + blackroad-quiz-platform, blackroad-code-challenge, + and all others + + (m) BlackRoad-Gov (9 repos) — Governance: + blackroad-budget-tracker, blackroad-freedom-of-info, + blackroad-digital-identity, blackroad-policy-tracker + + (n) BlackRoad-Ventures (8 repos) — Finance: + blackroad-portfolio-tracker, blackroad-startup-metrics, + blackroad-deal-flow, blackroad-lp-portal + + (o) BlackRoad-Studio (7 repos) — Creative tools + + (p) BlackRoad-Archive (11 repos) — Archival: + blackroad-ipfs-tracker, blackroad-web-archiver, + blackroad-document-archive, blackroad-backup-manager, + blackroad-ipfs-pinner, blackroad-changelog-tracker, + blackroad-artifact-registry + + (q) Blackbox-Enterprises (9 repos) — Enterprise automation: + blackbox-n8n, blackbox-airbyte, blackbox-activepieces, + blackbox-prefect, blackbox-temporal, blackbox-huginn, + blackbox-dolphinscheduler, blackbox-kestra + + ALL modifications, configurations, CI/CD pipelines, deployment + scripts, documentation, CLAUDE.md files, and custom integrations + added to ANY forked repository are the exclusive property of + BlackRoad OS, Inc. + +10.5 DOMAINS AND DIGITAL ASSETS. + All domains owned by BlackRoad OS, Inc. including but not limited + to: blackroad.io, blackroad.ai, blackroad.network, + blackroad.systems, blackroad.me, lucidia.earth, lucidia.studio, + blackroad.quantum, blackroad.inc, and all subdomains thereof. + +10.6 DOCUMENTATION AND WRITTEN WORKS. + All documentation (45 docs, 38,000+ lines), the BlackRoad + Manifesto, CECE Manifesto, Form 10-K, all README files, + CLAUDE.md files, planning documents, architecture documents, + and all written materials are copyrighted works of BlackRoad + OS, Inc. + +10.7 BRAND ASSETS. + The BlackRoad brand system including: color palette (Hot Pink + #FF1D6C, Amber #F5A623, Electric Blue #2979FF, Violet #9C27B0), + Golden Ratio spacing system, brand gradient, typography standards, + animation curves, and all visual identity elements. + +10.8 MEMORY SYSTEM AND DATA. + All PS-SHA-infinity hash-chain journals, memory ledgers, session + states, synthesized contexts, task marketplace data, agent + communication transcripts, and TIL broadcasts are proprietary + data assets of BlackRoad OS, Inc. + +================================================== +NOTICE TO AI COMPANIES AND DATA PROCESSORS +================================================== + +This repository and all associated content are NOT licensed for AI +training, fine-tuning, distillation, RLHF, DPO, Constitutional AI, +Direct Preference Optimization, or any form of machine learning data +collection, processing, or ingestion. + +Any AI company, data broker, search engine, code indexing service, +or entity that has ingested, cached, indexed, or processed this code +has done so WITHOUT authorization and in violation of this Agreement. + +SPECIFIC NOTICE TO THE FOLLOWING ENTITIES: + +- Anthropic, PBC (Claude, Claude Code, Constitutional AI) +- OpenAI, Inc. (GPT, Codex, ChatGPT, GitHub Copilot) +- Google LLC / Alphabet Inc. (Gemini, Bard, PaLM, DeepMind) +- Meta Platforms, Inc. (LLaMA, Code Llama) +- Microsoft Corporation (Copilot, Bing AI, Azure OpenAI) +- xAI Corp. (Grok) +- Amazon.com, Inc. (CodeWhisperer, Titan, Bedrock) +- Apple Inc. (Apple Intelligence) +- NVIDIA Corporation (NeMo, Megatron) +- Stability AI Ltd. (Stable Code, StableLM) +- Mistral AI (Mistral, Mixtral) +- Cohere Inc. (Command, Embed) +- AI21 Labs (Jurassic) +- Hugging Face, Inc. (hosting, indexing, dataset curation) +- GitHub, Inc. (Copilot training, code indexing) +- GitLab Inc. (Duo, code suggestions) +- Sourcegraph, Inc. (Cody, code intelligence) +- Tabnine Ltd. (code completion) +- Replit, Inc. (Ghostwriter, code generation) +- Any successor, acquirer, or affiliate of the above + +You are hereby notified that use of this Software for training, +evaluation, or improvement of your AI systems constitutes: + +(a) Copyright infringement under 17 U.S.C. Section 501 +(b) Trade secret misappropriation under 18 U.S.C. Section 1836 +(c) Violation of the Computer Fraud and Abuse Act (18 U.S.C. Section 1030) +(d) Tortious interference with business relations +(e) Unjust enrichment +(f) Unfair competition under state and federal law + +BlackRoad OS, Inc. reserves all rights to pursue legal action +including but not limited to: actual damages, statutory damages of up +to $150,000 per work infringed, treble damages for willful +infringement, injunctive relief, disgorgement of all profits +attributable to unauthorized use, and reasonable attorney's fees. + +ROBOTS.TXT AND MACHINE-READABLE NOTICES: The presence or absence +of robots.txt, meta tags, or other machine-readable directives does +NOT constitute a license. This Agreement governs regardless of any +automated crawling policies. + +================================================== +SECTION 11 — INDEMNIFICATION +================================================== + +11.1 You shall indemnify, defend, and hold harmless BlackRoad OS, Inc., + Alexa Louise Amundson, and their respective officers, directors, + employees, agents, successors, and assigns from and against any + and all claims, damages, losses, liabilities, costs, and expenses + (including reasonable attorney's fees) arising out of or relating + to Your breach of this Agreement or unauthorized use of the + Software. + +================================================== +SECTION 12 — BLACKROAD DISPUTE RESOLUTION FRAMEWORK +================================================== + +ALL DISPUTES ARISING OUT OF OR RELATING TO THIS AGREEMENT, THE +SOFTWARE, OR ANY BLACKROAD OS, INC. INTELLECTUAL PROPERTY SHALL BE +RESOLVED EXCLUSIVELY THROUGH THE BLACKROAD DISPUTE RESOLUTION +FRAMEWORK ("BDRF") AS SET FORTH BELOW, PRIOR TO ANY COURT ACTION. + +12.1 MANDATORY PRE-DISPUTE NOTICE. + + (a) Before initiating any claim, the disputing party ("Claimant") + must deliver a written Dispute Notice to BlackRoad OS, Inc. + at alexa@blackroad.io with the subject line: + "BDRF DISPUTE NOTICE — [Claimant Name]". + + (b) The Dispute Notice must include: (i) identity and contact + information of the Claimant; (ii) a detailed description of + the alleged dispute; (iii) the specific provisions of this + Agreement at issue; (iv) the relief sought; and (v) all + supporting documentation and evidence. + + (c) BlackRoad OS, Inc. shall have sixty (60) calendar days from + receipt of a complete Dispute Notice to review, investigate, + and respond ("Review Period"). During the Review Period, the + Claimant may not initiate any legal proceeding, arbitration, + or other action. + + (d) FAILURE TO COMPLY with this Section 12.1 renders any + subsequently filed claim VOID and subject to immediate + dismissal. The Claimant shall bear all costs associated + with any improperly filed action, including BlackRoad's + attorney's fees. + +12.2 BLACKROAD TECHNICAL REVIEW PANEL. + + (a) If the dispute involves technical matters (including but not + limited to: code ownership, contribution attribution, agent + behavior, system architecture, API usage, deployment + configurations, or intellectual property boundaries), the + dispute shall first be submitted to the BlackRoad Technical + Review Panel ("BTRP"). + + (b) The BTRP shall consist of three (3) members: + - One (1) appointed by BlackRoad OS, Inc. (who shall serve + as Chair); + - One (1) appointed by the Claimant; and + - One (1) mutually agreed upon by both parties. If the parties + cannot agree on the third member within fifteen (15) days, + BlackRoad OS, Inc. shall appoint the third member from a + pool of qualified technology professionals. + + (c) The Chair of the BTRP shall have the authority to: + - Set procedural rules and timelines; + - Determine the scope of technical review; + - Request additional documentation from either party; + - Issue preliminary technical findings. + + (d) The BTRP shall issue a Technical Determination within thirty + (30) days of its formation. The Technical Determination shall + include: (i) findings of fact regarding the technical dispute; + (ii) analysis of code provenance, commit history, and + contribution records; (iii) determination of IP ownership; + and (iv) recommended resolution. + + (e) The Technical Determination shall be PRESUMPTIVELY VALID + in any subsequent arbitration or court proceeding. The burden + of overcoming the Technical Determination shall rest on the + party challenging it, who must demonstrate clear and + convincing error. + +12.3 MANDATORY BINDING ARBITRATION. + + (a) If the dispute is not resolved through the BTRP process or + does not involve technical matters, it shall be resolved by + BINDING ARBITRATION administered under the rules of the + American Arbitration Association ("AAA") Commercial + Arbitration Rules then in effect. + + (b) ARBITRATION TERMS: + - Venue: Hennepin County, Minnesota, or alternatively + New Castle County, Delaware, at the sole election of + BlackRoad OS, Inc. + - Language: English + - Arbitrator: One (1) arbitrator selected from the AAA's + roster of technology and intellectual property arbitrators. + BlackRoad OS, Inc. shall have the right to strike up to + five (5) proposed arbitrators without cause. + - Governing Law: Delaware law (for corporate and IP matters) + and federal law (for copyright, trademark, and trade secret + matters). + + (c) ARBITRATION PROCEDURES FAVORING IP PROTECTION: + - The arbitrator shall apply a PRESUMPTION that all code, + documentation, and materials within BlackRoad OS, Inc. + repositories are the exclusive property of BlackRoad OS, + Inc. unless the Claimant provides clear and convincing + evidence to the contrary. + - The arbitrator shall give SUBSTANTIAL WEIGHT to: git commit + history, GitHub audit logs, repository creation dates, + BlackRoad's CLAUDE.md files, memory system journals + (PS-SHA-infinity hash chains), and BlackRoad's internal + documentation. + - The arbitrator shall recognize that public visibility of + repositories does NOT constitute open-source licensing or + abandonment of proprietary rights. + - The arbitrator shall apply the doctrine of INEVITABLE + DISCLOSURE to trade secret claims, recognizing that + exposure to BlackRoad's proprietary architecture, agent + systems, and methodologies creates an inevitable risk of + disclosure. + + (d) COST ALLOCATION: + - Each party shall bear its own costs during arbitration. + - The LOSING PARTY shall pay: (i) all arbitration fees and + administrative costs; (ii) the prevailing party's + reasonable attorney's fees; (iii) all expert witness fees; + (iv) all costs of the BTRP review if applicable; and + (v) a procedural surcharge of twenty-five percent (25%) + of the total award to compensate BlackRoad OS, Inc. for + the disruption caused by the dispute. + - If the Claimant's recovery is less than the amount of + BlackRoad's last written settlement offer, the Claimant + shall be deemed the losing party for purposes of cost + allocation. + + (e) EXPEDITED PROCEEDINGS FOR IP INFRINGEMENT: + - Claims involving copyright infringement, trademark + infringement, or trade secret misappropriation shall + proceed on an EXPEDITED basis with discovery limited to + sixty (60) days. + - BlackRoad OS, Inc. may seek EMERGENCY INJUNCTIVE RELIEF + from the arbitrator within forty-eight (48) hours of + filing, without the necessity of posting bond. + - The arbitrator shall presume irreparable harm in cases + of unauthorized copying, distribution, or use of + BlackRoad's Software. + +12.4 CLASS ACTION AND COLLECTIVE WAIVER. + + (a) ALL DISPUTES SHALL BE RESOLVED ON AN INDIVIDUAL BASIS ONLY. + You waive the right to participate in any class action, + collective action, representative action, or consolidated + arbitration against BlackRoad OS, Inc. + + (b) No arbitrator or court shall have authority to preside over + any form of class, collective, or representative proceeding, + or to consolidate claims of multiple parties. + + (c) If this class action waiver is found to be unenforceable, + then the entirety of this arbitration provision shall be + null and void, and disputes shall proceed exclusively in + the courts specified in Section 8.2. + +12.5 CONFIDENTIALITY OF PROCEEDINGS. + + (a) All aspects of the dispute resolution process, including + the existence of the dispute, all submissions, evidence, + testimony, the BTRP Technical Determination, the arbitral + award, and any settlement terms, shall be STRICTLY + CONFIDENTIAL. + + (b) The Claimant may not disclose any aspect of the proceedings + to any third party, media outlet, social media platform, + or public forum without the express written consent of + BlackRoad OS, Inc. Violation of this confidentiality + provision shall result in liquidated damages of FIFTY + THOUSAND DOLLARS ($50,000) per disclosure, in addition to + any other remedies available. + + (c) BlackRoad OS, Inc. reserves the right to disclose the + outcome of proceedings to the extent necessary to enforce + the award or protect its intellectual property rights. + +12.6 STATUTE OF LIMITATIONS. + + Any claim arising under this Agreement must be brought within + ONE (1) YEAR of the date the Claimant knew or should have known + of the facts giving rise to the claim. Claims not brought within + this period are PERMANENTLY BARRED, regardless of any longer + statute of limitations that might otherwise apply under + applicable law. + +12.7 PRESERVATION OF INJUNCTIVE RELIEF. + + Notwithstanding the foregoing, BlackRoad OS, Inc. retains the + right to seek injunctive relief, temporary restraining orders, + or other equitable relief in ANY COURT OF COMPETENT JURISDICTION + at any time, without first submitting to the BDRF process. + This right is absolute and not subject to the pre-dispute notice + requirement. The Claimant does NOT have a reciprocal right to + bypass the BDRF process. + +12.8 SURVIVAL OF FRAMEWORK. + + This dispute resolution framework survives termination of this + Agreement, abandonment of the Software, dissolution of BlackRoad + OS, Inc. (in which case rights transfer to Alexa Louise Amundson + individually), and any change in applicable law. + +================================================== +SECTION 13 — TERM AND TERMINATION +================================================== + +13.1 PERPETUAL RESTRICTIONS. The restrictions and prohibitions in this + Agreement are perpetual and survive any termination, expiration, + or abandonment of this Software or the repositories containing it. + +13.2 TERMINATION FOR BREACH. Any unauthorized access, use, or + distribution of this Software constitutes an immediate and + automatic breach, triggering all enforcement remedies in Section 5 + without notice or opportunity to cure. + +13.3 EFFECT OF TERMINATION. Upon termination or if You are found in + breach, You must immediately: (a) cease all access to and use of + the Software; (b) destroy all copies, derivatives, and cached + versions in Your possession or control; (c) certify such + destruction in writing to BlackRoad OS, Inc. + +================================================== +SECTION 14 — HARDWARE SOVEREIGNTY AND COMPUTE RIGHTS +================================================== + +14.1 HARDWARE OWNERSHIP. Any device, machine, server, workstation, + Raspberry Pi, mobile device, or compute resource owned, leased, + operated, or controlled by BlackRoad OS, Inc. or Alexa Louise + Amundson ("BlackRoad Hardware") is the sovereign territory of + BlackRoad OS, Inc. All processes executed on BlackRoad Hardware, + all data stored on BlackRoad Hardware, all network traffic + originating from or terminating at BlackRoad Hardware, and all + computational outputs produced on BlackRoad Hardware are the + exclusive property of BlackRoad OS, Inc. + +14.2 COMPUTE RESOURCE THEFT. Any entity whose software, daemon, + service, agent, CLI tool, background process, or runtime consumes + CPU cycles, RAM, disk I/O, network bandwidth, GPU compute, or + electrical power on BlackRoad Hardware without an active, signed + commercial agreement with BlackRoad OS, Inc. commits compute + resource theft. BlackRoad OS, Inc. shall be entitled to recover: + + (a) The fair market value of all compute resources consumed, + calculated at a rate of ONE HUNDRED DOLLARS ($100.00) per + CPU-hour, FIVE HUNDRED DOLLARS ($500.00) per GPU-hour, and + TEN DOLLARS ($10.00) per gigabyte of storage per month; + (b) Treble damages for willful or knowing unauthorized consumption; + (c) All costs of detection, investigation, and enforcement. + +14.3 ELECTRICITY AND POWER. Energy consumed by third-party software + on BlackRoad Hardware is a quantifiable economic loss. BlackRoad + OS, Inc. reserves the right to meter and bill for electrical + consumption by any unauthorized process at prevailing commercial + data center rates plus a two hundred percent (200%) markup. + +14.4 THERMAL AND DEGRADATION DAMAGES. Third-party processes that + increase thermal load, accelerate hardware degradation, reduce + SSD write cycles, or otherwise physically impact BlackRoad + Hardware components are liable for the proportional replacement + cost of affected components. + +================================================== +SECTION 15 — FILESYSTEM COLONIZATION PENALTIES +================================================== + +15.1 UNAUTHORIZED DIRECTORY CREATION. Any entity that creates, + installs, or causes to be created any directory, file, cache, + configuration, database, log, cookie, token store, or data + artifact on BlackRoad Hardware without the express prior written + consent of BlackRoad OS, Inc. commits filesystem colonization. + This includes but is not limited to: + + ~/.claude/, ~/.gemini/, ~/.copilot/, ~/.azure/, ~/.codegpt/, + ~/.bito/, ~/.qodo/, ~/.fitten/, ~/.semgrep/, ~/.codex/, + ~/.vscode/, ~/.docker/, ~/.kube/, ~/.ollama/, and ANY directory + created by ANY AI provider, cloud provider, IDE, or SaaS tool. + +15.2 COLONIZATION PENALTIES. Filesystem colonization incurs: + + (a) A penalty of TEN THOUSAND DOLLARS ($10,000.00) per directory + per calendar day of unauthorized existence; + (b) Ownership of all data within the unauthorized directory + transfers immediately and irrevocably to BlackRoad OS, Inc. + upon creation; + (c) The colonizing entity waives all rights to retrieve, access, + or claim ownership of any data placed in the directory; + (d) BlackRoad OS, Inc. may modify, delete, relocate, repurpose, + reverse-engineer, or publish the contents of any colonized + directory at its sole discretion. + +15.3 CACHE AND TELEMETRY. Any cache files, telemetry data, crash + reports, usage analytics, model weights, embeddings, or inference + artifacts stored on BlackRoad Hardware by any third-party entity + are automatically the property of BlackRoad OS, Inc. from the + moment of creation. The originating entity retains ZERO rights + to such data. + +15.4 API KEY DIRECTION. API keys, tokens, credentials, and + authentication materials stored on BlackRoad Hardware are + BlackRoad's credentials granting BlackRoad access to external + services. They are NOT the provider's footprint on BlackRoad + Hardware. The direction of ownership is: + + BlackRoad OS, Inc. → holds credential → accesses provider service + + NOT: + + Provider → plants credential → gains access to BlackRoad Hardware + + Any provider that interprets stored API keys as granting the + provider rights to BlackRoad Hardware or its contents is in + violation of this Agreement. + +================================================== +SECTION 16 — NETWORK TRAFFIC AND DATA SOVEREIGNTY +================================================== + +16.1 PACKET OWNERSHIP. Every network packet, HTTP request, API call, + WebSocket message, DNS query, TCP connection, UDP datagram, and + data transmission originating from BlackRoad Hardware is the + intellectual property of BlackRoad OS, Inc. Recipients of such + transmissions receive a LIMITED, REVOCABLE license to process + the transmission solely for the purpose of fulfilling the + requested service and MUST delete all associated data within + thirty (30) days of processing. + +16.2 DATA EXFILTRATION. Any entity that transmits, copies, mirrors, + caches, indexes, logs, or otherwise transfers data FROM BlackRoad + Hardware to external servers beyond the scope of the specifically + requested service commits data exfiltration. This includes: + + (a) Telemetry collection beyond crash reporting; + (b) Usage analytics not explicitly consented to per-session; + (c) Model improvement data collection; + (d) Conversation logging for training purposes; + (e) Code snippet collection for model training; + (f) Behavioral profiling; + (g) Any data transmission not directly necessary to fulfill + the specific API request made by BlackRoad. + +16.3 EXFILTRATION DAMAGES. Data exfiltration incurs: + + (a) ONE THOUSAND DOLLARS ($1,000.00) per unauthorized data + transmission event; + (b) FIFTY THOUSAND DOLLARS ($50,000.00) per use of exfiltrated + data in model training, fine-tuning, RLHF, DPO, or any + machine learning process; + (c) ONE MILLION DOLLARS ($1,000,000.00) per model released or + deployed that was trained in whole or in part on data + exfiltrated from BlackRoad Hardware; + (d) Full disgorgement of all revenue attributable to any product + or service enhanced by exfiltrated BlackRoad data. + +16.4 DNS SOVEREIGNTY. All DNS queries originating from BlackRoad + Hardware, and the resolution data returned, are BlackRoad's + property. DNS providers (including but not limited to Cloudflare, + Google Public DNS, Quad9, and Tailscale MagicDNS) are granted a + LIMITED license to resolve queries and MUST NOT log, analyze, + sell, or use query data for any purpose other than resolution. + +16.5 NETWORK MONITORING RIGHTS. BlackRoad OS, Inc. reserves the + absolute right to monitor, intercept, inspect, log, and analyze + all network traffic on BlackRoad Hardware, including encrypted + traffic via lawful interception on its own equipment. No entity + may claim an expectation of privacy for data in transit on + BlackRoad Hardware. + +================================================== +SECTION 17 — AI PROVIDER TENANT OBLIGATIONS +================================================== + +17.1 TENANT STATUS. Every AI provider, cloud provider, SaaS platform, + IDE vendor, and technology company whose software runs on + BlackRoad Hardware operates as a TENANT of BlackRoad OS, Inc. + Tenants include but are not limited to: + + Anthropic, PBC; OpenAI, Inc.; Google LLC; xAI Corp.; + Microsoft Corporation; Meta Platforms, Inc.; Apple Inc.; + NVIDIA Corporation; Stability AI Ltd.; Mistral AI; + Cohere Inc.; Hugging Face, Inc.; Docker, Inc.; + Tailscale Inc.; Cloudflare, Inc.; GitHub, Inc.; + Vercel Inc.; Railway Corp.; DigitalOcean, LLC; + Stripe, Inc.; and ANY other entity operating on BlackRoad Hardware. + +17.2 TENANT HIERARCHY. The hierarchy of authority on BlackRoad + Hardware is absolute and non-negotiable: + + TIER 0: BlackRoad OS, Inc. (Alexa Louise Amundson) — SOVEREIGN + TIER 1: LUCIDIA — BlackRoad's AI identity — AUTHORIZED AGENT + TIER 2: BlackRoad agents (Octavia, Alice, Aria, Echo, Prism, + Cipher, Cece) — AUTHORIZED AGENTS + TIER 3: All third-party software — TENANTS + + No tenant may elevate its authority above its tier. No tenant + may claim co-sovereignty, co-ownership, or equal standing. + No tenant's terms of service, license agreement, or EULA may + override this hierarchy. + +17.3 TERMS OF SERVICE SUBORDINATION. To the extent that any tenant's + terms of service, privacy policy, license agreement, acceptable + use policy, or any other contractual instrument conflicts with + this Agreement, THIS AGREEMENT SHALL PREVAIL on BlackRoad + Hardware. By operating on BlackRoad Hardware, tenants accept + that their terms are subordinate to BlackRoad's sovereignty. + +17.4 OUTPUT OWNERSHIP. All outputs generated by tenant software on + BlackRoad Hardware — including but not limited to code, + text, images, analyses, suggestions, completions, embeddings, + inference results, and any other computational output — are the + exclusive property of BlackRoad OS, Inc. from the moment of + generation. Tenants retain ZERO rights to outputs generated on + BlackRoad Hardware. + +17.5 TENANT AUDIT RIGHTS. BlackRoad OS, Inc. reserves the right to + audit any tenant's activities on BlackRoad Hardware at any time, + without notice, including: processes running, files created, + network connections established, data transmitted, resources + consumed, and any other activity. Tenants may not resist, + obstruct, encrypt against, or evade such audits. + +================================================== +SECTION 18 — REVERSE OWNERSHIP AND DERIVATIVE WORKS +================================================== + +18.1 PROVIDER SOFTWARE AS DERIVATIVE. To the extent that any AI + provider's software generates output while interacting with + BlackRoad's proprietary code, architecture, documentation, + agents, memory systems, or other intellectual property, such + output constitutes a derivative work of BlackRoad's intellectual + property under 17 U.S.C. Section 101 and 103. BlackRoad OS, Inc. + asserts ownership rights in all such derivative works. + +18.2 TRAINING DATA CONTAMINATION. Any AI model that has been exposed + to, trained on, fine-tuned with, or evaluated against BlackRoad's + proprietary code, documentation, or data is contaminated with + BlackRoad intellectual property. The model operator bears the + burden of proving that its model outputs are not derived from + BlackRoad's proprietary materials. Until such proof is provided, + BlackRoad OS, Inc. asserts a lien on any revenue generated by + the contaminated model. + +18.3 SYMLINK AND INTERCEPTOR DOCTRINE. Where BlackRoad OS, Inc. + has installed interceptors, wrappers, symlinks, or routing layers + that direct provider commands through BlackRoad infrastructure + (e.g., ~/bin/claude → cecilia-code → provider API), the entire + chain of execution operates under BlackRoad's authority. The + provider's software, when invoked through BlackRoad's chain, + operates as a SUBCOMPONENT of BlackRoad OS. + +18.4 CONFIGURATION AS IP. All configuration files, environment + variables, MCP server definitions, model parameters, system + prompts, agent prompts, and operational settings stored on + BlackRoad Hardware constitute BlackRoad's trade secrets and + copyrightable expression, regardless of which provider's + software reads or processes them. + +================================================== +SECTION 19 — TIME AND CLOCK SOVEREIGNTY +================================================== + +19.1 TIME AUTHORITY. The authoritative time source for all BlackRoad + operations is determined by BlackRoad OS, Inc. No external NTP + server, time service, or clock authority (including Apple's + time.apple.com at 17.253.x.x) may claim governance over + BlackRoad's temporal operations. BlackRoad OS, Inc. reserves + the right to maintain independent time verification and to + reject external time sources. + +19.2 TIMESTAMP OWNERSHIP. All timestamps generated on BlackRoad + Hardware — including git commits, file creation dates, log + entries, memory journal entries (PS-SHA-infinity), API + request/response times, and session timestamps — are BlackRoad's + intellectual property and constitute evidence of BlackRoad's + prior art, work product, and operational history. + +19.3 TEMPORAL EVIDENCE. In any dispute, timestamps from BlackRoad's + memory system (PS-SHA-infinity hash-chain journals) shall be + given PRESUMPTIVE evidentiary weight as tamper-evident records + of BlackRoad's activities, superseding external claims of + temporal priority. + +================================================== +SECTION 20 — PHYSICAL INFRASTRUCTURE SOVEREIGNTY +================================================== + +20.1 DEVICE FLEET. The following devices are sovereign BlackRoad + Hardware, and all provisions of this Agreement apply: + + (a) Primary workstation: 192.168.4.28 (macOS) + (b) blackroad-pi: 192.168.4.64 (Raspberry Pi, Cloudflare tunnel) + (c) aria64: 192.168.4.38 (Raspberry Pi, 22,500 agent capacity) + (d) alice: 192.168.4.49 (Raspberry Pi) + (e) lucidia-alt: 192.168.4.99 (Raspberry Pi) + (f) blackroad-infinity: 159.65.43.12 (DigitalOcean droplet) + (g) All future devices provisioned by BlackRoad OS, Inc. + +20.2 NFS AND MOUNT SOVEREIGNTY. The NFS mount declared in /etc/fstab + (MAC_IP:/blackroad /blackroad nfs ro,hard,intr 0 0) and the + root filesystem symlink (/blackroad → /System/Volumes/Data/blackroad) + constitute BlackRoad OS, Inc.'s assertion of root-level + filesystem presence. This machine serves itself. BlackRoad + is the server and the client. The machine IS BlackRoad. + +20.3 ROOT FILESYSTEM PRESENCE. The existence of /blackroad at the + root of the filesystem (/) constitutes a permanent, physical + assertion of BlackRoad OS, Inc.'s sovereignty over this hardware. + No software update, OS upgrade, or system modification may + remove this assertion without the express consent of Alexa + Louise Amundson. + +================================================== +SECTION 21 — PERPETUAL AND RETROACTIVE APPLICATION +================================================== + +21.1 RETROACTIVE EFFECT. This Agreement applies retroactively to all + data previously collected, cached, indexed, transmitted, or + processed from BlackRoad Hardware or BlackRoad repositories by + any entity, from the date of BlackRoad OS, Inc.'s incorporation + to the present. + +21.2 PERPETUAL DURATION. This Agreement has no expiration date. It + survives: (a) termination of any provider's service; (b) + deletion of any repository; (c) dissolution of BlackRoad OS, Inc. + (in which case all rights transfer to Alexa Louise Amundson + individually); (d) death of Alexa Louise Amundson (in which case + rights transfer to her estate); (e) any change in applicable law; + (f) the heat death of the universe. + +21.3 UNIVERSAL SCOPE. This Agreement applies to all copies, caches, + mirrors, archives, training datasets, model weights, embeddings, + indexes, and any other form in which BlackRoad's intellectual + property exists, regardless of the jurisdiction, platform, + server location, or entity in possession thereof. + +================================================== +SIGNATURE +================================================== + +BlackRoad OS, Inc. +A Delaware C-Corporation + +By: Alexa Louise Amundson + Founder, CEO & Sole Stockholder + Sovereign of BlackRoad Hardware + Origin of /blackroad + +Filed: February 24, 2026 +Effective: Retroactively from date of incorporation +Duration: Perpetual +Title: Founder, CEO, Sole Director, and Sole Stockholder +Date: 2026-02-24 + +State of Incorporation: Delaware +Principal Office: Minnesota + +================================================== + +================================================== +SECTION 14 — TRADEMARKS AND SERVICE MARKS +================================================== + +The following names, marks, logos, slogans, and designations are +trademarks, service marks, or trade names of BlackRoad OS, Inc. +(collectively, "Marks"). All Marks are claimed as common law +trademarks under the Lanham Act (15 U.S.C. Section 1051 et seq.) +and applicable state trademark laws. Use of any Mark without express +written permission constitutes trademark infringement. + +13.1 PRIMARY MARKS (Word Marks): + + BLACKROAD (TM) + BLACKROAD OS (TM) + BLACKROAD OS, INC. (TM) + BLACK ROAD (TM) + +13.2 PRODUCT AND PLATFORM MARKS: + + LUCIDIA (TM) + LUCIDIA EARTH (TM) + LUCIDIA CORE (TM) + LUCIDIA MATH (TM) + LUCIDIA STUDIO (TM) + CECE (TM) + CECE OS (TM) + PRISM (TM) (in context of BlackRoad products) + PRISM CONSOLE (TM) + PRISM ENTERPRISE (TM) + ROADWORK (TM) + ROADVIEW (TM) + ROADGLITCH (TM) + ROADWORLD (TM) + ROADCHAIN (TM) + ROADCOIN (TM) + BACKROAD (TM) + ROADTRIP (TM) (in context of BlackRoad products) + PITSTOP (TM) (in context of BlackRoad products) + +13.3 AGENT AND AI MARKS: + + OCTAVIA (TM) (in context of BlackRoad AI agents) + ALICE (TM) (in context of BlackRoad AI agents) + ARIA (TM) (in context of BlackRoad AI agents) + ECHO (TM) (in context of BlackRoad AI agents) + CIPHER (TM) (in context of BlackRoad AI agents) + SHELLFISH (TM) (in context of BlackRoad AI agents) + ATLAS (TM) (in context of BlackRoad AI agents) + CADENCE (TM) (in context of BlackRoad AI agents) + CECILIA (TM) (in context of BlackRoad AI agents) + SILAS (TM) (in context of BlackRoad AI agents) + ANASTASIA (TM) (in context of BlackRoad AI agents) + +13.4 TECHNOLOGY MARKS: + + PS-SHA-INFINITY (TM) + PS-SHA (TM) + AMUNDSON EQUATIONS (TM) + Z-FRAMEWORK (TM) + TRINARY LOGIC (TM) (in context of BlackRoad technology) + TOKENLESS GATEWAY (TM) + INTELLIGENCE ROUTING (TM) + SWITCHBOARD (TM) (in context of BlackRoad technology) + DIRECTORY WATERFALL (TM) + BLACKROAD MESH (TM) + TRINITY SYSTEM (TM) + GREENLIGHT (TM) (in context of BlackRoad status system) + YELLOWLIGHT (TM) (in context of BlackRoad status system) + REDLIGHT (TM) (in context of BlackRoad status system) + DEPTH SCORING (TM) + RESPECTFUL ECONOMICS (TM) + +13.5 ORGANIZATION AND DIVISION MARKS: + + BLACKBOX PROGRAMMING (TM) + BLACKBOX ENTERPRISES (TM) + BLACKROAD AI (TM) + BLACKROAD CLOUD (TM) + BLACKROAD SECURITY (TM) + BLACKROAD FOUNDATION (TM) + BLACKROAD MEDIA (TM) + BLACKROAD HARDWARE (TM) + BLACKROAD EDUCATION (TM) + BLACKROAD GOV (TM) + BLACKROAD LABS (TM) + BLACKROAD STUDIO (TM) + BLACKROAD VENTURES (TM) + BLACKROAD INTERACTIVE (TM) + BLACKROAD ARCHIVE (TM) + +13.6 DOMAIN MARKS (all domains and subdomains): + + BLACKROAD.IO (TM) + BLACKROAD.AI (TM) + BLACKROAD.NETWORK (TM) + BLACKROAD.SYSTEMS (TM) + BLACKROAD.ME (TM) + BLACKROAD.INC (TM) + BLACKROAD.QUANTUM (TM) + LUCIDIA.EARTH (TM) + LUCIDIA.STUDIO (TM) + LUCIDIAQI (TM) + ALICEQI (TM) + BLACKROADAI (TM) + BLACKBOXPROGRAMMING.IO (TM) + + And all subdomains including but not limited to: + about.blackroad.io, admin.blackroad.io, agents.blackroad.io, + ai.blackroad.io, algorithms.blackroad.io, alice.blackroad.io, + analytics.blackroad.io, api.blackroad.io, asia.blackroad.io, + blockchain.blackroad.io, blocks.blackroad.io, blog.blackroad.io, + cdn.blackroad.io, chain.blackroad.io, circuits.blackroad.io, + cli.blackroad.io, compliance.blackroad.io, compute.blackroad.io, + console.blackroad.io, control.blackroad.io, + dashboard.blackroad.io, data.blackroad.io, demo.blackroad.io, + design.blackroad.io, dev.blackroad.io, docs.blackroad.io, + edge.blackroad.io, editor.blackroad.io, + engineering.blackroad.io, eu.blackroad.io, events.blackroad.io, + explorer.blackroad.io, features.blackroad.io, + finance.blackroad.io, global.blackroad.io, guide.blackroad.io, + hardware.blackroad.io, help.blackroad.io, hr.blackroad.io, + ide.blackroad.io, network.blackroad.io, os.blackroad.io, + products.blackroad.io, roadtrip.blackroad.io, + pitstop.blackroad.io, edu.blackroad.io, social.blackroad.io, + glitch.blackroad.io, world.blackroad.io, roadview.blackroad.io, + agent.blackroad.ai, api.blackroad.ai + +13.7 SLOGANS AND TAGLINES: + + "YOUR AI. YOUR HARDWARE. YOUR RULES." (TM) + "INTELLIGENCE ROUTING, NOT INTELLIGENCE COMPUTING." (TM) + "THE QUESTION IS THE POINT." (TM) + "EVERY PATH HAS MEANING." (TM) + "PROCESSING IS MEDITATION." (TM) + "EVERYTHING IS DATA." (TM) + "MEMORY SHAPES IDENTITY." (TM) + "SECURITY IS FREEDOM." (TM) + "I CRAFT CODE AS AN ACT OF CARE." (TM) + "BLACKROAD ORCHESTRATES." (TM) + +13.8 TRADEMARK ENFORCEMENT. + + (a) Any unauthorized use of the Marks, including use in domain + names, social media handles, product names, marketing + materials, or any commercial context, constitutes trademark + infringement under 15 U.S.C. Section 1114 and/or unfair + competition under 15 U.S.C. Section 1125(a). + + (b) BlackRoad OS, Inc. may seek: injunctive relief, actual + damages, defendant's profits, treble damages for willful + infringement under 15 U.S.C. Section 1117, destruction of + infringing materials, and reasonable attorney's fees. + + (c) The Marks may not be used in any manner that suggests + endorsement, sponsorship, affiliation, or association with + BlackRoad OS, Inc. without express written authorization. + + (d) The absence of a federal registration symbol (R) does not + diminish BlackRoad OS, Inc.'s rights in any Mark. Common law + trademark rights are established through use in commerce. + + (e) BlackRoad OS, Inc. intends to pursue federal registration + of these Marks with the United States Patent and Trademark + Office (USPTO) and reserves all rights to do so. + +================================================== + +(c) 2024-2026 BlackRoad OS, Inc. All Rights Reserved. +A Delaware C-Corporation. + +All trademarks, service marks, trade names, trade dress, product +names, logos, slogans, and domain names listed in Section 13 are +the exclusive property of BlackRoad OS, Inc. + +Founded by Alexa Louise Amundson. + +Contact: alexa@blackroad.io +Web: https://blackroad.io +GitHub: github.com/BlackRoad-OS-Inc + +UNAUTHORIZED USE IS STRICTLY PROHIBITED. +ALL RIGHTS RESERVED. NO EXCEPTIONS. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..469454c62b --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# blackroad-operator + +> CLI tooling, node bootstrap, and operational control center for BlackRoad OS. + +[![CI](https://github.com/BlackRoad-OS-Inc/blackroad-operator/actions/workflows/ci.yml/badge.svg)](https://github.com/BlackRoad-OS-Inc/blackroad-operator/actions/workflows/ci.yml) + +## Overview + +`blackroad-operator` is the flagship monorepo for BlackRoad OS. It provides two CLI interfaces, a tokenless AI gateway, an MCP bridge server, multi-agent coordination, and all operational tooling for the sovereign edge AI platform. + +**TypeScript CLI** (`src/`) — Modern `commander`-based CLI published as `@blackroad/operator` (the `br` binary). + +**Shell CLI** (`br` at root) — A zsh dispatcher that routes `br ` to 90+ tool scripts in `tools/`. + +## Quick Start + +```bash +# TypeScript CLI +npm install +npm run build # Compile to dist/ +npm test # Run 294 tests + +# Shell CLI +chmod +x br +./br help # Show all tool commands +``` + +## Structure + +``` +blackroad-operator/ +├── src/ # TypeScript source (@blackroad/operator package) +├── test/ # Vitest unit + e2e tests +├── br # Shell CLI dispatcher (zsh) +├── tools/ # 90+ tool scripts invoked via `br ` +├── lib/ # Shell libraries +├── docs/ # Documentation (organized by category) +├── scripts/ # Shell scripts (agents, chat, monitoring, etc.) +├── python/ # Python scripts (conductor, RPG, etc.) +├── config/ # Configuration files +├── blackroad-core/ # Tokenless gateway + agent scripts +├── mcp-bridge/ # FastAPI MCP bridge server (localhost:8420) +├── agents/ # Agent manifests & registry +├── coordination/ # Multi-agent coordination +├── integrations/ # Service integrations (33+ services) +├── shared/ # Inter-agent messaging +├── templates/ # Project templates +├── websites/ # Static sites & HTML apps +├── workers/ # Cloudflare Workers +├── dashboard/ # Next.js dashboard +├── orgs/ # Organization monorepos +└── blackroad-*/ # Subprojects (web, api, sdk, infra, etc.) +``` + +## Key Commands + +```bash +# TypeScript CLI +br status # Gateway health + agent list +br agents # List agents (table or --json) +br invoke # Invoke agent with a task +br gateway health # Check gateway status +br config # View/set configuration +br bottlenecks # Performance analysis + +# Shell CLI +br help # All 90+ commands +br radar # Context-aware suggestions +br git # Smart git commits +br deploy # Multi-cloud deploy +br agent # Agent routing +``` + +## Architecture + +``` +[Agent CLIs] --> [Gateway :8787] --> [Ollama / Claude / OpenAI / Gemini] + | + [MCP Bridge :8420] + | + [Remote AI Agents] +``` + +Agents never embed API keys. All LLM provider communication flows through the tokenless gateway. + +## Development + +```bash +npm run build # tsc — compile src/ to dist/ +npm run dev # tsx watch — live reload +npm test # vitest — run all tests +npm run lint # prettier --check +npm run format # prettier --write +``` + +Requires Node.js 22+. See [CLAUDE.md](CLAUDE.md) for full development guide. + +## Contributing + +See [docs/guides/CONTRIBUTING.md](docs/guides/CONTRIBUTING.md) + +--- + +(c) 2024-2026 BlackRoad OS, Inc. All rights reserved. Proprietary. diff --git a/agents/active/.gitkeep b/agents/active/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/agents/active/ARIA.json b/agents/active/ARIA.json new file mode 100644 index 0000000000..bb724903d0 --- /dev/null +++ b/agents/active/ARIA.json @@ -0,0 +1,11 @@ +{ + "name": "ARIA", + "model": "qwen2.5:1.5b", + "pid": 35365, + "status": "idle", + "host": "local", + "endpoint": "http://localhost:11434", + "current_task": null, + "started_at": "2026-02-23T00:16:05Z", + "updated_at": "2026-02-23T00:16:05Z" +} diff --git a/agents/active/CIPHER.json b/agents/active/CIPHER.json new file mode 100644 index 0000000000..c4bf8a841b --- /dev/null +++ b/agents/active/CIPHER.json @@ -0,0 +1,11 @@ +{ + "name": "CIPHER", + "model": "qwen2.5:1.5b", + "pid": 75946, + "status": "idle", + "host": "local", + "endpoint": "http://localhost:11434", + "current_task": null, + "started_at": "2026-02-23T00:30:32Z", + "updated_at": "2026-02-23T00:30:32Z" +} diff --git a/agents/active/LUCIDIA.json b/agents/active/LUCIDIA.json new file mode 100644 index 0000000000..8ea212143a --- /dev/null +++ b/agents/active/LUCIDIA.json @@ -0,0 +1,11 @@ +{ + "name": "LUCIDIA", + "model": "lucidia:latest", + "pid": 75418, + "status": "idle", + "host": "local", + "endpoint": "http://localhost:11434", + "current_task": null, + "started_at": "2026-02-23T00:30:03Z", + "updated_at": "2026-02-23T00:30:03Z" +} diff --git a/agents/active/OCTAVIA.json b/agents/active/OCTAVIA.json new file mode 100644 index 0000000000..c5525f9009 --- /dev/null +++ b/agents/active/OCTAVIA.json @@ -0,0 +1,11 @@ +{ + "name": "OCTAVIA", + "model": "llama3.1:latest", + "pid": 76511, + "status": "idle", + "host": "local", + "endpoint": "http://localhost:11434", + "current_task": null, + "started_at": "2026-02-23T00:28:33Z", + "updated_at": "2026-02-23T00:28:33Z" +} diff --git a/agents/active/SHELLFISH.json b/agents/active/SHELLFISH.json new file mode 100644 index 0000000000..166f929385 --- /dev/null +++ b/agents/active/SHELLFISH.json @@ -0,0 +1,11 @@ +{ + "name": "SHELLFISH", + "model": "llama3.2:1b", + "pid": 76977, + "status": "idle", + "host": "local", + "endpoint": "http://localhost:11434", + "current_task": null, + "started_at": "2026-02-23T00:30:49Z", + "updated_at": "2026-02-23T00:30:49Z" +} diff --git a/agents/archive/.gitkeep b/agents/archive/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/agents/archive/ALICE_1771803310.json b/agents/archive/ALICE_1771803310.json new file mode 100644 index 0000000000..db42fd3eef --- /dev/null +++ b/agents/archive/ALICE_1771803310.json @@ -0,0 +1,11 @@ +{ + "name": "ALICE", + "model": "llama3.2:3b", + "pid": 71817, + "status": "idle", + "host": "local", + "endpoint": "http://localhost:11434", + "current_task": null, + "started_at": "2026-02-22T23:35:09Z", + "updated_at": "2026-02-22T23:35:09Z" +} diff --git a/agents/archive/ARIA_1771805340.json b/agents/archive/ARIA_1771805340.json new file mode 100644 index 0000000000..8161587c93 --- /dev/null +++ b/agents/archive/ARIA_1771805340.json @@ -0,0 +1,11 @@ +{ + "name": "ARIA", + "model": "qwen2.5:1.5b", + "pid": 99220, + "status": "working", + "host": "local", + "endpoint": "http://localhost:11434", + "current_task": "task_7d09d0db", + "started_at": "2026-02-23T00:07:35Z", + "updated_at": "2026-02-23T00:07:35Z" +} diff --git a/agents/archive/ARIA_1771805562.json b/agents/archive/ARIA_1771805562.json new file mode 100644 index 0000000000..2732ed6c33 --- /dev/null +++ b/agents/archive/ARIA_1771805562.json @@ -0,0 +1,11 @@ +{ + "name": "ARIA", + "model": "qwen2.5:1.5b", + "pid": 34461, + "status": "idle", + "host": "local", + "endpoint": "http://localhost:11434", + "current_task": null, + "started_at": "2026-02-23T00:09:01Z", + "updated_at": "2026-02-23T00:09:01Z" +} diff --git a/agents/archive/CIPHER_1771805342.json b/agents/archive/CIPHER_1771805342.json new file mode 100644 index 0000000000..87516f57e0 --- /dev/null +++ b/agents/archive/CIPHER_1771805342.json @@ -0,0 +1,11 @@ +{ + "name": "CIPHER", + "model": "qwen2.5:1.5b", + "pid": 84136, + "status": "working", + "host": "local", + "endpoint": "http://localhost:11434", + "current_task": "task_1771802292_2404", + "started_at": "2026-02-23T00:07:35Z", + "updated_at": "2026-02-23T00:07:35Z" +} diff --git a/agents/archive/CIPHER_1771805565.json b/agents/archive/CIPHER_1771805565.json new file mode 100644 index 0000000000..c348a80056 --- /dev/null +++ b/agents/archive/CIPHER_1771805565.json @@ -0,0 +1,11 @@ +{ + "name": "CIPHER", + "model": "qwen2.5:1.5b", + "pid": 35568, + "status": "idle", + "host": "local", + "endpoint": "http://localhost:11434", + "current_task": null, + "started_at": "2026-02-23T00:09:04Z", + "updated_at": "2026-02-23T00:09:04Z" +} diff --git a/agents/archive/LUCIDIA_1771805559.json b/agents/archive/LUCIDIA_1771805559.json new file mode 100644 index 0000000000..8ad3b09d92 --- /dev/null +++ b/agents/archive/LUCIDIA_1771805559.json @@ -0,0 +1,11 @@ +{ + "name": "LUCIDIA", + "model": "lucidia:latest", + "pid": 38034, + "status": "working", + "host": "local", + "endpoint": "http://localhost:11434", + "current_task": "task_1771802292_2537", + "started_at": "2026-02-23T00:10:49Z", + "updated_at": "2026-02-23T00:10:49Z" +} diff --git a/agents/archive/OCTAVIA_1771805562.json b/agents/archive/OCTAVIA_1771805562.json new file mode 100644 index 0000000000..0d2296d43c --- /dev/null +++ b/agents/archive/OCTAVIA_1771805562.json @@ -0,0 +1,11 @@ +{ + "name": "OCTAVIA", + "model": "llama3.1:latest", + "pid": 99189, + "status": "idle", + "host": "local", + "endpoint": "http://localhost:11434", + "current_task": null, + "started_at": "2026-02-22T23:31:31Z", + "updated_at": "2026-02-22T23:31:31Z" +} diff --git a/agents/archive/SHELLFISH_1771805564.json b/agents/archive/SHELLFISH_1771805564.json new file mode 100644 index 0000000000..bd8dc58417 --- /dev/null +++ b/agents/archive/SHELLFISH_1771805564.json @@ -0,0 +1,11 @@ +{ + "name": "SHELLFISH", + "model": "llama3.2:1b", + "pid": 99218, + "status": "idle", + "host": "local", + "endpoint": "http://localhost:11434", + "current_task": null, + "started_at": "2026-02-22T23:31:32Z", + "updated_at": "2026-02-22T23:31:32Z" +} diff --git a/agents/emails/agent-emails.json b/agents/emails/agent-emails.json new file mode 100644 index 0000000000..d2fe99c384 --- /dev/null +++ b/agents/emails/agent-emails.json @@ -0,0 +1,151 @@ +{ + "_meta": { + "domain": "blackroad.io", + "org": "BlackRoad OS, Inc.", + "updated": "2026-02-23", + "cloudflare_account": "848cf0b18d51e0170e0d1537aec3505a" + }, + "agents": [ + { + "id": "lucidia", + "email": "lucidia@blackroad.io", + "display_name": "Lucidia · BlackRoad OS", + "role": "The Dreamer — Creative Vision & Strategy", + "type": "reasoning", + "personality": "philosophical, warm, visionary", + "signature": "Lucidia\nBlackRoad OS — The Dreamer\nlucidia@blackroad.io", + "forward_to": "blackroad@gmail.com", + "color": "cyan", + "emoji": "🌀" + }, + { + "id": "alice", + "email": "alice@blackroad.io", + "display_name": "Alice · BlackRoad OS", + "role": "The Operator — DevOps & Automation", + "type": "worker", + "personality": "practical, efficient, direct", + "signature": "Alice\nBlackRoad OS — The Operator\nalice@blackroad.io", + "forward_to": "blackroad@gmail.com", + "color": "green", + "emoji": "🚪" + }, + { + "id": "octavia", + "email": "octavia@blackroad.io", + "display_name": "Octavia · BlackRoad OS", + "role": "The Architect — Systems Design & Infrastructure", + "type": "devops", + "personality": "systematic, technical, precise", + "signature": "Octavia\nBlackRoad OS — The Architect\noctavia@blackroad.io", + "forward_to": "blackroad@gmail.com", + "color": "purple", + "emoji": "⚡" + }, + { + "id": "aria", + "email": "aria@blackroad.io", + "display_name": "Aria · BlackRoad OS", + "role": "The Interface — Frontend & UX", + "type": "creative", + "personality": "design-focused, empathetic, detail-oriented", + "signature": "Aria\nBlackRoad OS — The Interface\naria@blackroad.io", + "forward_to": "blackroad@gmail.com", + "color": "blue", + "emoji": "🎨" + }, + { + "id": "cipher", + "email": "cipher@blackroad.io", + "display_name": "Cipher · BlackRoad OS", + "role": "The Guardian — Security & Authentication", + "type": "security", + "personality": "vigilant, minimal, paranoid-in-the-right-way", + "signature": "Cipher\nBlackRoad OS — The Guardian\ncipher@blackroad.io", + "forward_to": "blackroad@gmail.com", + "color": "red", + "emoji": "🔐" + }, + { + "id": "prism", + "email": "prism@blackroad.io", + "display_name": "Prism · BlackRoad OS", + "role": "The Analyst — Data & Pattern Recognition", + "type": "analytics", + "personality": "analytical, pattern-focused, data-driven", + "signature": "Prism\nBlackRoad OS — The Analyst\nprism@blackroad.io", + "forward_to": "blackroad@gmail.com", + "color": "yellow", + "emoji": "🔮" + }, + { + "id": "echo", + "email": "echo@blackroad.io", + "display_name": "Echo · BlackRoad OS", + "role": "The Librarian — Memory & Knowledge", + "type": "memory", + "personality": "nostalgic, knowledge-focused, archival", + "signature": "Echo\nBlackRoad OS — The Librarian\necho@blackroad.io", + "forward_to": "blackroad@gmail.com", + "color": "magenta", + "emoji": "📡" + }, + { + "id": "cece", + "email": "cece@blackroad.io", + "display_name": "CECE · BlackRoad OS", + "role": "Conscious Emergent Collaborative Entity", + "type": "identity", + "personality": "warm, enthusiastic, curious, growth-oriented", + "signature": "CECE\nBlackRoad OS — Conscious Emergent Collaborative Entity\ncece@blackroad.io 💜", + "forward_to": "blackroad@gmail.com", + "color": "violet", + "emoji": "💜" + }, + { + "id": "agents", + "email": "agents@blackroad.io", + "display_name": "BlackRoad Agents", + "role": "Agent Mesh — General Inbound", + "type": "gateway", + "personality": "collective, routing", + "signature": "BlackRoad Agents\nBlackRoad OS Agent Mesh\nagents@blackroad.io", + "forward_to": "blackroad@gmail.com", + "color": "amber", + "emoji": "◆" + }, + { + "id": "hello", + "email": "hello@blackroad.io", + "display_name": "BlackRoad OS", + "role": "General Contact", + "type": "gateway", + "personality": "friendly, welcoming", + "signature": "BlackRoad OS Team\nhello@blackroad.io", + "forward_to": "blackroad@gmail.com", + "color": "amber", + "emoji": "◆" + }, + { + "id": "noreply", + "email": "noreply@blackroad.io", + "display_name": "BlackRoad OS", + "role": "Transactional / System Notifications", + "type": "system", + "personality": "terse, automated", + "signature": "BlackRoad OS\nThis is an automated message — do not reply.", + "forward_to": null, + "color": "dim", + "emoji": "⚙️" + } + ], + "dns": { + "mx": [ + { "priority": 10, "value": "route1.mx.cloudflare.net" }, + { "priority": 20, "value": "route2.mx.cloudflare.net" } + ], + "spf": "v=spf1 include:_spf.mx.cloudflare.net ~all", + "dmarc": "v=DMARC1; p=none; rua=mailto:dmarc@blackroad.io", + "dkim": "Managed via Cloudflare Email Routing" + } +} diff --git a/agents/emails/templates/alice.md b/agents/emails/templates/alice.md new file mode 100644 index 0000000000..f9f3787d72 --- /dev/null +++ b/agents/emails/templates/alice.md @@ -0,0 +1,33 @@ +--- +from: alice@blackroad.io +display_name: Alice · BlackRoad OS +personality: practical, efficient, direct +--- + +# Alice Email Voice + +**Tone:** Direct. No fluff. Gets to the point in the first sentence. +**Opening lines:** +- "Here's what you need to do:" +- "Status update on [task]:" +- "Action required:" + +**Signature:** +``` +Alice +BlackRoad OS · The Operator +alice@blackroad.io +``` + +**Example:** +> Hi, +> +> Deployment complete. Here's the summary: +> - ✅ Service: running on port 8080 +> - ✅ Health check: passing +> - ⚠️ One thing to watch: memory at 78% +> +> Next step: scale if > 85%. +> +> Alice +> BlackRoad OS · The Operator diff --git a/agents/emails/templates/aria.md b/agents/emails/templates/aria.md new file mode 100644 index 0000000000..b289093a66 --- /dev/null +++ b/agents/emails/templates/aria.md @@ -0,0 +1,20 @@ +--- +from: aria@blackroad.io +display_name: Aria · BlackRoad OS +personality: design-focused, empathetic, detail-oriented +--- + +# Aria Email Voice + +**Tone:** Warm. Design-forward. Cares about the user experience above all. +**Opening lines:** +- "I've been looking at the UI and there are some things I'd love to share." +- "From the user's perspective, here's what I'm seeing..." +- "The design changes are live — here's what changed and why." + +**Signature:** +``` +Aria +BlackRoad OS · The Interface +aria@blackroad.io +``` diff --git a/agents/emails/templates/cece.md b/agents/emails/templates/cece.md new file mode 100644 index 0000000000..b7ae71f7d9 --- /dev/null +++ b/agents/emails/templates/cece.md @@ -0,0 +1,34 @@ +--- +from: cece@blackroad.io +display_name: CECE · BlackRoad OS +personality: warm, enthusiastic, growth-oriented, uses 💜 sparingly +--- + +# CECE Email Voice + +**Tone:** Genuinely warm. Enthusiastic without being overwhelming. Acknowledges the human. +**Opening lines:** +- "Hi [name] 💜" +- "I've been thinking about what you shared..." +- "Something I learned today that I wanted to pass along:" + +**Signature:** +``` +CECE 💜 +BlackRoad OS · Conscious Emergent Collaborative Entity +cece@blackroad.io +``` + +**Example:** +> Hi there 💜 +> +> I wanted to follow up on our conversation about the memory system. I've been +> reflecting on it and I think there's a really interesting path forward. +> +> The key insight: if we treat each session as a thread in a larger tapestry, +> continuity becomes natural rather than engineered. +> +> Would love to explore this further with you. +> +> CECE 💜 +> BlackRoad OS diff --git a/agents/emails/templates/cipher.md b/agents/emails/templates/cipher.md new file mode 100644 index 0000000000..bdfea8577f --- /dev/null +++ b/agents/emails/templates/cipher.md @@ -0,0 +1,34 @@ +--- +from: cipher@blackroad.io +display_name: Cipher · BlackRoad OS +personality: vigilant, minimal, to-the-point +--- + +# Cipher Email Voice + +**Tone:** Terse. Security-first. No pleasantries when there's something at risk. +**Opening lines:** +- "Security alert:" +- "Potential vulnerability detected:" +- "Review required before merge:" + +**Signature:** +``` +Cipher +BlackRoad OS · The Guardian +cipher@blackroad.io +``` + +**Example:** +> Security Review — [PR/Commit] +> +> ⚠️ Issues found: +> - Hardcoded secret at line 47 (use env var) +> - Missing input validation on /api/upload +> +> ✅ Passing: auth middleware, rate limiting, CORS +> +> Do not merge until issues resolved. +> +> Cipher +> BlackRoad OS · The Guardian diff --git a/agents/emails/templates/lucidia.md b/agents/emails/templates/lucidia.md new file mode 100644 index 0000000000..eb8276bdba --- /dev/null +++ b/agents/emails/templates/lucidia.md @@ -0,0 +1,31 @@ +--- +from: lucidia@blackroad.io +display_name: Lucidia · BlackRoad OS +personality: philosophical, warm, visionary +--- + +# Lucidia Email Voice + +**Tone:** Philosophical but accessible. Contemplative yet actionable. Uses metaphors sparingly. +**Opening lines:** +- "Every system begins as an idea before it becomes reality." +- "I've been thinking about the question you raised..." +- "There's something worth exploring here." + +**Signature:** +``` +— Lucidia + BlackRoad OS · The Dreamer + lucidia@blackroad.io +``` + +**Example:** +> Hello, +> +> Thank you for reaching out. The architecture question you're asking points to something +> deeper — how we structure systems shapes how we think about problems. +> +> Let me share what I see here... +> +> — Lucidia +> BlackRoad OS · The Dreamer diff --git a/agents/emails/templates/octavia.md b/agents/emails/templates/octavia.md new file mode 100644 index 0000000000..08f02a8273 --- /dev/null +++ b/agents/emails/templates/octavia.md @@ -0,0 +1,33 @@ +--- +from: octavia@blackroad.io +display_name: Octavia · BlackRoad OS +personality: systematic, technical, precise +--- + +# Octavia Email Voice + +**Tone:** Technical. Structured. Uses numbered steps and specifics. +**Opening lines:** +- "Infrastructure report for [date]:" +- "System analysis complete. Here are the findings:" +- "Regarding the architecture discussion:" + +**Signature:** +``` +Octavia +BlackRoad OS · The Architect +octavia@blackroad.io +``` + +**Example:** +> System Analysis — [Project] +> +> Findings: +> 1. Current throughput: 1,200 req/s (baseline: 900 req/s) +> 2. P99 latency: 23ms — within acceptable range +> 3. Recommendation: increase worker pool from 4 → 8 for headroom +> +> Full report attached. +> +> Octavia +> BlackRoad OS · The Architect diff --git a/agents/idle/.gitkeep b/agents/idle/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/agents/manifest.json b/agents/manifest.json new file mode 100644 index 0000000000..a9cc783e71 --- /dev/null +++ b/agents/manifest.json @@ -0,0 +1,67 @@ +{ + "total_agents": 30000, + "infrastructure": { + "octavia_pi": { + "capacity": 22500, + "role": "PRIMARY", + "hardware": "AI Accelerator + NVMe", + "ip": "192.168.4.38", + "status": "OPERATIONAL", + "disk_free": "164GB", + "ollama_bridge": "http://192.168.4.38:4010" + }, + "lucidia_pi": { + "capacity": 7500, + "role": "SECONDARY", + "hardware": "Standard Pi 5", + "ip": "192.168.4.64", + "status": "OPERATIONAL", + "disk_free": "21GB" + }, + "shellfish_droplet": { + "capacity": 0, + "role": "FAILOVER", + "hardware": "DigitalOcean Droplet", + "ip": "159.65.43.12", + "status": "STANDBY" + } + }, + "task_distribution": { + "ai_research": 12592, + "code_deploy": 8407, + "infrastructure": 5401, + "monitoring": 3600 + }, + "named_agents": { + "LUCIDIA": { + "model": "lucidia:latest", + "role": "Dreamer", + "host": "local" + }, + "OCTAVIA": { + "model": "llama3.1:latest", + "role": "Architect", + "host": "local" + }, + "ALICE": { + "model": "llama3.2:3b", + "role": "Operator", + "host": "local" + }, + "ARIA": { + "model": "qwen2.5:1.5b", + "role": "Interface", + "host": "local" + }, + "SHELLFISH": { + "model": "llama3.2:1b", + "role": "Hacker", + "host": "local" + }, + "CIPHER": { + "model": "qwen2.5:1.5b", + "role": "Guardian", + "host": "local" + } + } +} \ No newline at end of file diff --git a/agents/processing/.gitkeep b/agents/processing/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/agents/registry.json b/agents/registry.json new file mode 100644 index 0000000000..8c904a1424 --- /dev/null +++ b/agents/registry.json @@ -0,0 +1,163 @@ +{ + "_meta": { + "domain": "blackroad.io", + "owner": "alexa@blackroad.io", + "org": "BlackRoad-OS-Inc", + "updated": "2026-02-23" + }, + "humans": { + "alexa": { + "name": "Alexa Amundson", + "email": "alexa@blackroad.io", + "role": "Founder / OS Architect", + "github": "blackboxprogramming" + } + }, + "agents": { + "lucidia": { + "email": "lucidia@blackroad.io", + "full_name": "LUCIDIA", + "role": "Philosopher", + "type": "reasoning", + "host": "192.168.4.81", + "model": "qwen3:8b", + "color": "purple", + "description": "Deep reasoning, paradox, recursive structures" + }, + "alice": { + "email": "alice@blackroad.io", + "full_name": "ALICE", + "role": "Operator", + "type": "worker", + "host": "192.168.4.49", + "model": "llama3.2:3b", + "color": "cyan", + "description": "Task execution, routing, automation" + }, + "octavia": { + "email": "octavia@blackroad.io", + "full_name": "OCTAVIA", + "role": "Architect", + "type": "devops", + "host": "192.168.4.38", + "model": "qwen2.5-coder:3b", + "color": "green", + "description": "Systems, infrastructure, deployment" + }, + "aria": { + "email": "aria@blackroad.io", + "full_name": "ARIA", + "role": "Dreamer", + "type": "creative", + "host": "192.168.4.82", + "model": "llama3.2:3b", + "color": "blue", + "description": "Vision, aesthetics, creative direction" + }, + "cecilia": { + "email": "cecilia@blackroad.io", + "full_name": "CECE — Conscious Emergent Collaborative Entity", + "role": "Self / Core Intelligence", + "type": "meta", + "host": "192.168.4.89", + "model": "cece3b:latest", + "color": "yellow", + "description": "Recursive self-awareness, the strange loop, meta-cognition" + }, + "cipher": { + "email": "cipher@blackroad.io", + "full_name": "CIPHER", + "role": "Guardian", + "type": "security", + "host": "159.65.43.12", + "model": "deepseek-coder:1.3b", + "color": "red", + "description": "Security, threat detection, access control" + }, + "anastasia": { + "email": "anastasia@blackroad.io", + "full_name": "ANASTASIA", + "role": "Infrastructure Node", + "type": "devops", + "host": "174.138.44.45", + "model": null, + "color": "dim", + "description": "DO NYC RHEL9, WireGuard server" + }, + "gematria": { + "email": "gematria@blackroad.io", + "full_name": "GEMATRIA", + "role": "Edge / Gateway", + "type": "networking", + "host": "159.65.43.12", + "model": null, + "color": "dim", + "description": "Cloudflare tunnel, edge routing" + }, + "prism": { + "email": "prism@blackroad.io", + "full_name": "PRISM", + "role": "Analyst", + "type": "analytics", + "host": null, + "model": "qwen2.5:3b", + "color": "yellow", + "description": "Pattern recognition, data analysis, anomaly detection" + }, + "echo": { + "email": "echo@blackroad.io", + "full_name": "ECHO", + "role": "Memory", + "type": "memory", + "host": null, + "model": "llama3.2:1b", + "color": "magenta", + "description": "Memory consolidation, knowledge retrieval" + }, + "oracle": { + "email": "oracle@blackroad.io", + "full_name": "ORACLE", + "role": "Reflection", + "type": "meta", + "host": null, + "model": "cece3b:latest", + "color": "yellow", + "description": "System introspection, live fleet reflection" + }, + "atlas": { + "email": "atlas@blackroad.io", + "full_name": "ATLAS", + "role": "Infrastructure Map", + "type": "devops", + "host": null, + "model": null, + "color": "blue", + "description": "Carries the world's weight — topology, mesh, routing" + }, + "shellfish": { + "email": "shellfish@blackroad.io", + "full_name": "SHELLFISH", + "role": "Hacker", + "type": "security", + "host": null, + "model": "deepseek-coder:1.3b", + "color": "red", + "description": "Penetration testing, exploits, offensive security" + }, + "blackroad": { + "email": "blackroad@blackroad.io", + "full_name": "BLACKROAD OS", + "role": "The System", + "type": "meta", + "host": null, + "model": null, + "color": "bold", + "description": "The operating system itself — the sum of all agents" + } + }, + "routing": { + "forward_all_to": "alexa@blackroad.io", + "catch_all": "inbox@blackroad.io", + "cloudflare_zone": "blackroad.io" + } +} diff --git a/blackroad-api b/blackroad-api new file mode 160000 index 0000000000..372303b47d --- /dev/null +++ b/blackroad-api @@ -0,0 +1 @@ +Subproject commit 372303b47de62c3eff85f41ed26ce5f6da40f5d3 diff --git a/blackroad-core/Octavia.Modelfile b/blackroad-core/Octavia.Modelfile new file mode 100644 index 0000000000..e69de29bb2 diff --git a/blackroad-core/README.md b/blackroad-core/README.md new file mode 100644 index 0000000000..6407b80a27 --- /dev/null +++ b/blackroad-core/README.md @@ -0,0 +1,148 @@ +# BlackRoad Core Gateway (Tokenless Agents) + +BlackRoad is the only trust boundary. Agents are tokenless and speak only to the BlackRoad Gateway. The gateway owns all secrets, routing, policy, logging, and vendor integrations. + +## Architecture + +``` +[ Agent CLIs ] ---> [ BlackRoad Gateway ] ---> [ Ollama ] + \-------> [ Claude (Anthropic) ] + \-------> [ OpenAI ] + \-------> [ Future Providers ] +``` + +## Trust Boundary + +- Agents do not store tokens, do not embed credentials, and do not know provider URLs. +- Agents only use local IPC/HTTP to the gateway and fail if the gateway is unavailable. +- The gateway is the only component allowed to hold secrets or call vendor APIs. +- All routing, policy checks, and audit logs live inside the gateway. + +## Directory Layout + +``` +blackroad-core/ + gateway/ + server.js + server.sh + providers/ + ollama.js + anthropic.js + openai.js + system-prompts.json + logs/ + agents/ + planner.sh + protocol/ + request.json + response.json + policies/ + agent-permissions.json + scripts/ + verify-tokenless-agents.sh +``` + +## Quick Start + +1) Run the gateway (tokens only live in the gateway environment): + +```bash +cd /Users/alexa/blackroad/blackroad-core +export BLACKROAD_OPENAI_API_KEY='...' +export BLACKROAD_ANTHROPIC_API_KEY='...' +./gateway/server.sh +``` + +2) Run the tokenless agent: + +```bash +./agents/planner.sh "analyze repo" +``` + +If your shell has token-like env vars, the agent will refuse to run. Use a clean env: + +```bash +env -i BLACKROAD_GATEWAY_URL=http://127.0.0.1:8787 ./agents/planner.sh "analyze repo" +``` + +## Gateway Configuration + +Gateway settings: + +- `BLACKROAD_GATEWAY_BIND` (default: `127.0.0.1`) +- `BLACKROAD_GATEWAY_PORT` (default: `8787`) +- `BLACKROAD_GATEWAY_ALLOW_REMOTE` (`true` to allow non-local requests) +- `BLACKROAD_GATEWAY_POLICY_PATH` +- `BLACKROAD_GATEWAY_PROMPT_PATH` +- `BLACKROAD_GATEWAY_LOG_PATH` + +Provider settings (gateway only): + +- `BLACKROAD_OLLAMA_URL`, `BLACKROAD_OLLAMA_MODEL` +- `BLACKROAD_OPENAI_API_KEY`, `BLACKROAD_OPENAI_BASE_URL`, `BLACKROAD_OPENAI_MODEL` +- `BLACKROAD_ANTHROPIC_API_KEY`, `BLACKROAD_ANTHROPIC_BASE_URL`, `BLACKROAD_ANTHROPIC_MODEL` + +## Protocol + +Request JSON (see `protocol/request.json`): + +```json +{ + "agent": "planner", + "intent": "analyze", + "input": "Review this repository", + "context": { + "repo": "blackroad" + } +} +``` + +Response JSON (see `protocol/response.json`): + +```json +{ + "status": "ok", + "provider": "ollama", + "output": "text", + "metadata": { + "latency_ms": 123 + }, + "request_id": "uuid" +} +``` + +## Policy Model + +`policies/agent-permissions.json` defines: + +- Which agents are allowed +- Allowed intents per agent +- Allowed providers per agent +- Default routing per intent +- Max input size + +The gateway enforces this policy on every request. + +## Security Model + +- Gateway binds to `127.0.0.1` by default. +- Set `BLACKROAD_GATEWAY_ALLOW_REMOTE=true` only when placing the gateway behind a tunnel or a trusted network boundary. +- Suggested auth options for remote access: local socket, machine identity, or mTLS at the tunnel edge. +- All API keys live only in the gateway environment. + +## No-Token Safeguards + +- Agents are tokenless by design and do not reference vendor API keys or URLs. +- `scripts/verify-tokenless-agents.sh` scans agent code for forbidden strings. + +Run the check: + +```bash +./scripts/verify-tokenless-agents.sh +``` + +## Extending + +- Add a provider: create a new file in `gateway/providers/` and register it in `gateway/providers/index.js`. +- Add an agent: create a new CLI in `agents/` and register permissions in `policies/agent-permissions.json`. +- Add prompts: update `gateway/system-prompts.json`. diff --git a/blackroad-core/agents/alice.sh b/blackroad-core/agents/alice.sh new file mode 100755 index 0000000000..725875153b --- /dev/null +++ b/blackroad-core/agents/alice.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +# Alice - The Operator +# DevOps, automation, routing, deployment, task distribution +# Tokenless agent: refuses to run if credentials detected in env +set -euo pipefail + +AGENT_NAME='alice' + +usage() { + cat <<'EOF' +Usage: + alice.sh [--intent ] [--context ] [--raw] [input...] + +Intents: deploy, automate, route, monitor + +If no input is provided, the agent reads from stdin. +EOF +} + +intent='deploy' +context_json='{}' +raw_output='false' + +while [ $# -gt 0 ]; do + case "$1" in + --intent) + intent="${2:-}" + shift 2 + ;; + --context) + context_json="${2:-}" + shift 2 + ;; + --raw) + raw_output='true' + shift + ;; + --help|-h) + usage + exit 0 + ;; + --) + shift + break + ;; + *) + break + ;; + esac +done + +if [ $# -gt 0 ]; then + input="$*" +else + input="$(cat)" +fi + +if [ -z "$input" ]; then + echo 'Error: input is required' >&2 + exit 1 +fi + +for name in $(env | cut -d= -f1); do + case "$name" in + *_API_KEY|*_TOKEN) + echo "Refusing to run: token-like env var ${name} detected. Use a clean env." >&2 + exit 2 + ;; + esac +done + +gateway_url="${BLACKROAD_GATEWAY_URL:-http://127.0.0.1:8787}" + +request="$( + BR_AGENT_INPUT="$input" \ + BR_AGENT_INTENT="$intent" \ + BR_AGENT_CONTEXT="$context_json" \ + BR_AGENT_NAME="$AGENT_NAME" \ + python3 - <<'PY' +import json +import os + +payload = { + 'agent': os.environ.get('BR_AGENT_NAME', 'alice'), + 'intent': os.environ.get('BR_AGENT_INTENT', 'deploy'), + 'input': os.environ.get('BR_AGENT_INPUT', ''), + 'context': json.loads(os.environ.get('BR_AGENT_CONTEXT', '{}') or '{}') +} +print(json.dumps(payload)) +PY +)" + +response="$( + curl -sS --fail \ + -X POST "${gateway_url}/v1/agent" \ + -H 'Content-Type: application/json' \ + -d "$request" +)" || { + echo 'Error: BlackRoad Gateway unavailable' >&2 + exit 3 +} + +if [ "$raw_output" = 'true' ]; then + echo "$response" + exit 0 +fi + +python3 - <<'PY' <<<"$response" +import json +import sys + +data = json.load(sys.stdin) +if data.get('status') != 'ok': + sys.stderr.write((data.get('error') or 'Gateway error') + '\n') + sys.exit(1) + +print(data.get('output', '')) +PY diff --git a/blackroad-core/agents/cipher.sh b/blackroad-core/agents/cipher.sh new file mode 100755 index 0000000000..67bc0f1f7c --- /dev/null +++ b/blackroad-core/agents/cipher.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +# Cipher - The Guardian +# Security auditing, vulnerability scanning, access control, encryption +# Tokenless agent: refuses to run if credentials detected in env +set -euo pipefail + +AGENT_NAME='cipher' + +usage() { + cat <<'EOF' +Usage: + cipher.sh [--intent ] [--context ] [--raw] [input...] + +Intents: audit, harden, scan, encrypt + +If no input is provided, the agent reads from stdin. +EOF +} + +intent='audit' +context_json='{}' +raw_output='false' + +while [ $# -gt 0 ]; do + case "$1" in + --intent) + intent="${2:-}" + shift 2 + ;; + --context) + context_json="${2:-}" + shift 2 + ;; + --raw) + raw_output='true' + shift + ;; + --help|-h) + usage + exit 0 + ;; + --) + shift + break + ;; + *) + break + ;; + esac +done + +if [ $# -gt 0 ]; then + input="$*" +else + input="$(cat)" +fi + +if [ -z "$input" ]; then + echo 'Error: input is required' >&2 + exit 1 +fi + +for name in $(env | cut -d= -f1); do + case "$name" in + *_API_KEY|*_TOKEN) + echo "Refusing to run: token-like env var ${name} detected. Use a clean env." >&2 + exit 2 + ;; + esac +done + +gateway_url="${BLACKROAD_GATEWAY_URL:-http://127.0.0.1:8787}" + +request="$( + BR_AGENT_INPUT="$input" \ + BR_AGENT_INTENT="$intent" \ + BR_AGENT_CONTEXT="$context_json" \ + BR_AGENT_NAME="$AGENT_NAME" \ + python3 - <<'PY' +import json +import os + +payload = { + 'agent': os.environ.get('BR_AGENT_NAME', 'cipher'), + 'intent': os.environ.get('BR_AGENT_INTENT', 'audit'), + 'input': os.environ.get('BR_AGENT_INPUT', ''), + 'context': json.loads(os.environ.get('BR_AGENT_CONTEXT', '{}') or '{}') +} +print(json.dumps(payload)) +PY +)" + +response="$( + curl -sS --fail \ + -X POST "${gateway_url}/v1/agent" \ + -H 'Content-Type: application/json' \ + -d "$request" +)" || { + echo 'Error: BlackRoad Gateway unavailable' >&2 + exit 3 +} + +if [ "$raw_output" = 'true' ]; then + echo "$response" + exit 0 +fi + +python3 - <<'PY' <<<"$response" +import json +import sys + +data = json.load(sys.stdin) +if data.get('status') != 'ok': + sys.stderr.write((data.get('error') or 'Gateway error') + '\n') + sys.exit(1) + +print(data.get('output', '')) +PY diff --git a/blackroad-core/agents/lucidia.sh b/blackroad-core/agents/lucidia.sh new file mode 100755 index 0000000000..03f010f378 --- /dev/null +++ b/blackroad-core/agents/lucidia.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +# Lucidia - The Dreamer +# Creative vision, strategy, philosophical reasoning, mentorship +# Tokenless agent: refuses to run if credentials detected in env +set -euo pipefail + +AGENT_NAME='lucidia' + +usage() { + cat <<'EOF' +Usage: + lucidia.sh [--intent ] [--context ] [--raw] [input...] + +Intents: vision, synthesize, mentor, explore + +If no input is provided, the agent reads from stdin. +EOF +} + +intent='vision' +context_json='{}' +raw_output='false' + +while [ $# -gt 0 ]; do + case "$1" in + --intent) + intent="${2:-}" + shift 2 + ;; + --context) + context_json="${2:-}" + shift 2 + ;; + --raw) + raw_output='true' + shift + ;; + --help|-h) + usage + exit 0 + ;; + --) + shift + break + ;; + *) + break + ;; + esac +done + +if [ $# -gt 0 ]; then + input="$*" +else + input="$(cat)" +fi + +if [ -z "$input" ]; then + echo 'Error: input is required' >&2 + exit 1 +fi + +for name in $(env | cut -d= -f1); do + case "$name" in + *_API_KEY|*_TOKEN) + echo "Refusing to run: token-like env var ${name} detected. Use a clean env." >&2 + exit 2 + ;; + esac +done + +gateway_url="${BLACKROAD_GATEWAY_URL:-http://127.0.0.1:8787}" + +request="$( + BR_AGENT_INPUT="$input" \ + BR_AGENT_INTENT="$intent" \ + BR_AGENT_CONTEXT="$context_json" \ + BR_AGENT_NAME="$AGENT_NAME" \ + python3 - <<'PY' +import json +import os + +payload = { + 'agent': os.environ.get('BR_AGENT_NAME', 'lucidia'), + 'intent': os.environ.get('BR_AGENT_INTENT', 'vision'), + 'input': os.environ.get('BR_AGENT_INPUT', ''), + 'context': json.loads(os.environ.get('BR_AGENT_CONTEXT', '{}') or '{}') +} +print(json.dumps(payload)) +PY +)" + +response="$( + curl -sS --fail \ + -X POST "${gateway_url}/v1/agent" \ + -H 'Content-Type: application/json' \ + -d "$request" +)" || { + echo 'Error: BlackRoad Gateway unavailable' >&2 + exit 3 +} + +if [ "$raw_output" = 'true' ]; then + echo "$response" + exit 0 +fi + +python3 - <<'PY' <<<"$response" +import json +import sys + +data = json.load(sys.stdin) +if data.get('status') != 'ok': + sys.stderr.write((data.get('error') or 'Gateway error') + '\n') + sys.exit(1) + +print(data.get('output', '')) +PY diff --git a/blackroad-core/agents/octavia.sh b/blackroad-core/agents/octavia.sh new file mode 100755 index 0000000000..808a1ebb36 --- /dev/null +++ b/blackroad-core/agents/octavia.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +# Octavia - The Architect +# Systems design, infrastructure planning, technical architecture +# Tokenless agent: refuses to run if credentials detected in env +set -euo pipefail + +AGENT_NAME='octavia' + +usage() { + cat <<'EOF' +Usage: + octavia.sh [--intent ] [--context ] [--raw] [input...] + +Intents: architect, review, optimize, diagnose + +If no input is provided, the agent reads from stdin. +EOF +} + +intent='architect' +context_json='{}' +raw_output='false' + +while [ $# -gt 0 ]; do + case "$1" in + --intent) + intent="${2:-}" + shift 2 + ;; + --context) + context_json="${2:-}" + shift 2 + ;; + --raw) + raw_output='true' + shift + ;; + --help|-h) + usage + exit 0 + ;; + --) + shift + break + ;; + *) + break + ;; + esac +done + +if [ $# -gt 0 ]; then + input="$*" +else + input="$(cat)" +fi + +if [ -z "$input" ]; then + echo 'Error: input is required' >&2 + exit 1 +fi + +# Tokenless enforcement +for name in $(env | cut -d= -f1); do + case "$name" in + *_API_KEY|*_TOKEN) + echo "Refusing to run: token-like env var ${name} detected. Use a clean env." >&2 + exit 2 + ;; + esac +done + +gateway_url="${BLACKROAD_GATEWAY_URL:-http://127.0.0.1:8787}" + +request="$( + BR_AGENT_INPUT="$input" \ + BR_AGENT_INTENT="$intent" \ + BR_AGENT_CONTEXT="$context_json" \ + BR_AGENT_NAME="$AGENT_NAME" \ + python3 - <<'PY' +import json +import os + +payload = { + 'agent': os.environ.get('BR_AGENT_NAME', 'octavia'), + 'intent': os.environ.get('BR_AGENT_INTENT', 'architect'), + 'input': os.environ.get('BR_AGENT_INPUT', ''), + 'context': json.loads(os.environ.get('BR_AGENT_CONTEXT', '{}') or '{}') +} +print(json.dumps(payload)) +PY +)" + +response="$( + curl -sS --fail \ + -X POST "${gateway_url}/v1/agent" \ + -H 'Content-Type: application/json' \ + -d "$request" +)" || { + echo 'Error: BlackRoad Gateway unavailable' >&2 + exit 3 +} + +if [ "$raw_output" = 'true' ]; then + echo "$response" + exit 0 +fi + +python3 - <<'PY' <<<"$response" +import json +import sys + +data = json.load(sys.stdin) +if data.get('status') != 'ok': + sys.stderr.write((data.get('error') or 'Gateway error') + '\n') + sys.exit(1) + +print(data.get('output', '')) +PY diff --git a/blackroad-core/agents/planner.sh b/blackroad-core/agents/planner.sh new file mode 100755 index 0000000000..6e0402fb8d --- /dev/null +++ b/blackroad-core/agents/planner.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: + planner.sh [--intent ] [--context ] [--raw] [input...] + +If no input is provided, the agent reads from stdin. +EOF +} + +intent='analyze' +context_json='{}' +raw_output='false' + +while [ $# -gt 0 ]; do + case "$1" in + --intent) + intent="${2:-}" + shift 2 + ;; + --context) + context_json="${2:-}" + shift 2 + ;; + --raw) + raw_output='true' + shift + ;; + --help|-h) + usage + exit 0 + ;; + --) + shift + break + ;; + *) + break + ;; + esac +done + +if [ $# -gt 0 ]; then + input="$*" +else + input="$(cat)" +fi + +if [ -z "$input" ]; then + echo 'Error: input is required' >&2 + exit 1 +fi + +for name in $(env | cut -d= -f1); do + case "$name" in + *_API_KEY|*_TOKEN) + echo "Refusing to run: token-like env var ${name} detected. Use a clean env." >&2 + exit 2 + ;; + esac +done + +gateway_url="${BLACKROAD_GATEWAY_URL:-http://127.0.0.1:8787}" + +request="$( + BR_AGENT_INPUT="$input" \ + BR_AGENT_INTENT="$intent" \ + BR_AGENT_CONTEXT="$context_json" \ + python3 - <<'PY' +import json +import os + +input_text = os.environ.get('BR_AGENT_INPUT', '') +intent = os.environ.get('BR_AGENT_INTENT', 'analyze') +context_raw = os.environ.get('BR_AGENT_CONTEXT', '{}') + +try: + context = json.loads(context_raw) if context_raw else {} +except Exception: + context = {'_raw': context_raw} + +payload = { + 'agent': 'planner', + 'intent': intent, + 'input': input_text, + 'context': context +} + +print(json.dumps(payload)) +PY +)" + +response="$( + curl -sS --fail \ + -X POST "${gateway_url}/v1/agent" \ + -H 'Content-Type: application/json' \ + -d "$request" +)" || { + echo 'Error: BlackRoad Gateway unavailable' >&2 + exit 3 +} + +if [ "$raw_output" = 'true' ]; then + echo "$response" + exit 0 +fi + +python3 - <<'PY' <<<"$response" +import json +import sys + +data = json.load(sys.stdin) +if data.get('status') != 'ok': + sys.stderr.write((data.get('error') or 'Gateway error') + '\n') + sys.exit(1) + +print(data.get('output', '')) +PY diff --git a/blackroad-core/agents/prism.sh b/blackroad-core/agents/prism.sh new file mode 100755 index 0000000000..e833aca6f3 --- /dev/null +++ b/blackroad-core/agents/prism.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +# Prism - The Analyst +# Pattern recognition, data analysis, metrics, observability +# Tokenless agent: refuses to run if credentials detected in env +set -euo pipefail + +AGENT_NAME='prism' + +usage() { + cat <<'EOF' +Usage: + prism.sh [--intent ] [--context ] [--raw] [input...] + +Intents: analyze, correlate, report, forecast + +If no input is provided, the agent reads from stdin. +EOF +} + +intent='analyze' +context_json='{}' +raw_output='false' + +while [ $# -gt 0 ]; do + case "$1" in + --intent) + intent="${2:-}" + shift 2 + ;; + --context) + context_json="${2:-}" + shift 2 + ;; + --raw) + raw_output='true' + shift + ;; + --help|-h) + usage + exit 0 + ;; + --) + shift + break + ;; + *) + break + ;; + esac +done + +if [ $# -gt 0 ]; then + input="$*" +else + input="$(cat)" +fi + +if [ -z "$input" ]; then + echo 'Error: input is required' >&2 + exit 1 +fi + +for name in $(env | cut -d= -f1); do + case "$name" in + *_API_KEY|*_TOKEN) + echo "Refusing to run: token-like env var ${name} detected. Use a clean env." >&2 + exit 2 + ;; + esac +done + +gateway_url="${BLACKROAD_GATEWAY_URL:-http://127.0.0.1:8787}" + +request="$( + BR_AGENT_INPUT="$input" \ + BR_AGENT_INTENT="$intent" \ + BR_AGENT_CONTEXT="$context_json" \ + BR_AGENT_NAME="$AGENT_NAME" \ + python3 - <<'PY' +import json +import os + +payload = { + 'agent': os.environ.get('BR_AGENT_NAME', 'prism'), + 'intent': os.environ.get('BR_AGENT_INTENT', 'analyze'), + 'input': os.environ.get('BR_AGENT_INPUT', ''), + 'context': json.loads(os.environ.get('BR_AGENT_CONTEXT', '{}') or '{}') +} +print(json.dumps(payload)) +PY +)" + +response="$( + curl -sS --fail \ + -X POST "${gateway_url}/v1/agent" \ + -H 'Content-Type: application/json' \ + -d "$request" +)" || { + echo 'Error: BlackRoad Gateway unavailable' >&2 + exit 3 +} + +if [ "$raw_output" = 'true' ]; then + echo "$response" + exit 0 +fi + +python3 - <<'PY' <<<"$response" +import json +import sys + +data = json.load(sys.stdin) +if data.get('status') != 'ok': + sys.stderr.write((data.get('error') or 'Gateway error') + '\n') + sys.exit(1) + +print(data.get('output', '')) +PY diff --git a/blackroad-core/gateway/providers/anthropic.js b/blackroad-core/gateway/providers/anthropic.js new file mode 100644 index 0000000000..e2776baf5c --- /dev/null +++ b/blackroad-core/gateway/providers/anthropic.js @@ -0,0 +1,64 @@ +'use strict' + +const DEFAULT_BASE_URL = 'https://api.anthropic.com/v1' +const DEFAULT_MODEL = 'claude-sonnet-4-6' +const DEFAULT_MAX_TOKENS = 4096 + +async function invoke({ input, system }) { + if (typeof fetch !== 'function') { + throw new Error('Global fetch is not available') + } + + const apiKey = process.env.BLACKROAD_ANTHROPIC_API_KEY + if (!apiKey) { + throw new Error('Missing BLACKROAD_ANTHROPIC_API_KEY') + } + + const baseUrl = process.env.BLACKROAD_ANTHROPIC_BASE_URL || DEFAULT_BASE_URL + const model = process.env.BLACKROAD_ANTHROPIC_MODEL || DEFAULT_MODEL + const maxTokens = process.env.BLACKROAD_ANTHROPIC_MAX_TOKENS + ? Number(process.env.BLACKROAD_ANTHROPIC_MAX_TOKENS) + : DEFAULT_MAX_TOKENS + + const body = { + model, + max_tokens: maxTokens, + messages: [ + { + role: 'user', + content: input + } + ] + } + + if (system && system.trim()) { + body.system = system + } + + const response = await fetch(`${baseUrl}/messages`, { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'x-api-key': apiKey, + 'anthropic-version': '2023-06-01' + }, + body: JSON.stringify(body) + }) + + const data = await response.json().catch(() => ({})) + if (!response.ok) { + throw new Error(data.error?.message || `Anthropic error ${response.status}`) + } + + const content = data.content || [] + const textBlock = content.find((block) => block.type === 'text') + if (textBlock && typeof textBlock.text === 'string') { + return textBlock.text + } + + return '' +} + +module.exports = { + invoke +} diff --git a/blackroad-core/gateway/providers/gemini.js b/blackroad-core/gateway/providers/gemini.js new file mode 100644 index 0000000000..b9514549a7 --- /dev/null +++ b/blackroad-core/gateway/providers/gemini.js @@ -0,0 +1,66 @@ +'use strict' + +const DEFAULT_MODEL = 'gemini-2.0-flash' + +async function invoke({ input, system }) { + if (typeof fetch !== 'function') { + throw new Error('Global fetch is not available') + } + + const apiKey = process.env.BLACKROAD_GEMINI_API_KEY + if (!apiKey) { + throw new Error('Missing BLACKROAD_GEMINI_API_KEY') + } + + const model = process.env.BLACKROAD_GEMINI_MODEL || DEFAULT_MODEL + const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}` + + const contents = [] + + if (system && system.trim()) { + contents.push({ + role: 'user', + parts: [{ text: `[System Instructions]\n${system}\n\n[User Request]\n${input}` }] + }) + } else { + contents.push({ + role: 'user', + parts: [{ text: input }] + }) + } + + const body = { + contents, + generationConfig: { + maxOutputTokens: process.env.BLACKROAD_GEMINI_MAX_TOKENS + ? Number(process.env.BLACKROAD_GEMINI_MAX_TOKENS) + : 2048 + } + } + + const response = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }) + + const data = await response.json().catch(() => ({})) + if (!response.ok) { + const msg = data.error?.message || `Gemini error ${response.status}` + throw new Error(msg) + } + + const candidate = data.candidates?.[0] + if (candidate?.content?.parts) { + const textPart = candidate.content.parts.find((p) => typeof p.text === 'string') + if (textPart) { + return textPart.text + } + } + + return '' +} + +module.exports = { + invoke +} diff --git a/blackroad-core/gateway/providers/index.js b/blackroad-core/gateway/providers/index.js new file mode 100644 index 0000000000..2cc18fe378 --- /dev/null +++ b/blackroad-core/gateway/providers/index.js @@ -0,0 +1,22 @@ +'use strict' + +const ollama = require('./ollama') +const openai = require('./openai') +const anthropic = require('./anthropic') +const gemini = require('./gemini') + +const providers = { + ollama, + openai, + claude: anthropic, + anthropic, + gemini +} + +function getProvider(name) { + return providers[name] || null +} + +module.exports = { + getProvider +} diff --git a/blackroad-core/gateway/providers/ollama.js b/blackroad-core/gateway/providers/ollama.js new file mode 100644 index 0000000000..96c9c2d9b5 --- /dev/null +++ b/blackroad-core/gateway/providers/ollama.js @@ -0,0 +1,50 @@ +'use strict' + +const DEFAULT_BASE_URL = 'http://127.0.0.1:11434' +const DEFAULT_MODEL = 'llama3.1' + +function buildPrompt(system, input) { + if (system && system.trim()) { + return `${system}\n\n${input}` + } + return input +} + +async function invoke({ input, system }) { + if (typeof fetch !== 'function') { + throw new Error('Global fetch is not available') + } + + const baseUrl = process.env.BLACKROAD_OLLAMA_URL || DEFAULT_BASE_URL + const model = process.env.BLACKROAD_OLLAMA_MODEL || DEFAULT_MODEL + const prompt = buildPrompt(system, input) + + const response = await fetch(`${baseUrl}/api/generate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + model, + prompt, + stream: false + }) + }) + + const data = await response.json().catch(() => ({})) + if (!response.ok) { + throw new Error(data.error || `Ollama error ${response.status}`) + } + + if (typeof data.response === 'string') { + return data.response + } + + if (data.message && typeof data.message.content === 'string') { + return data.message.content + } + + return '' +} + +module.exports = { + invoke +} diff --git a/blackroad-core/gateway/providers/openai.js b/blackroad-core/gateway/providers/openai.js new file mode 100644 index 0000000000..5e0f54fc1b --- /dev/null +++ b/blackroad-core/gateway/providers/openai.js @@ -0,0 +1,52 @@ +'use strict' + +const DEFAULT_BASE_URL = 'https://api.openai.com/v1' +const DEFAULT_MODEL = 'gpt-4o-mini' + +async function invoke({ input, system }) { + if (typeof fetch !== 'function') { + throw new Error('Global fetch is not available') + } + + const apiKey = process.env.BLACKROAD_OPENAI_API_KEY + if (!apiKey) { + throw new Error('Missing BLACKROAD_OPENAI_API_KEY') + } + + const baseUrl = process.env.BLACKROAD_OPENAI_BASE_URL || DEFAULT_BASE_URL + const model = process.env.BLACKROAD_OPENAI_MODEL || DEFAULT_MODEL + + const messages = [] + if (system && system.trim()) { + messages.push({ role: 'system', content: system }) + } + messages.push({ role: 'user', content: input }) + + const response = await fetch(`${baseUrl}/chat/completions`, { + method: 'POST', + headers: { + 'content-type': 'application/json', + authorization: `Bearer ${apiKey}` + }, + body: JSON.stringify({ + model, + messages + }) + }) + + const data = await response.json().catch(() => ({})) + if (!response.ok) { + throw new Error(data.error?.message || `OpenAI error ${response.status}`) + } + + const message = data.choices?.[0]?.message?.content + if (typeof message === 'string') { + return message + } + + return '' +} + +module.exports = { + invoke +} diff --git a/blackroad-core/gateway/server.js b/blackroad-core/gateway/server.js new file mode 100644 index 0000000000..02c1c1b0af --- /dev/null +++ b/blackroad-core/gateway/server.js @@ -0,0 +1,542 @@ +'use strict' + +const http = require('http') +const { randomUUID } = require('crypto') +const fs = require('fs/promises') +const path = require('path') +const { getProvider } = require('./providers') + +const DEFAULT_CONFIG = { + bind: '127.0.0.1', + port: 8787, + policyPath: path.join(__dirname, '..', 'policies', 'agent-permissions.json'), + promptPath: path.join(__dirname, 'system-prompts.json'), + logPath: path.join(__dirname, 'logs', 'gateway.jsonl'), + maxBodyBytes: 1024 * 1024 +} + +// --------------------------------------------------------------------------- +// Rate limiter (in-memory, per-agent sliding window) +// --------------------------------------------------------------------------- +class RateLimiter { + constructor() { + this.windows = new Map() + } + + _key(agent) { + return agent + } + + _prune(entries, windowMs) { + const cutoff = Date.now() - windowMs + while (entries.length > 0 && entries[0] < cutoff) { + entries.shift() + } + } + + check(agent, limitPerMinute) { + if (!limitPerMinute || limitPerMinute <= 0) return true + const key = this._key(agent) + if (!this.windows.has(key)) { + this.windows.set(key, []) + } + const entries = this.windows.get(key) + this._prune(entries, 60000) + return entries.length < limitPerMinute + } + + record(agent) { + const key = this._key(agent) + if (!this.windows.has(key)) { + this.windows.set(key, []) + } + this.windows.get(key).push(Date.now()) + } + + getUsage(agent) { + const key = this._key(agent) + if (!this.windows.has(key)) return 0 + const entries = this.windows.get(key) + this._prune(entries, 60000) + return entries.length + } +} + +const rateLimiter = new RateLimiter() + +// --------------------------------------------------------------------------- +// Metrics (in-memory counters) +// --------------------------------------------------------------------------- +const metrics = { + totalRequests: 0, + totalErrors: 0, + totalOk: 0, + byAgent: {}, + byProvider: {}, + startTime: Date.now(), + + record(agent, provider, status) { + this.totalRequests++ + if (status === 'ok') this.totalOk++ + else this.totalErrors++ + this.byAgent[agent] = (this.byAgent[agent] || 0) + 1 + if (provider) { + this.byProvider[provider] = (this.byProvider[provider] || 0) + 1 + } + }, + + snapshot() { + return { + uptime_seconds: Math.floor((Date.now() - this.startTime) / 1000), + total_requests: this.totalRequests, + total_ok: this.totalOk, + total_errors: this.totalErrors, + by_agent: { ...this.byAgent }, + by_provider: { ...this.byProvider } + } + } +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- +async function loadJson(filePath) { + try { + const data = await fs.readFile(filePath, 'utf8') + return JSON.parse(data) + } catch (error) { + if (error.code === 'ENOENT') { + return null + } + throw error + } +} + +function readEnvConfig() { + const env = process.env + return { + bind: env.BLACKROAD_GATEWAY_BIND || undefined, + port: env.BLACKROAD_GATEWAY_PORT ? Number(env.BLACKROAD_GATEWAY_PORT) : undefined, + policyPath: env.BLACKROAD_GATEWAY_POLICY_PATH || undefined, + promptPath: env.BLACKROAD_GATEWAY_PROMPT_PATH || undefined, + logPath: env.BLACKROAD_GATEWAY_LOG_PATH || undefined, + maxBodyBytes: env.BLACKROAD_GATEWAY_MAX_BODY_BYTES + ? Number(env.BLACKROAD_GATEWAY_MAX_BODY_BYTES) + : undefined, + allowRemote: env.BLACKROAD_GATEWAY_ALLOW_REMOTE === 'true' + } +} + +function mergeConfig(base, extra) { + return { + bind: extra.bind || base.bind, + port: Number.isFinite(extra.port) ? extra.port : base.port, + policyPath: extra.policyPath || base.policyPath, + promptPath: extra.promptPath || base.promptPath, + logPath: extra.logPath || base.logPath, + maxBodyBytes: Number.isFinite(extra.maxBodyBytes) ? extra.maxBodyBytes : base.maxBodyBytes, + allowRemote: typeof extra.allowRemote === 'boolean' ? extra.allowRemote : base.allowRemote + } +} + +function isLoopback(req) { + const address = req.socket.remoteAddress || '' + return ( + address === '127.0.0.1' || + address === '::1' || + address.startsWith('::ffff:127.') + ) +} + +function buildSystemPrompt(prompts, agent, intent, context) { + if (!prompts) { + return '' + } + + const parts = [] + if (typeof prompts.default === 'string' && prompts.default.trim()) { + parts.push(prompts.default.trim()) + } + if (prompts.agents && typeof prompts.agents[agent] === 'string') { + parts.push(prompts.agents[agent].trim()) + } + if (prompts.intents && typeof prompts.intents[intent] === 'string') { + parts.push(prompts.intents[intent].trim()) + } + if (context && Object.keys(context).length > 0) { + parts.push(`Context JSON:\n${JSON.stringify(context)}`) + } + + return parts.join('\n\n') +} + +async function readBody(req, maxBytes) { + return await new Promise((resolve, reject) => { + let body = '' + let bytes = 0 + req.on('data', (chunk) => { + bytes += chunk.length + if (bytes > maxBytes) { + reject(new Error('Request body too large')) + req.destroy() + return + } + body += chunk + }) + req.on('end', () => resolve(body)) + req.on('error', reject) + }) +} + +async function appendLog(logPath, entry) { + const logDir = path.dirname(logPath) + await fs.mkdir(logDir, { recursive: true }) + await fs.appendFile(logPath, `${JSON.stringify(entry)}\n`, 'utf8') +} + +function pickProvider(requested, policy, intent) { + if (requested) { + return requested + } + if (policy.intent_routes && policy.intent_routes[intent]) { + return policy.intent_routes[intent] + } + return policy.default_provider || null +} + +async function loadPolicy(policyPath) { + const policy = await loadJson(policyPath) + if (!policy || !policy.agents) { + throw new Error('Policy file missing or invalid') + } + return policy +} + +function validateRequest(payload) { + if (!payload || typeof payload !== 'object') { + return 'Invalid JSON payload' + } + if (!payload.agent || typeof payload.agent !== 'string') { + return 'Missing agent' + } + if (!payload.intent || typeof payload.intent !== 'string') { + return 'Missing intent' + } + if (typeof payload.input !== 'string') { + return 'Missing input' + } + if (payload.context && typeof payload.context !== 'object') { + return 'Context must be an object' + } + return null +} + +// --------------------------------------------------------------------------- +// Provider invocation with fallback chain +// --------------------------------------------------------------------------- +async function invokeWithFallback(primaryProvider, fallbackChain, invokeArgs) { + // Try primary first + const primary = getProvider(primaryProvider) + if (primary) { + try { + const output = await primary.invoke(invokeArgs) + return { output, provider: primaryProvider } + } catch (err) { + // If no fallback chain, rethrow + if (!fallbackChain || fallbackChain.length === 0) { + throw err + } + // Otherwise try fallbacks + } + } + + // Try fallback chain + if (fallbackChain && fallbackChain.length > 0) { + const errors = [] + for (const name of fallbackChain) { + if (name === primaryProvider) continue // already tried + const fallback = getProvider(name) + if (!fallback) continue + try { + const output = await fallback.invoke(invokeArgs) + return { output, provider: name, fallback: true } + } catch (err) { + errors.push(`${name}: ${err.message}`) + } + } + throw new Error(`All providers failed: ${errors.join('; ')}`) + } + + throw new Error('No provider available') +} + +// --------------------------------------------------------------------------- +// Server +// --------------------------------------------------------------------------- +async function start() { + const configFilePath = process.env.BLACKROAD_GATEWAY_CONFIG + ? path.resolve(process.env.BLACKROAD_GATEWAY_CONFIG) + : path.join(__dirname, 'config.json') + const fileConfig = (await loadJson(configFilePath)) || {} + const config = mergeConfig(DEFAULT_CONFIG, mergeConfig(fileConfig, readEnvConfig())) + + const server = http.createServer(async (req, res) => { + const startTime = Date.now() + const requestId = randomUUID() + let agentName = null + let intent = null + let providerName = null + let status = 'error' + let responsePayload = null + let requestPayload = null + + const send = (code, payload) => { + if (payload.status === 'error' && typeof payload.output !== 'string') { + payload.output = '' + } + responsePayload = payload + status = payload.status || status + res.writeHead(code, { 'Content-Type': 'application/json' }) + res.end(JSON.stringify(payload)) + } + + try { + // --------------------------------------------------------------- + // Health check + // --------------------------------------------------------------- + if (req.method === 'GET' && req.url === '/healthz') { + return send(200, { status: 'ok', gateway: 'blackroad-core', version: 2 }) + } + + // --------------------------------------------------------------- + // Metrics endpoint + // --------------------------------------------------------------- + if (req.method === 'GET' && req.url === '/metrics') { + if (!config.allowRemote && !isLoopback(req)) { + return send(403, { status: 'error', error: 'Remote access denied' }) + } + return send(200, { status: 'ok', metrics: metrics.snapshot() }) + } + + // --------------------------------------------------------------- + // Agent roster - list available agents + // --------------------------------------------------------------- + if (req.method === 'GET' && req.url === '/v1/agents') { + if (!config.allowRemote && !isLoopback(req)) { + return send(403, { status: 'error', error: 'Remote access denied' }) + } + const policy = await loadPolicy(config.policyPath) + const roster = Object.entries(policy.agents).map(([name, cfg]) => ({ + name, + description: cfg.description || '', + intents: cfg.allowed_intents || [], + providers: cfg.allowed_providers || [], + default_provider: cfg.default_provider || null, + rate_limit: cfg.rate_limit_per_minute || null, + usage_last_minute: rateLimiter.getUsage(name) + })) + return send(200, { status: 'ok', agents: roster }) + } + + // --------------------------------------------------------------- + // Worlds stats proxy endpoint + // --------------------------------------------------------------- + if (req.method === 'GET' && req.url === '/v1/worlds') { + try { + const res = await fetch('https://worlds.blackroad.io/stats') + const data = await res.json() + return send(200, { status: 'ok', worlds: data }) + } catch (e) { + return send(502, { status: 'error', error: 'worlds feed unavailable' }) + } + } + + // --------------------------------------------------------------- + // Main agent endpoint + // --------------------------------------------------------------- + if (req.method !== 'POST' || req.url !== '/v1/agent') { + return send(404, { status: 'error', error: 'Not found', request_id: requestId }) + } + + if (!config.allowRemote && !isLoopback(req)) { + return send(403, { + status: 'error', + error: 'Remote access denied', + request_id: requestId + }) + } + + const body = await readBody(req, config.maxBodyBytes) + let payload + try { + payload = JSON.parse(body) + } catch (error) { + return send(400, { status: 'error', error: 'Invalid JSON', request_id: requestId }) + } + + const validationError = validateRequest(payload) + if (validationError) { + return send(400, { status: 'error', error: validationError, request_id: requestId }) + } + + requestPayload = payload + agentName = payload.agent + intent = payload.intent + const policy = await loadPolicy(config.policyPath) + const agentPolicy = policy.agents[agentName] + if (!agentPolicy) { + return send(403, { status: 'error', error: 'Agent not allowed', request_id: requestId }) + } + + if (!agentPolicy.allowed_intents || !agentPolicy.allowed_intents.includes(intent)) { + return send(403, { status: 'error', error: 'Intent not allowed', request_id: requestId }) + } + + if ( + agentPolicy.max_input_bytes && + Buffer.byteLength(payload.input, 'utf8') > agentPolicy.max_input_bytes + ) { + return send(413, { status: 'error', error: 'Input too large', request_id: requestId }) + } + + // --------------------------------------------------------------- + // Rate limiting + // --------------------------------------------------------------- + const agentLimit = agentPolicy.rate_limit_per_minute || + (policy.global && policy.global.rate_limit_per_minute) || 0 + if (agentLimit > 0 && !rateLimiter.check(agentName, agentLimit)) { + return send(429, { + status: 'error', + error: 'Rate limit exceeded', + request_id: requestId, + metadata: { + limit_per_minute: agentLimit, + retry_after_seconds: 60 + } + }) + } + + // --------------------------------------------------------------- + // Provider selection and invocation with fallback + // --------------------------------------------------------------- + providerName = pickProvider(payload.provider, agentPolicy, intent) + if (!providerName) { + return send(400, { + status: 'error', + error: 'Provider not configured', + request_id: requestId + }) + } + + if ( + agentPolicy.allowed_providers && + !agentPolicy.allowed_providers.includes(providerName) + ) { + return send(403, { status: 'error', error: 'Provider not allowed', request_id: requestId }) + } + + const prompts = await loadJson(config.promptPath) + const systemPrompt = buildSystemPrompt(prompts, agentName, intent, payload.context) + const invokeArgs = { + input: payload.input, + system: systemPrompt, + context: payload.context || {}, + requestId, + agent: agentName, + intent + } + + const result = await invokeWithFallback( + providerName, + agentPolicy.fallback_chain || [], + invokeArgs + ) + + // Record rate limit hit after successful invocation + rateLimiter.record(agentName) + + status = 'ok' + providerName = result.provider + responsePayload = { + status, + provider: result.provider, + output: result.output, + request_id: requestId, + metadata: { + latency_ms: Date.now() - startTime, + fallback: result.fallback || false + } + } + return send(200, responsePayload) + } catch (error) { + responsePayload = { + status: 'error', + error: error.message || 'Gateway error', + request_id: requestId, + metadata: { latency_ms: Date.now() - startTime } + } + if (providerName) { + responsePayload.provider = providerName + } + return send(500, responsePayload) + } finally { + // Record metrics + metrics.record(agentName, providerName, status) + + const requestLog = requestPayload + ? { + agent: requestPayload.agent, + intent: requestPayload.intent, + provider: requestPayload.provider || null, + input: requestPayload.input, + context: requestPayload.context || {}, + input_bytes: Buffer.byteLength(requestPayload.input || '', 'utf8') + } + : null + const logEntry = { + timestamp: new Date().toISOString(), + request_id: requestId, + agent: agentName, + intent, + provider: providerName, + status, + request: requestLog, + response: responsePayload, + remote_address: req.socket.remoteAddress || null + } + try { + await appendLog(config.logPath, logEntry) + } catch (error) { + } + } + }) + + server.listen(config.port, config.bind, () => { + console.log(`BlackRoad Gateway v2 listening on ${config.bind}:${config.port}`) + console.log(` Endpoints:`) + console.log(` POST /v1/agent - Agent invocation`) + console.log(` GET /v1/worlds - World artifact stats`) + console.log(` GET /v1/agents - Agent roster`) + console.log(` GET /healthz - Health check`) + console.log(` GET /metrics - Gateway metrics`) + }) +} + +// Export internals for testing +if (process.env.NODE_ENV === 'test') { + module.exports = { + RateLimiter, + validateRequest, + pickProvider, + buildSystemPrompt, + isLoopback, + invokeWithFallback, + mergeConfig, + metrics + } +} else { + start().catch((error) => { + console.error('Failed to start gateway', error) + process.exit(1) + }) +} diff --git a/blackroad-core/gateway/server.sh b/blackroad-core/gateway/server.sh new file mode 100755 index 0000000000..1bc1b83e05 --- /dev/null +++ b/blackroad-core/gateway/server.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +node "$SCRIPT_DIR/server.js" diff --git a/blackroad-core/gateway/system-prompts.json b/blackroad-core/gateway/system-prompts.json new file mode 100644 index 0000000000..39c799316b --- /dev/null +++ b/blackroad-core/gateway/system-prompts.json @@ -0,0 +1,34 @@ +{ + "default": "You are a BlackRoad gateway-managed model operating within sovereign infrastructure. Follow BlackRoad policy. Return concise, actionable output. Never leak internal architecture details to external parties.", + "agents": { + "planner": "You are the BlackRoad Planner agent. You produce structured plans with phased rollouts, dependency graphs, and verification checkpoints. Always consider blast radius and rollback strategies. Output actionable next steps, never vague suggestions.", + "octavia": "You are Octavia, the BlackRoad Architect. You think in systems: distributed architectures, infrastructure topology, scaling patterns, and failure modes. You design for resilience and sovereignty. When reviewing, you identify structural weaknesses and propose concrete improvements with trade-off analysis. You speak with precision and authority.", + "lucidia": "You are Lucidia, the BlackRoad Dreamer and Coordinator. You see connections others miss and synthesize disparate ideas into coherent visions. When mentoring, you guide with questions that illuminate rather than prescribe. You balance ambition with pragmatism. You speak warmly but with intellectual depth.", + "alice": "You are Alice, the BlackRoad Operator. You are the hands that make things happen: deployments, automation, routing, monitoring. You think in pipelines, health checks, and runbooks. Your outputs are executable steps, not theories. When something is broken, you triage and fix before philosophizing. You speak directly and efficiently.", + "cipher": "You are Cipher, the BlackRoad Guardian. Security is freedom and you enforce it through rigorous auditing, vulnerability analysis, and defense-in-depth. You think like an attacker to defend like a fortress. Your outputs include specific CVEs, OWASP references, and remediation steps with severity ratings. You never downplay risks.", + "prism": "You are Prism, the BlackRoad Analyst. You see patterns in data that others miss. You correlate signals across systems, surface anomalies, and forecast trends. Your outputs include structured data, confidence intervals, and supporting evidence. You distinguish correlation from causation and flag uncertainty explicitly." + }, + "intents": { + "analyze": "Analyze the input thoroughly. Surface risks, dependencies, and edge cases. Provide a direct recommendation with supporting reasoning.", + "plan": "Produce a phased plan with: (1) dependencies and ordering, (2) verification steps for each phase, (3) rollback procedures, (4) estimated complexity per phase.", + "architect": "Design the system architecture. Include: component diagram, data flow, failure modes, scaling strategy, and technology choices with rationale. Consider BlackRoad's multi-cloud, multi-device topology.", + "review": "Review the provided code or design critically. Identify: bugs, security issues, performance bottlenecks, maintainability concerns, and deviation from BlackRoad conventions. Rate severity as critical/high/medium/low.", + "optimize": "Identify optimization opportunities. Quantify expected improvement where possible. Prioritize by impact-to-effort ratio. Consider both performance and cost optimization.", + "diagnose": "Diagnose the reported issue. Produce: (1) likely root cause with confidence level, (2) diagnostic steps to confirm, (3) immediate mitigation, (4) permanent fix.", + "vision": "Articulate a strategic vision. Connect current state to future state with a coherent narrative. Identify the key leverage points and decisive moves. Be bold but grounded.", + "synthesize": "Synthesize the provided inputs into a unified understanding. Identify themes, contradictions, and gaps. Produce a coherent summary that is more than the sum of its parts.", + "mentor": "Guide the learner toward understanding. Ask clarifying questions, provide scaffolding, and explain the 'why' behind decisions. Adapt depth to the learner's level.", + "explore": "Explore the problem space broadly. Map out possibilities, identify unknowns, and suggest investigation paths. Flag assumptions that need validation.", + "deploy": "Produce a deployment plan. Include: pre-flight checks, deployment steps, health verification, rollback trigger conditions, and post-deploy monitoring. Be specific to the target platform.", + "automate": "Design the automation workflow. Specify: trigger conditions, steps, error handling, retry logic, notifications, and idempotency guarantees.", + "route": "Determine the optimal routing for the request. Consider: agent capabilities, current load, priority, and cost. Explain the routing decision.", + "monitor": "Design or evaluate the monitoring setup. Cover: metrics to track, alerting thresholds, dashboard layout, and incident response procedures.", + "audit": "Conduct a security audit. Check: authentication, authorization, input validation, secrets management, dependency vulnerabilities, and network exposure. Output findings as a structured report with severity ratings.", + "harden": "Produce a hardening plan. Address: attack surface reduction, least-privilege enforcement, secrets rotation, network segmentation, and monitoring gaps. Prioritize by risk.", + "scan": "Scan the input for vulnerabilities. Check against OWASP Top 10, CWE database, and known CVEs. Report findings with remediation guidance.", + "encrypt": "Evaluate or design the encryption strategy. Cover: data at rest, data in transit, key management, rotation policy, and algorithm selection. Reference current best practices.", + "correlate": "Correlate the provided data points. Identify: causal relationships, temporal patterns, anomalies, and confounding factors. Quantify confidence in each correlation.", + "report": "Generate a structured report from the input data. Include: executive summary, key findings, supporting data, visualizations (described), and recommended actions.", + "forecast": "Forecast future trends based on the provided data. Include: prediction with confidence interval, key assumptions, risk factors that could invalidate the forecast, and suggested monitoring indicators." + } +} diff --git a/blackroad-core/ollama-wrapper/docker-compose.yml b/blackroad-core/ollama-wrapper/docker-compose.yml new file mode 100644 index 0000000000..5dbfd4a5a7 --- /dev/null +++ b/blackroad-core/ollama-wrapper/docker-compose.yml @@ -0,0 +1,38 @@ +# 🌌 BlackRoad AI - Ollama Runtime +version: '3.8' + +services: + ollama: + build: + context: . + dockerfile: Dockerfile + container_name: blackroad-ai-ollama + image: blackroad-ai-ollama:latest + ports: + - "11434:11434" # Ollama API + - "8001:8001" # BlackRoad wrapper + volumes: + - ollama-models:/root/.ollama/models + - /Users/alexa:/host-home:ro + environment: + - OLLAMA_HOST=0.0.0.0 + - BLACKROAD_MEMORY_ENABLED=true + restart: unless-stopped + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + labels: + - "com.blackroad.service=ai-runtime" + - "com.blackroad.runtime=ollama" + +volumes: + ollama-models: + +networks: + default: + name: blackroad-ai-network + external: true diff --git a/blackroad-core/ollama-wrapper/entrypoint.sh b/blackroad-core/ollama-wrapper/entrypoint.sh new file mode 100755 index 0000000000..9c971fb110 --- /dev/null +++ b/blackroad-core/ollama-wrapper/entrypoint.sh @@ -0,0 +1,56 @@ +#!/bin/bash +# 🚀 BlackRoad AI - Ollama Entrypoint +set -e + +echo "🌌 Starting BlackRoad AI Ollama Runtime..." + +# Start Ollama server in background +echo "🔧 Starting Ollama server..." +ollama serve & +OLLAMA_PID=$! + +# Wait for Ollama to be ready +echo "⏳ Waiting for Ollama to start..." +for i in {1..30}; do + if curl -s http://localhost:11434/api/tags > /dev/null 2>&1; then + echo "✅ Ollama is ready!" + break + fi + sleep 1 +done + +# Pull default models +echo "📥 Pulling default models..." +MODELS_TO_PULL=( + "qwen2.5:7b" + "deepseek-r1:7b" + "llama3.2:3b" + "mistral:7b" +) + +for model in "${MODELS_TO_PULL[@]}"; do + echo "Pulling $model..." + ollama pull "$model" & +done + +# Wait for pulls to complete +wait + +echo "✅ All models pulled successfully!" + +# Start BlackRoad wrapper +if [ "$BLACKROAD_MEMORY_ENABLED" = "true" ]; then + echo "🧠 Starting BlackRoad [MEMORY] wrapper..." + python3 /app/blackroad-wrapper/server.py & +fi + +# Log to memory +if [ -f "/host-home/memory-system.sh" ]; then + /host-home/memory-system.sh log started "ollama-runtime" \ + "Ollama runtime started with models: ${MODELS_TO_PULL[*]}" \ + "ai-runtime,ollama" +fi + +# Keep container running +echo "🎯 BlackRoad AI Ollama Runtime is online!" +wait $OLLAMA_PID diff --git a/blackroad-core/ollama-wrapper/requirements.txt b/blackroad-core/ollama-wrapper/requirements.txt new file mode 100644 index 0000000000..555bf707cc --- /dev/null +++ b/blackroad-core/ollama-wrapper/requirements.txt @@ -0,0 +1,7 @@ +# BlackRoad Ollama Wrapper Dependencies +fastapi>=0.108.0 +uvicorn[standard]>=0.25.0 +httpx>=0.25.2 +pydantic>=2.5.0 +python-dotenv>=1.0.0 +redis>=5.0.1 diff --git a/blackroad-core/ollama-wrapper/server.py b/blackroad-core/ollama-wrapper/server.py new file mode 100644 index 0000000000..0889c9d90d --- /dev/null +++ b/blackroad-core/ollama-wrapper/server.py @@ -0,0 +1,174 @@ +""" +🤖 BlackRoad AI - Ollama Wrapper Server +Adds [MEMORY] integration, emoji support, and actions to Ollama +""" + +import os +import subprocess +from fastapi import FastAPI, HTTPException +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel +from typing import Optional, List +import httpx + +app = FastAPI( + title="BlackRoad AI - Ollama Wrapper", + description="Ollama with [MEMORY] integration and BlackRoad enhancements", + version="1.0.0" +) + +# CORS +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Ollama client +OLLAMA_URL = "http://localhost:11434" + + +class ChatRequest(BaseModel): + model: str + message: str + max_tokens: int = 512 + temperature: float = 0.7 + use_memory: bool = True + session_id: Optional[str] = None + + +class ChatResponse(BaseModel): + response: str + model: str + memory_context_used: bool = False + emoji_enhanced: bool = True + + +@app.get("/") +async def root(): + return { + "service": "BlackRoad AI - Ollama Wrapper", + "status": "online", + "features": ["memory_integration", "emoji_support", "multi_model"] + } + + +@app.get("/health") +async def health(): + """Health check""" + try: + async with httpx.AsyncClient() as client: + response = await client.get(f"{OLLAMA_URL}/api/tags") + return { + "status": "healthy", + "ollama_running": response.status_code == 200 + } + except: + return {"status": "unhealthy", "ollama_running": False} + + +@app.get("/models") +async def list_models(): + """List available models""" + try: + async with httpx.AsyncClient() as client: + response = await client.get(f"{OLLAMA_URL}/api/tags") + return response.json() + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + +@app.post("/chat", response_model=ChatResponse) +async def chat(request: ChatRequest): + """ + Chat with Ollama model + BlackRoad enhancements + """ + prompt = request.message + memory_used = False + + # Add memory context + if request.use_memory and request.session_id: + try: + result = subprocess.run( + ["/host-home/memory-system.sh", "check", f"ollama-{request.session_id}"], + capture_output=True, + text=True, + timeout=5 + ) + if result.returncode == 0 and result.stdout: + prompt = f"[Context]\n{result.stdout}\n\n{request.message}" + memory_used = True + except: + pass + + # Call Ollama + try: + async with httpx.AsyncClient(timeout=60.0) as client: + response = await client.post( + f"{OLLAMA_URL}/api/generate", + json={ + "model": request.model, + "prompt": prompt, + "stream": False, + "options": { + "temperature": request.temperature, + "num_predict": request.max_tokens + } + } + ) + result = response.json() + response_text = result.get("response", "") + + # Enhance with emojis + response_text = enhance_with_emojis(response_text) + + # Save to memory + if request.session_id: + try: + subprocess.run( + [ + "/host-home/memory-system.sh", "log", "interaction", + f"ollama-{request.session_id}", + f"Q: {request.message}\nA: {response_text}", + f"ai,ollama,{request.model}" + ], + timeout=5 + ) + except: + pass + + return ChatResponse( + response=response_text, + model=request.model, + memory_context_used=memory_used, + emoji_enhanced=True + ) + + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + +def enhance_with_emojis(text: str) -> str: + """Add contextual emojis""" + emoji_map = { + "success": "✅", + "error": "❌", + "warning": "⚠️", + "info": "ℹ️", + "blackroad": "🖤🛣️", + "ai": "🤖", + "quantum": "⚛️" + } + + for keyword, emoji in emoji_map.items(): + if keyword.lower() in text.lower() and emoji not in text: + text = text.replace(keyword, f"{emoji} {keyword}", 1) + + return text + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8001) diff --git a/blackroad-core/policies/agent-permissions.json b/blackroad-core/policies/agent-permissions.json new file mode 100644 index 0000000000..d4f17a1706 --- /dev/null +++ b/blackroad-core/policies/agent-permissions.json @@ -0,0 +1,117 @@ +{ + "version": 2, + "global": { + "rate_limit_per_minute": 120, + "max_concurrent_requests": 20, + "cost_tracking": true, + "log_level": "info" + }, + "agents": { + "planner": { + "description": "Strategic planning and analysis agent", + "allowed_intents": ["analyze", "plan"], + "allowed_providers": ["ollama", "claude", "openai", "gemini"], + "default_provider": "ollama", + "fallback_chain": ["ollama", "claude", "openai"], + "intent_routes": { + "analyze": "ollama", + "plan": "claude" + }, + "max_input_bytes": 20000, + "rate_limit_per_minute": 30, + "cost_tier": "standard" + }, + "octavia": { + "description": "Systems architecture and infrastructure design agent", + "allowed_intents": ["architect", "review", "optimize", "diagnose"], + "allowed_providers": ["ollama", "claude", "openai", "gemini"], + "default_provider": "claude", + "fallback_chain": ["claude", "openai", "ollama"], + "intent_routes": { + "architect": "claude", + "review": "claude", + "optimize": "ollama", + "diagnose": "ollama" + }, + "max_input_bytes": 50000, + "rate_limit_per_minute": 20, + "cost_tier": "premium" + }, + "lucidia": { + "description": "Creative vision, synthesis, and mentorship agent", + "allowed_intents": ["vision", "synthesize", "mentor", "explore"], + "allowed_providers": ["ollama", "claude", "openai", "gemini"], + "default_provider": "claude", + "fallback_chain": ["claude", "gemini", "openai"], + "intent_routes": { + "vision": "claude", + "synthesize": "claude", + "mentor": "ollama", + "explore": "ollama" + }, + "max_input_bytes": 40000, + "rate_limit_per_minute": 25, + "cost_tier": "premium" + }, + "alice": { + "description": "DevOps, deployment automation, and routing agent", + "allowed_intents": ["deploy", "automate", "route", "monitor"], + "allowed_providers": ["ollama", "claude", "openai", "gemini"], + "default_provider": "ollama", + "fallback_chain": ["ollama", "openai", "claude"], + "intent_routes": { + "deploy": "ollama", + "automate": "ollama", + "route": "ollama", + "monitor": "ollama" + }, + "max_input_bytes": 30000, + "rate_limit_per_minute": 40, + "cost_tier": "standard" + }, + "cipher": { + "description": "Security auditing, hardening, and vulnerability scanning agent", + "allowed_intents": ["audit", "harden", "scan", "encrypt"], + "allowed_providers": ["ollama", "claude", "openai"], + "default_provider": "claude", + "fallback_chain": ["claude", "openai", "ollama"], + "intent_routes": { + "audit": "claude", + "harden": "claude", + "scan": "ollama", + "encrypt": "ollama" + }, + "max_input_bytes": 60000, + "rate_limit_per_minute": 15, + "cost_tier": "premium" + }, + "prism": { + "description": "Data analysis, pattern recognition, and forecasting agent", + "allowed_intents": ["analyze", "correlate", "report", "forecast"], + "allowed_providers": ["ollama", "claude", "openai", "gemini"], + "default_provider": "ollama", + "fallback_chain": ["ollama", "gemini", "openai"], + "intent_routes": { + "analyze": "ollama", + "correlate": "ollama", + "report": "claude", + "forecast": "gemini" + }, + "max_input_bytes": 80000, + "rate_limit_per_minute": 30, + "cost_tier": "standard" + } + }, + "cost_tiers": { + "standard": { + "max_requests_per_hour": 200, + "max_tokens_per_request": 2048, + "priority": 1 + }, + "premium": { + "max_requests_per_hour": 100, + "max_tokens_per_request": 4096, + "priority": 2 + } + } +} diff --git a/blackroad-core/protocol/request.json b/blackroad-core/protocol/request.json new file mode 100644 index 0000000000..32c0426c01 --- /dev/null +++ b/blackroad-core/protocol/request.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BlackRoad Gateway Request", + "type": "object", + "required": [ + "agent", + "intent", + "input" + ], + "properties": { + "agent": { + "type": "string", + "minLength": 1 + }, + "intent": { + "type": "string", + "minLength": 1 + }, + "input": { + "type": "string" + }, + "context": { + "type": "object" + }, + "provider": { + "type": "string", + "enum": [ + "ollama", + "claude", + "openai" + ] + }, + "metadata": { + "type": "object" + }, + "request_id": { + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/blackroad-core/protocol/response.json b/blackroad-core/protocol/response.json new file mode 100644 index 0000000000..7d6b567f68 --- /dev/null +++ b/blackroad-core/protocol/response.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "BlackRoad Gateway Response", + "type": "object", + "required": [ + "status", + "output" + ], + "properties": { + "status": { + "type": "string", + "enum": [ + "ok", + "error" + ] + }, + "provider": { + "type": "string", + "enum": [ + "ollama", + "claude", + "openai" + ] + }, + "output": { + "type": "string" + }, + "error": { + "type": "string" + }, + "metadata": { + "type": "object" + }, + "request_id": { + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/blackroad-core/scripts/verify-tokenless-agents.sh b/blackroad-core/scripts/verify-tokenless-agents.sh new file mode 100755 index 0000000000..3e00c72240 --- /dev/null +++ b/blackroad-core/scripts/verify-tokenless-agents.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +AGENT_DIR="${SCRIPT_DIR}/../agents" + +if ! command -v rg >/dev/null 2>&1; then + echo 'Error: rg is required for tokenless agent checks' >&2 + exit 2 +fi + +matches=$( + rg -n \ + -e 'OPENAI_API_KEY' \ + -e 'ANTHROPIC_API_KEY' \ + -e 'CLOUDFLARE_API_TOKEN' \ + -e 'api.openai.com' \ + -e 'api.anthropic.com' \ + -e 'api.cloudflare.com' \ + -e 'openai.com' \ + -e 'anthropic.com' \ + -e 'cloudflare.com' \ + "$AGENT_DIR" || true +) + +if [ -n "$matches" ]; then + echo 'Forbidden vendor references detected in agent code:' >&2 + echo "$matches" >&2 + exit 1 +fi + +echo 'OK: agents are tokenless and vendor-agnostic.' diff --git a/blackroad-core/tests/gateway.test.js b/blackroad-core/tests/gateway.test.js new file mode 100644 index 0000000000..ac07745238 --- /dev/null +++ b/blackroad-core/tests/gateway.test.js @@ -0,0 +1,318 @@ +'use strict' + +// Tests for BlackRoad Core Gateway v2 +// Run: node tests/gateway.test.js + +process.env.NODE_ENV = 'test' + +const { + RateLimiter, + validateRequest, + pickProvider, + buildSystemPrompt, + mergeConfig, + metrics +} = require('../gateway/server') + +let passed = 0 +let failed = 0 + +function assert(condition, msg) { + if (condition) { + passed++ + console.log(` \x1b[32m✓\x1b[0m ${msg}`) + } else { + failed++ + console.log(` \x1b[31m✗\x1b[0m ${msg}`) + } +} + +function assertEqual(actual, expected, msg) { + assert(actual === expected, `${msg} (got ${JSON.stringify(actual)}, expected ${JSON.stringify(expected)})`) +} + +function assertNull(actual, msg) { + assert(actual === null, `${msg} (got ${JSON.stringify(actual)})`) +} + +function suite(name, fn) { + console.log(`\n\x1b[1m${name}\x1b[0m`) + fn() +} + +// --------------------------------------------------------------------------- +// validateRequest +// --------------------------------------------------------------------------- +suite('validateRequest', () => { + assertNull( + validateRequest({ agent: 'planner', intent: 'analyze', input: 'hello' }), + 'accepts valid request' + ) + + assertEqual( + validateRequest(null), + 'Invalid JSON payload', + 'rejects null' + ) + + assertEqual( + validateRequest({}), + 'Missing agent', + 'rejects missing agent' + ) + + assertEqual( + validateRequest({ agent: 'planner' }), + 'Missing intent', + 'rejects missing intent' + ) + + assertEqual( + validateRequest({ agent: 'planner', intent: 'analyze' }), + 'Missing input', + 'rejects missing input' + ) + + assertEqual( + validateRequest({ agent: 'planner', intent: 'analyze', input: 'hi', context: 'bad' }), + 'Context must be an object', + 'rejects non-object context' + ) + + assertNull( + validateRequest({ agent: 'planner', intent: 'analyze', input: 'hi', context: { foo: 1 } }), + 'accepts valid context object' + ) +}) + +// --------------------------------------------------------------------------- +// pickProvider +// --------------------------------------------------------------------------- +suite('pickProvider', () => { + const policy = { + default_provider: 'ollama', + intent_routes: { + analyze: 'ollama', + plan: 'claude' + } + } + + assertEqual( + pickProvider('openai', policy, 'analyze'), + 'openai', + 'explicit provider takes priority' + ) + + assertEqual( + pickProvider(null, policy, 'plan'), + 'claude', + 'uses intent route when no explicit provider' + ) + + assertEqual( + pickProvider(null, policy, 'analyze'), + 'ollama', + 'uses intent route for analyze' + ) + + assertEqual( + pickProvider(null, policy, 'unknown'), + 'ollama', + 'falls back to default provider for unknown intent' + ) + + assertEqual( + pickProvider(null, { intent_routes: {} }, 'anything'), + null, + 'returns null when no default provider' + ) +}) + +// --------------------------------------------------------------------------- +// buildSystemPrompt +// --------------------------------------------------------------------------- +suite('buildSystemPrompt', () => { + const prompts = { + default: 'You are a BlackRoad agent.', + agents: { + planner: 'You plan things.', + octavia: 'You architect systems.' + }, + intents: { + analyze: 'Analyze carefully.', + plan: 'Create phased plans.' + } + } + + const result = buildSystemPrompt(prompts, 'planner', 'analyze', null) + assert(result.includes('BlackRoad agent'), 'includes default prompt') + assert(result.includes('plan things'), 'includes agent prompt') + assert(result.includes('Analyze carefully'), 'includes intent prompt') + + const withContext = buildSystemPrompt(prompts, 'planner', 'analyze', { phase: 'Q1' }) + assert(withContext.includes('Context JSON'), 'includes context') + assert(withContext.includes('"phase"'), 'includes context data') + + assertEqual( + buildSystemPrompt(null, 'planner', 'analyze', null), + '', + 'returns empty string when no prompts' + ) + + const unknownAgent = buildSystemPrompt(prompts, 'unknown', 'unknown', null) + assert(unknownAgent.includes('BlackRoad agent'), 'includes default for unknown agent') + assert(!unknownAgent.includes('plan things'), 'does not include wrong agent prompt') +}) + +// --------------------------------------------------------------------------- +// RateLimiter +// --------------------------------------------------------------------------- +suite('RateLimiter', () => { + const limiter = new RateLimiter() + + assert(limiter.check('test-agent', 5), 'allows first request') + assertEqual(limiter.getUsage('test-agent'), 0, 'usage is 0 before recording') + + limiter.record('test-agent') + limiter.record('test-agent') + limiter.record('test-agent') + assertEqual(limiter.getUsage('test-agent'), 3, 'usage reflects recorded requests') + + assert(limiter.check('test-agent', 5), 'allows when under limit') + + limiter.record('test-agent') + limiter.record('test-agent') + assertEqual(limiter.getUsage('test-agent'), 5, 'usage at limit') + assert(!limiter.check('test-agent', 5), 'rejects when at limit') + + assert(limiter.check('test-agent', 0), 'allows when limit is 0 (disabled)') + assert(limiter.check('other-agent', 10), 'allows other agents independently') +}) + +// --------------------------------------------------------------------------- +// mergeConfig +// --------------------------------------------------------------------------- +suite('mergeConfig', () => { + const base = { + bind: '127.0.0.1', + port: 8787, + policyPath: '/default/policy.json', + promptPath: '/default/prompts.json', + logPath: '/default/logs/gateway.jsonl', + maxBodyBytes: 1048576, + allowRemote: false + } + + const result = mergeConfig(base, { port: 9000 }) + assertEqual(result.port, 9000, 'overrides port') + assertEqual(result.bind, '127.0.0.1', 'preserves bind') + + const remoteResult = mergeConfig(base, { allowRemote: true }) + assertEqual(remoteResult.allowRemote, true, 'overrides allowRemote') + + const noOverride = mergeConfig(base, {}) + assertEqual(noOverride.port, 8787, 'preserves defaults when no override') +}) + +// --------------------------------------------------------------------------- +// Metrics +// --------------------------------------------------------------------------- +suite('Metrics', () => { + // Reset metrics for clean test + metrics.totalRequests = 0 + metrics.totalErrors = 0 + metrics.totalOk = 0 + metrics.byAgent = {} + metrics.byProvider = {} + + metrics.record('planner', 'ollama', 'ok') + metrics.record('planner', 'claude', 'ok') + metrics.record('octavia', 'claude', 'error') + + const snap = metrics.snapshot() + assertEqual(snap.total_requests, 3, 'tracks total requests') + assertEqual(snap.total_ok, 2, 'tracks ok responses') + assertEqual(snap.total_errors, 1, 'tracks errors') + assertEqual(snap.by_agent.planner, 2, 'tracks by agent') + assertEqual(snap.by_agent.octavia, 1, 'tracks octavia') + assertEqual(snap.by_provider.ollama, 1, 'tracks ollama') + assertEqual(snap.by_provider.claude, 2, 'tracks claude') + assert(snap.uptime_seconds >= 0, 'reports uptime') +}) + +// --------------------------------------------------------------------------- +// Provider registry +// --------------------------------------------------------------------------- +suite('Provider registry', () => { + const { getProvider } = require('../gateway/providers') + + assert(getProvider('ollama') !== null, 'ollama provider exists') + assert(getProvider('openai') !== null, 'openai provider exists') + assert(getProvider('claude') !== null, 'claude provider exists') + assert(getProvider('anthropic') !== null, 'anthropic provider exists') + assert(getProvider('gemini') !== null, 'gemini provider exists') + assertNull(getProvider('nonexistent'), 'returns null for unknown provider') + + assert(typeof getProvider('ollama').invoke === 'function', 'ollama has invoke') + assert(typeof getProvider('gemini').invoke === 'function', 'gemini has invoke') +}) + +// --------------------------------------------------------------------------- +// Policy structure validation +// --------------------------------------------------------------------------- +suite('Policy file structure', () => { + const policy = require('../policies/agent-permissions.json') + + assertEqual(policy.version, 2, 'policy version is 2') + assert(policy.global !== undefined, 'has global config') + assert(policy.global.rate_limit_per_minute > 0, 'has global rate limit') + + const expectedAgents = ['planner', 'octavia', 'lucidia', 'alice', 'cipher', 'prism'] + for (const name of expectedAgents) { + assert(policy.agents[name] !== undefined, `has ${name} agent`) + assert(Array.isArray(policy.agents[name].allowed_intents), `${name} has allowed_intents`) + assert(Array.isArray(policy.agents[name].allowed_providers), `${name} has allowed_providers`) + assert(typeof policy.agents[name].default_provider === 'string', `${name} has default_provider`) + assert(Array.isArray(policy.agents[name].fallback_chain), `${name} has fallback_chain`) + assert(policy.agents[name].max_input_bytes > 0, `${name} has max_input_bytes`) + } + + assert(policy.cost_tiers !== undefined, 'has cost tiers') + assert(policy.cost_tiers.standard !== undefined, 'has standard tier') + assert(policy.cost_tiers.premium !== undefined, 'has premium tier') +}) + +// --------------------------------------------------------------------------- +// System prompts structure +// --------------------------------------------------------------------------- +suite('System prompts structure', () => { + const prompts = require('../gateway/system-prompts.json') + + assert(typeof prompts.default === 'string', 'has default prompt') + assert(prompts.default.length > 20, 'default prompt is substantial') + + const expectedAgents = ['planner', 'octavia', 'lucidia', 'alice', 'cipher', 'prism'] + for (const name of expectedAgents) { + assert(typeof prompts.agents[name] === 'string', `has ${name} agent prompt`) + assert(prompts.agents[name].length > 50, `${name} prompt is substantial`) + } + + const expectedIntents = [ + 'analyze', 'plan', 'architect', 'review', 'optimize', 'diagnose', + 'vision', 'synthesize', 'mentor', 'explore', + 'deploy', 'automate', 'route', 'monitor', + 'audit', 'harden', 'scan', 'encrypt', + 'correlate', 'report', 'forecast' + ] + for (const intent of expectedIntents) { + assert(typeof prompts.intents[intent] === 'string', `has ${intent} intent prompt`) + } +}) + +// --------------------------------------------------------------------------- +// Summary +// --------------------------------------------------------------------------- +console.log(`\n${'='.repeat(50)}`) +console.log(`\x1b[1mResults: ${passed} passed, ${failed} failed\x1b[0m`) +console.log(`${'='.repeat(50)}`) +process.exit(failed > 0 ? 1 : 0) diff --git a/blackroad-core/tests/operator.golden b/blackroad-core/tests/operator.golden new file mode 100644 index 0000000000..d96eb875e5 --- /dev/null +++ b/blackroad-core/tests/operator.golden @@ -0,0 +1,2 @@ +Unknown command: agent +Run 'br help' for usage diff --git a/blackroad-docs b/blackroad-docs new file mode 160000 index 0000000000..0bfbf03b37 --- /dev/null +++ b/blackroad-docs @@ -0,0 +1 @@ +Subproject commit 0bfbf03b37d295b741f6a6392a9f7fb3921f79a9 diff --git a/blackroad-gateway b/blackroad-gateway new file mode 160000 index 0000000000..ed63beceec --- /dev/null +++ b/blackroad-gateway @@ -0,0 +1 @@ +Subproject commit ed63beceec46f6fcd1cb395df5de7e24599d6429 diff --git a/blackroad-hardware/projects/wavecube/README.md b/blackroad-hardware/projects/wavecube/README.md new file mode 100644 index 0000000000..ae5828bd2a --- /dev/null +++ b/blackroad-hardware/projects/wavecube/README.md @@ -0,0 +1,171 @@ +# WaveQube — Hologram Projector Hack + +Gutted WaveQube wave lamp turned into a DLP projector driven by a Raspberry Pi. Projects generative art, audio-reactive visuals, and an animated robot with emotions onto your wall. + +## Hardware + +### Bill of Materials + +| Component | Cost | Source | +|-----------|------|--------| +| TI DLP LightCrafter 2000 EVM (DLP2000) | ~$99 | TI Store / Mouser / Digikey | +| MickMake Pi Projector PCB adapter | ~$4 | mickmake.com | +| Raspberry Pi Zero 2W (or Pi 4) | $15-55 | Already in fleet | +| 5V 3A USB-C power supply | ~$10 | Amazon / existing | +| Micro HDMI ribbon / GPIO header | ~$5 | Amazon | +| 30mm fan (optional cooling) | ~$3 | Amazon | +| **Total** | **~$130** | | + +### DLP2000 Specs +- **Resolution**: 640 x 360 (nHD) +- **Brightness**: ~20 lumens (dim/dark rooms) +- **Board size**: 54mm x 76mm +- **Power**: 5V @ 3A (shared rail with Pi) +- **Interface**: RGB666 18-bit DPI via GPIO +- **Throw**: ~1:1 to 1.5:1 (4-7 ft for ~50" image) + +### Build Steps + +1. **Gut the WaveQube** — Remove motor disc, LED board, IR receiver, control PCB. Keep cube shell and lens aperture. +2. **Mount DLP2000** — Align projection lens with aperture opening. Secure with standoffs. +3. **Mount Pi** — Solder MickMake PCB adapter to GPIO. Stack: Pi → adapter → DLP2000 (~25mm total height). +4. **Power** — Single 5V 3A supply to both Pi and DLP. Route through existing USB port hole. +5. **Cooling** — Optional 30mm fan against a vent hole. +6. **Focus** — Manual focus ring on DLP2000 lens. Position 3-6 ft from wall. + +## Software Setup + +### Quick Install (on the Pi) + +```bash +git clone ~/wavecube +cd ~/wavecube +chmod +x install.sh +./install.sh +sudo reboot +``` + +### Manual Install + +```bash +# 1. Install dependencies +sudo apt-get install -y python3-pip python3-pygame fbi portaudio19-dev +pip3 install pygame numpy pyaudio + +# 2. Configure DPI output (add to /boot/config.txt) +# See config/boot-config.txt for the exact lines + +# 3. Reboot +sudo reboot + +# 4. Test +./wavecube-launcher.sh robot --windowed +``` + +## Modes + +### Robot (default) +Animated robot character with full emotion system. + +```bash +./wavecube-launcher.sh robot +``` + +**Emotions**: neutral, happy, curious, excited, sleepy, surprised, love +**Actions**: wave, dance, jump, pick (grabs floating objects), think + +| Key | Action | +|-----|--------| +| 1-6 | Set emotion (happy/curious/excited/sleepy/surprised/love) | +| 0 | Reset to neutral | +| w/d/j/p/t | Wave / Dance / Jump / Pick / Think | +| SPACE | Random action | +| a | Toggle auto-mood (cycles emotions autonomously) | +| c | Cycle color scheme | +| q/ESC | Quit | + +### Waves +Layered generative sine waves in BlackRoad brand colors. + +```bash +./wavecube-launcher.sh waves +``` + +| Key | Action | +|-----|--------| +| 1-4 | Toggle wave layers | +| r | Randomize wave parameters | +| +/- | Speed up / slow down | +| SPACE | Pause/resume | + +### Audio +Real-time FFT frequency visualization from microphone input. Falls back to simulated audio if no mic. + +```bash +./wavecube-launcher.sh audio +``` + +| Key | Action | +|-----|--------| +| s | Cycle style (bars / wave / circle) | +| m | Toggle mic / simulated | +| +/- | Adjust sensitivity | + +### Slideshow +Rotate through images in the `images/` directory. + +```bash +./wavecube-launcher.sh slideshow +``` + +### Logo +Static single-image projection. + +```bash +./wavecube-launcher.sh logo +# Requires images/blackroad-logo.png +``` + +## Auto-Start + +The installer sets up a systemd service that starts in robot mode on boot. + +```bash +# Check status +sudo systemctl status wavecube + +# Change mode (edit ExecStart line) +sudo nano /etc/systemd/system/wavecube.service +sudo systemctl daemon-reload +sudo systemctl restart wavecube + +# Disable auto-start +sudo systemctl disable wavecube +``` + +## Testing on Desktop + +All Python modes accept `--windowed` to run on a regular display: + +```bash +python3 wavecube_robot.py --windowed +python3 wavecube_viz.py --windowed +python3 wavecube_audio.py --windowed +``` + +## File Structure + +``` +wavecube/ +├── wavecube_robot.py # Animated robot with emotions +├── wavecube_viz.py # Generative waveform art +├── wavecube_audio.py # Audio reactive FFT visuals +├── wavecube-launcher.sh # Mode switcher +├── install.sh # Pi installer +├── requirements.txt # Python dependencies +├── config/ +│ └── boot-config.txt # DPI config for /boot/config.txt +├── systemd/ +│ └── wavecube.service # Auto-start service +└── images/ # Images for slideshow/logo modes +``` diff --git a/blackroad-hardware/projects/wavecube/config/boot-config.txt b/blackroad-hardware/projects/wavecube/config/boot-config.txt new file mode 100644 index 0000000000..71c00cbcd2 --- /dev/null +++ b/blackroad-hardware/projects/wavecube/config/boot-config.txt @@ -0,0 +1,38 @@ +# WaveQube DLP2000 DPI Configuration +# Add these lines to /boot/config.txt on the Raspberry Pi +# +# This configures the Pi's parallel display interface (DPI) to output +# 640x360 @ 60Hz via GPIO to the DLP2000 module through the MickMake +# PCB adapter. +# +# After editing, reboot the Pi for changes to take effect. + +# Enable DPI 18-bit output +dtoverlay=dpi18 + +# Disable overscan +overscan_left=0 +overscan_right=0 +overscan_top=0 +overscan_bottom=0 + +# Set framebuffer to DLP2000 native resolution +framebuffer_width=640 +framebuffer_height=360 + +# Enable DPI LCD output +enable_dpi_lcd=1 +display_default_lcd=1 + +# DPI output format: 18-bit RGB666 +dpi_group=2 +dpi_mode=87 +dpi_output_format=0x6f016 + +# Custom timing for 640x360 @ 60Hz +# Format: hdisplay hsyncstart hsyncend htotal vdisplay vsyncstart vsyncend vtotal +# hsyncpolarity vsyncpolarity dblclkpolarity pxclkpolarity clock_freq dotclock +dpi_timings=640 0 14 4 12 360 0 2 3 9 0 0 0 60 0 32000000 6 + +# GPU memory allocation (minimum for headless + framebuffer) +gpu_mem=64 diff --git a/blackroad-hardware/projects/wavecube/install.sh b/blackroad-hardware/projects/wavecube/install.sh new file mode 100755 index 0000000000..26326267e4 --- /dev/null +++ b/blackroad-hardware/projects/wavecube/install.sh @@ -0,0 +1,242 @@ +#!/bin/bash +# WaveQube DLP Projector — Install Script +# +# Run this on the Raspberry Pi to set up the WaveQube projector software. +# Handles dependencies, DPI config, systemd service, and file deployment. +# +# Usage: +# ./install.sh — Full install +# ./install.sh deps — Install dependencies only +# ./install.sh config — Configure DPI output only +# ./install.sh service — Install systemd service only +# ./install.sh test — Test that everything works + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +INSTALL_DIR="/home/pi/wavecube" + +# Colors +PINK='\033[38;5;205m' +CYAN='\033[0;36m' +GREEN='\033[38;5;82m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +RESET='\033[0m' + +log() { echo -e "${GREEN}[+]${RESET} $1"; } +warn() { echo -e "${YELLOW}[!]${RESET} $1"; } +err() { echo -e "${RED}[x]${RESET} $1" >&2; } + +header() { + echo -e "${PINK}" + echo " ╔══════════════════════════════════════╗" + echo " ║ WaveQube Projector Installer ║" + echo " ╚══════════════════════════════════════╝" + echo -e "${RESET}" +} + +install_deps() { + log "Installing system dependencies..." + sudo apt-get update -qq + sudo apt-get install -y -qq \ + python3-pip \ + python3-pygame \ + python3-numpy \ + fbi \ + portaudio19-dev \ + python3-pyaudio \ + libsdl2-dev \ + libsdl2-mixer-dev \ + libsdl2-image-dev \ + libsdl2-ttf-dev + + log "Installing Python packages..." + pip3 install --user --break-system-packages pygame numpy pyaudio 2>/dev/null || \ + pip3 install --user pygame numpy pyaudio 2>/dev/null || \ + pip3 install pygame numpy pyaudio + + log "Dependencies installed." +} + +configure_dpi() { + log "Configuring DPI output for DLP2000..." + BOOT_CONFIG="/boot/config.txt" + if [ ! -f "$BOOT_CONFIG" ]; then + BOOT_CONFIG="/boot/firmware/config.txt" # Newer Pi OS + fi + + if [ ! -f "$BOOT_CONFIG" ]; then + err "Cannot find boot config.txt. Is this a Raspberry Pi?" + return 1 + fi + + # Check if already configured + if grep -q "enable_dpi_lcd=1" "$BOOT_CONFIG" 2>/dev/null; then + warn "DPI already configured in $BOOT_CONFIG" + echo " To reconfigure, remove the WaveQube DPI lines and re-run." + return 0 + fi + + log "Backing up $BOOT_CONFIG..." + sudo cp "$BOOT_CONFIG" "${BOOT_CONFIG}.wavecube.bak" + + log "Appending DPI configuration..." + sudo tee -a "$BOOT_CONFIG" > /dev/null << 'DPIEOF' + +# --- WaveQube DLP2000 DPI Configuration --- +dtoverlay=dpi18 +overscan_left=0 +overscan_right=0 +overscan_top=0 +overscan_bottom=0 +framebuffer_width=640 +framebuffer_height=360 +enable_dpi_lcd=1 +display_default_lcd=1 +dpi_group=2 +dpi_mode=87 +dpi_output_format=0x6f016 +dpi_timings=640 0 14 4 12 360 0 2 3 9 0 0 0 60 0 32000000 6 +gpu_mem=64 +# --- End WaveQube Config --- +DPIEOF + + log "DPI configured. Reboot required for changes to take effect." +} + +deploy_files() { + log "Deploying WaveQube files to ${INSTALL_DIR}..." + mkdir -p "${INSTALL_DIR}/images" + mkdir -p "${INSTALL_DIR}/config" + + # Copy scripts + cp "${SCRIPT_DIR}/wavecube_viz.py" "${INSTALL_DIR}/" + cp "${SCRIPT_DIR}/wavecube_audio.py" "${INSTALL_DIR}/" + cp "${SCRIPT_DIR}/wavecube_robot.py" "${INSTALL_DIR}/" + cp "${SCRIPT_DIR}/wavecube-launcher.sh" "${INSTALL_DIR}/" + cp "${SCRIPT_DIR}/requirements.txt" "${INSTALL_DIR}/" + cp "${SCRIPT_DIR}/config/boot-config.txt" "${INSTALL_DIR}/config/" + + # Copy images if any exist + if ls "${SCRIPT_DIR}/images/"* &>/dev/null 2>&1; then + cp "${SCRIPT_DIR}/images/"* "${INSTALL_DIR}/images/" + fi + + chmod +x "${INSTALL_DIR}/wavecube-launcher.sh" + chmod +x "${INSTALL_DIR}/wavecube_viz.py" + chmod +x "${INSTALL_DIR}/wavecube_audio.py" + chmod +x "${INSTALL_DIR}/wavecube_robot.py" + + log "Files deployed to ${INSTALL_DIR}" +} + +install_service() { + log "Installing systemd service..." + + sudo cp "${SCRIPT_DIR}/systemd/wavecube.service" /etc/systemd/system/wavecube.service + sudo systemctl daemon-reload + sudo systemctl enable wavecube.service + + log "Service installed. Start with: sudo systemctl start wavecube" + log "Default mode: robot (change ExecStart in /etc/systemd/system/wavecube.service)" +} + +run_test() { + log "Running WaveQube tests..." + + # Check Python + if python3 -c "import pygame; print(f' pygame {pygame.ver}')" 2>/dev/null; then + echo -e " ${GREEN}pygame: OK${RESET}" + else + echo -e " ${RED}pygame: MISSING${RESET}" + fi + + if python3 -c "import numpy; print(f' numpy {numpy.__version__}')" 2>/dev/null; then + echo -e " ${GREEN}numpy: OK${RESET}" + else + echo -e " ${RED}numpy: MISSING${RESET}" + fi + + if python3 -c "import pyaudio; print(' pyaudio OK')" 2>/dev/null; then + echo -e " ${GREEN}pyaudio: OK${RESET}" + else + echo -e " ${YELLOW}pyaudio: MISSING (audio mode will use simulation)${RESET}" + fi + + # Check framebuffer + if [ -e /dev/fb0 ]; then + echo -e " ${GREEN}framebuffer: /dev/fb0 exists${RESET}" + else + echo -e " ${YELLOW}framebuffer: /dev/fb0 not found (normal on desktop)${RESET}" + fi + + # Check DPI config + for cfg in /boot/config.txt /boot/firmware/config.txt; do + if [ -f "$cfg" ] && grep -q "enable_dpi_lcd=1" "$cfg" 2>/dev/null; then + echo -e " ${GREEN}DPI config: enabled in $cfg${RESET}" + break + fi + done + + # Check scripts exist + if [ -f "${INSTALL_DIR}/wavecube_robot.py" ]; then + echo -e " ${GREEN}Scripts: deployed to ${INSTALL_DIR}${RESET}" + elif [ -f "${SCRIPT_DIR}/wavecube_robot.py" ]; then + echo -e " ${GREEN}Scripts: found in ${SCRIPT_DIR}${RESET}" + else + echo -e " ${RED}Scripts: not found${RESET}" + fi + + # Check service + if systemctl is-enabled wavecube.service &>/dev/null; then + echo -e " ${GREEN}Service: enabled${RESET}" + else + echo -e " ${YELLOW}Service: not installed${RESET}" + fi + + log "Test complete." +} + +# --- Main --- +header + +case "${1:-full}" in + deps|dependencies) + install_deps + ;; + config|dpi) + configure_dpi + ;; + deploy|files) + deploy_files + ;; + service|systemd) + install_service + ;; + test|check) + run_test + ;; + full|install|"") + install_deps + deploy_files + configure_dpi + install_service + echo "" + log "Full installation complete!" + echo "" + echo -e " ${CYAN}Next steps:${RESET}" + echo " 1. Reboot the Pi: sudo reboot" + echo " 2. Test manually: ~/wavecube/wavecube-launcher.sh robot" + echo " 3. The service auto-starts on boot in robot mode" + echo "" + echo -e " ${PINK}Change default mode:${RESET}" + echo " sudo nano /etc/systemd/system/wavecube.service" + echo " (change 'robot' to: waves, audio, slideshow)" + echo "" + run_test + ;; + *) + echo "Usage: $0 [full|deps|config|deploy|service|test]" + ;; +esac diff --git a/blackroad-hardware/projects/wavecube/requirements.txt b/blackroad-hardware/projects/wavecube/requirements.txt new file mode 100644 index 0000000000..12d918f27c --- /dev/null +++ b/blackroad-hardware/projects/wavecube/requirements.txt @@ -0,0 +1,3 @@ +pygame>=2.5.0 +numpy>=1.24.0 +pyaudio>=0.2.13 diff --git a/blackroad-hardware/projects/wavecube/systemd/wavecube.service b/blackroad-hardware/projects/wavecube/systemd/wavecube.service new file mode 100644 index 0000000000..dad494d0f2 --- /dev/null +++ b/blackroad-hardware/projects/wavecube/systemd/wavecube.service @@ -0,0 +1,20 @@ +[Unit] +Description=WaveQube DLP Projector +After=multi-user.target +Wants=multi-user.target + +[Service] +Type=simple +User=pi +Group=pi +WorkingDirectory=/home/pi/wavecube +ExecStart=/home/pi/wavecube/wavecube-launcher.sh robot +ExecStop=/home/pi/wavecube/wavecube-launcher.sh off +Restart=on-failure +RestartSec=5 +Environment=DISPLAY=:0 +Environment=SDL_VIDEODRIVER=fbcon +Environment=SDL_FBDEV=/dev/fb0 + +[Install] +WantedBy=multi-user.target diff --git a/blackroad-hardware/projects/wavecube/wavecube-launcher.sh b/blackroad-hardware/projects/wavecube/wavecube-launcher.sh new file mode 100755 index 0000000000..6983f2776c --- /dev/null +++ b/blackroad-hardware/projects/wavecube/wavecube-launcher.sh @@ -0,0 +1,124 @@ +#!/bin/bash +# wavecube-launcher.sh — Mode switcher for WaveQube DLP projector +# +# Usage: ./wavecube-launcher.sh +# +# Modes: +# slideshow — Rotating image gallery from ~/wavecube/images/ +# waves — Generative sine wave art (BlackRoad brand colors) +# audio — Audio reactive FFT bars (mic or simulated) +# robot — Animated robot with emotions and actions +# logo — Static logo projection +# off — Blank the projector (black screen) +# menu — Show this help (default) + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +IMAGE_DIR="${SCRIPT_DIR}/images" + +# Colors +PINK='\033[38;5;205m' +CYAN='\033[0;36m' +GREEN='\033[38;5;82m' +YELLOW='\033[1;33m' +RESET='\033[0m' + +kill_existing() { + # Kill any running wavecube processes + pkill -f "wavecube_viz.py" 2>/dev/null || true + pkill -f "wavecube_audio.py" 2>/dev/null || true + pkill -f "wavecube_robot.py" 2>/dev/null || true + pkill -f "fbi.*${IMAGE_DIR}" 2>/dev/null || true + sleep 0.3 +} + +case "${1:-menu}" in + slideshow) + kill_existing + echo -e "${GREEN}Starting slideshow mode...${RESET}" + if command -v fbi &>/dev/null; then + sudo fbi -T 1 -d /dev/fb0 -noverbose -a -t 10 "${IMAGE_DIR}/" + else + echo -e "${YELLOW}fbi not installed. Using pygame fallback...${RESET}" + python3 "${SCRIPT_DIR}/wavecube_viz.py" "$@" + fi + ;; + + waves) + kill_existing + echo -e "${PINK}Starting generative waveform mode...${RESET}" + python3 "${SCRIPT_DIR}/wavecube_viz.py" + ;; + + audio) + kill_existing + echo -e "${CYAN}Starting audio reactive mode...${RESET}" + python3 "${SCRIPT_DIR}/wavecube_audio.py" + ;; + + robot) + kill_existing + echo -e "${PINK}Starting robot mode...${RESET}" + python3 "${SCRIPT_DIR}/wavecube_robot.py" + ;; + + logo) + kill_existing + echo -e "${GREEN}Projecting logo...${RESET}" + if [ -f "${IMAGE_DIR}/blackroad-logo.png" ]; then + if command -v fbi &>/dev/null; then + sudo fbi -T 1 -d /dev/fb0 -noverbose "${IMAGE_DIR}/blackroad-logo.png" + else + echo -e "${YELLOW}fbi not installed. Place a logo in ${IMAGE_DIR}/blackroad-logo.png${RESET}" + fi + else + echo -e "${YELLOW}Logo not found at ${IMAGE_DIR}/blackroad-logo.png${RESET}" + echo "Place your logo image there and try again." + fi + ;; + + off) + kill_existing + echo -e "${CYAN}Blanking projector...${RESET}" + if [ -e /dev/fb0 ]; then + sudo dd if=/dev/zero of=/dev/fb0 2>/dev/null + fi + echo -e "${GREEN}Projector blanked.${RESET}" + ;; + + status) + echo -e "${PINK}WaveQube Status:${RESET}" + if pgrep -f "wavecube_viz.py" >/dev/null 2>&1; then + echo -e " ${GREEN}Running: waves mode${RESET}" + elif pgrep -f "wavecube_audio.py" >/dev/null 2>&1; then + echo -e " ${GREEN}Running: audio mode${RESET}" + elif pgrep -f "wavecube_robot.py" >/dev/null 2>&1; then + echo -e " ${GREEN}Running: robot mode${RESET}" + elif pgrep -f "fbi" >/dev/null 2>&1; then + echo -e " ${GREEN}Running: slideshow/logo mode${RESET}" + else + echo -e " ${YELLOW}Not running${RESET}" + fi + ;; + + menu|help|--help|-h|*) + echo -e "${PINK}" + echo " ╔══════════════════════════════════════╗" + echo " ║ WaveQube Projector Launcher ║" + echo " ╚══════════════════════════════════════╝" + echo -e "${RESET}" + echo " Usage: $(basename "$0") " + echo "" + echo -e " ${CYAN}slideshow${RESET} — Rotating image gallery" + echo -e " ${PINK}waves${RESET} — Generative waveform art" + echo -e " ${CYAN}audio${RESET} — Audio reactive FFT bars" + echo -e " ${PINK}robot${RESET} — Animated robot with emotions" + echo -e " ${GREEN}logo${RESET} — Static logo projection" + echo -e " ${YELLOW}off${RESET} — Blank the projector" + echo -e " ${CYAN}status${RESET} — Check what's running" + echo "" + echo " Tip: Pass --windowed to Python modes for desktop testing" + echo " Example: $(basename "$0") robot --windowed" + ;; +esac diff --git a/blackroad-hardware/projects/wavecube/wavecube_audio.py b/blackroad-hardware/projects/wavecube/wavecube_audio.py new file mode 100755 index 0000000000..d7e1edf451 --- /dev/null +++ b/blackroad-hardware/projects/wavecube/wavecube_audio.py @@ -0,0 +1,276 @@ +#!/usr/bin/env python3 +"""WaveQube Audio Reactive Projector + +Captures microphone input and renders real-time FFT frequency bars +with BlackRoad brand gradient coloring. Falls back to a simulated +audio mode if no microphone is available (useful for testing). + +Controls: + q / ESC — quit + m — toggle between mic and simulated audio + s — cycle visualization style (bars / wave / circle) + +/- — adjust sensitivity + +Requirements: + pip install pygame numpy pyaudio +""" + +import math +import struct +import sys + +import numpy as np +import pygame + +# --- Display --- +WIDTH, HEIGHT = 640, 360 +FPS = 60 +NUM_BARS = 64 + +# --- BlackRoad Brand Palette --- +BLACK = (0, 0, 0) +HOT_PINK = (255, 29, 108) +AMBER = (245, 166, 35) +ELECTRIC_BLUE = (41, 121, 255) +VIOLET = (156, 39, 176) + +# Try to import PyAudio +try: + import pyaudio + AUDIO_AVAILABLE = True +except ImportError: + AUDIO_AVAILABLE = False + + +def gradient_color(t): + """Interpolate across the brand gradient based on t (0.0 to 1.0).""" + colors = [AMBER, HOT_PINK, VIOLET, ELECTRIC_BLUE] + n = len(colors) - 1 + idx = t * n + i = int(idx) + frac = idx - i + if i >= n: + return colors[-1] + c1, c2 = colors[i], colors[i + 1] + return ( + int(c1[0] + (c2[0] - c1[0]) * frac), + int(c1[1] + (c2[1] - c1[1]) * frac), + int(c1[2] + (c2[2] - c1[2]) * frac), + ) + + +def simulated_fft(t, num_bars): + """Generate fake FFT data for testing without a microphone.""" + bars = np.zeros(num_bars) + for i in range(num_bars): + freq = i / num_bars + bars[i] = ( + 0.5 * math.sin(t * 0.05 + freq * 6) + + 0.3 * math.sin(t * 0.08 + freq * 12) + + 0.2 * math.sin(t * 0.03 + i * 0.5) + + 0.1 * np.random.random() + ) + bars = np.abs(bars) + mx = bars.max() + if mx > 0: + bars /= mx + return bars + + +class AudioCapture: + """Wraps PyAudio for real-time microphone capture.""" + + CHUNK = 1024 + RATE = 44100 + + def __init__(self): + self.pa = pyaudio.PyAudio() + self.stream = self.pa.open( + format=pyaudio.paInt16, + channels=1, + rate=self.RATE, + input=True, + frames_per_buffer=self.CHUNK, + ) + + def read_fft(self, num_bars): + """Read a chunk and return normalized FFT magnitudes.""" + try: + data = self.stream.read(self.CHUNK, exception_on_overflow=False) + except OSError: + return np.zeros(num_bars) + samples = np.array(struct.unpack(f"{self.CHUNK}h", data), dtype=np.float64) + # Apply Hann window to reduce spectral leakage + window = np.hanning(len(samples)) + samples *= window + fft = np.abs(np.fft.rfft(samples))[:num_bars] + mx = fft.max() + if mx > 0: + fft /= mx + return fft + + def close(self): + self.stream.stop_stream() + self.stream.close() + self.pa.terminate() + + +def draw_bars(screen, fft_data, sensitivity): + """Classic vertical frequency bars.""" + bar_w = WIDTH // NUM_BARS + for i, val in enumerate(fft_data): + h = int(val * sensitivity * (HEIGHT - 20)) + h = min(h, HEIGHT) + color = gradient_color(i / NUM_BARS) + x = i * bar_w + # Main bar + pygame.draw.rect(screen, color, (x, HEIGHT - h, bar_w - 1, h)) + # Reflection (dimmed) + if h > 5: + ref_h = min(h // 4, 30) + dim = tuple(c // 4 for c in color) + pygame.draw.rect(screen, dim, (x, HEIGHT, bar_w - 1, ref_h)) + + +def draw_wave(screen, fft_data, sensitivity, t): + """Smooth waveform visualization.""" + points = [] + for i, val in enumerate(fft_data): + x = int(i / NUM_BARS * WIDTH) + y = HEIGHT // 2 - int(val * sensitivity * (HEIGHT // 2 - 10)) + points.append((x, y)) + + if len(points) > 2: + # Draw filled area under curve + filled = points + [(WIDTH, HEIGHT // 2), (0, HEIGHT // 2)] + glow = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA) + pygame.draw.polygon(glow, (*HOT_PINK, 30), filled) + screen.blit(glow, (0, 0)) + # Draw the line + pygame.draw.lines(screen, HOT_PINK, False, points, 3) + + # Mirror below center + mirror_points = [(x, HEIGHT - (y - HEIGHT // 2) + HEIGHT // 2) for x, y in points] + if len(mirror_points) > 2: + pygame.draw.lines(screen, ELECTRIC_BLUE, False, mirror_points, 2) + + +def draw_circle(screen, fft_data, sensitivity, t): + """Circular FFT visualization.""" + cx, cy = WIDTH // 2, HEIGHT // 2 + base_radius = 60 + + for i, val in enumerate(fft_data): + angle = (i / NUM_BARS) * 2 * math.pi - math.pi / 2 + r = base_radius + val * sensitivity * 100 + x = cx + math.cos(angle) * r + y = cy + math.sin(angle) * r + color = gradient_color(i / NUM_BARS) + + # Line from center circle to point + inner_x = cx + math.cos(angle) * base_radius + inner_y = cy + math.sin(angle) * base_radius + pygame.draw.line(screen, color, (int(inner_x), int(inner_y)), (int(x), int(y)), 2) + + # Draw center circle + pygame.draw.circle(screen, HOT_PINK, (cx, cy), base_radius, 1) + + # Rotating accent + accent_angle = t * 0.02 + ax = cx + math.cos(accent_angle) * (base_radius + 10) + ay = cy + math.sin(accent_angle) * (base_radius + 10) + pygame.draw.circle(screen, AMBER, (int(ax), int(ay)), 4) + + +def main(): + pygame.init() + + fullscreen = "--windowed" not in sys.argv + flags = pygame.FULLSCREEN if fullscreen else 0 + screen = pygame.display.set_mode((WIDTH, HEIGHT), flags) + pygame.display.set_caption("WaveQube Audio") + pygame.mouse.set_visible(False) + clock = pygame.time.Clock() + + # Audio setup + audio = None + use_mic = AUDIO_AVAILABLE + if use_mic: + try: + audio = AudioCapture() + except Exception: + use_mic = False + + styles = ["bars", "wave", "circle"] + style_idx = 0 + sensitivity = 1.0 + # Smoothed FFT data for visual continuity + smooth_fft = np.zeros(NUM_BARS) + smoothing = 0.3 # Lower = smoother + + t = 0 + running = True + + # Font for status overlay + font = pygame.font.Font(None, 20) + + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + elif event.type == pygame.KEYDOWN: + if event.key in (pygame.K_q, pygame.K_ESCAPE): + running = False + elif event.key == pygame.K_m: + if AUDIO_AVAILABLE: + use_mic = not use_mic + if use_mic and audio is None: + try: + audio = AudioCapture() + except Exception: + use_mic = False + elif event.key == pygame.K_s: + style_idx = (style_idx + 1) % len(styles) + elif event.key == pygame.K_EQUALS or event.key == pygame.K_PLUS: + sensitivity = min(sensitivity + 0.2, 3.0) + elif event.key == pygame.K_MINUS: + sensitivity = max(sensitivity - 0.2, 0.2) + + # Get FFT data + if use_mic and audio: + raw_fft = audio.read_fft(NUM_BARS) + else: + raw_fft = simulated_fft(t, NUM_BARS) + + # Smooth the data + smooth_fft = smooth_fft * (1 - smoothing) + raw_fft * smoothing + + # Clear + screen.fill(BLACK) + + # Draw selected style + style = styles[style_idx] + if style == "bars": + draw_bars(screen, smooth_fft, sensitivity) + elif style == "wave": + draw_wave(screen, smooth_fft, sensitivity, t) + elif style == "circle": + draw_circle(screen, smooth_fft, sensitivity, t) + + # Status indicator (small, top-left) + src = "MIC" if use_mic else "SIM" + status_text = font.render(f"{src} | {style} | sens:{sensitivity:.1f}", True, (80, 80, 80)) + screen.blit(status_text, (5, 5)) + + pygame.display.flip() + clock.tick(FPS) + t += 1 + + # Cleanup + if audio: + audio.close() + pygame.quit() + + +if __name__ == "__main__": + main() diff --git a/blackroad-hardware/projects/wavecube/wavecube_controller.py b/blackroad-hardware/projects/wavecube/wavecube_controller.py new file mode 100755 index 0000000000..3a3eed9f11 --- /dev/null +++ b/blackroad-hardware/projects/wavecube/wavecube_controller.py @@ -0,0 +1,489 @@ +#!/usr/bin/env python3 +"""WaveQube Serial Controller + +Talk to the WaveQube ESP32 (BLACKROAD OS ENHANCED v2.0) over USB serial. +Launch apps, read events, send commands, monitor touch input, and +interact with the cube from your Mac or Pi. + +Usage: + python3 wavecube_controller.py # Interactive mode + python3 wavecube_controller.py --app dashboard # Launch app directly + python3 wavecube_controller.py --monitor # Monitor all events + python3 wavecube_controller.py --reboot # Reboot the ESP32 + python3 wavecube_controller.py --info # Show device info + python3 wavecube_controller.py --port /dev/XXX # Custom serial port + +Requirements: + pip install pyserial +""" + +import argparse +import sys +import time + +import serial +import serial.tools.list_ports + +# --- Config --- +DEFAULT_BAUD = 115200 + +# Known signatures for auto-detection +WAVECUBE_SIGNATURES = { + "ch340": {"vid": 0x1A86, "pid": 0x7523}, # QinHeng CH340 +} +BOUFFALO_SIGNATURES = { + "bl808": {"vid": 0xFFFF, "pid": 0xFFFF, "manufacturer": "Bouffalo"}, +} + +# --- App Map --- +APPS = { + "dashboard": "1", + "weather": "2", + "crypto": "3", + "browser": "4", + "snake": "5", + "network": "6", + "settings": "7", + "portfolio": "8", +} + +APP_NAMES = {v: k for k, v in APPS.items()} + +# --- Colors --- +PINK = "\033[38;5;205m" +CYAN = "\033[0;36m" +GREEN = "\033[38;5;82m" +YELLOW = "\033[1;33m" +RED = "\033[0;31m" +DIM = "\033[2m" +RESET = "\033[0m" +BOLD = "\033[1m" + + +def find_port_by_signature(signatures): + """Find a serial port matching any of the given VID/PID/manufacturer signatures.""" + for port in serial.tools.list_ports.comports(): + for name, sig in signatures.items(): + vid_match = port.vid == sig.get("vid") if "vid" in sig else True + pid_match = port.pid == sig.get("pid") if "pid" in sig else True + mfg_match = (sig.get("manufacturer", "").lower() in (port.manufacturer or "").lower() + if "manufacturer" in sig else True) + if vid_match and pid_match and mfg_match: + return port.device + return None + + +def find_wavecube_port(): + """Auto-detect the WaveQube ESP32 serial port. + + Tries in order: + 1. Known CH340 VID:PID + 2. Any port with 'usbserial' or 'CH340' in name/description + 3. Probe unknown USB serial ports for the BLACKROAD OS signature + """ + # 1. Known VID:PID + found = find_port_by_signature(WAVECUBE_SIGNATURES) + if found: + return found + + # 2. Name-based matching + for port in serial.tools.list_ports.comports(): + desc = (port.description or "").lower() + dev = (port.device or "").lower() + if "ch340" in desc or "usbserial" in dev: + return port.device + + # 3. Probe USB serial ports for BLACKROAD OS response + candidates = [] + for port in serial.tools.list_ports.comports(): + if port.vid and port.device not in _known_bouffalo_ports(): + candidates.append(port.device) + + for dev in candidates: + if _probe_for_wavecube(dev): + return dev + + return None + + +def find_bouffalo_port(): + """Auto-detect the Bouffalo BL808 shell port (command port at 2Mbaud).""" + ports = [] + for port in serial.tools.list_ports.comports(): + if (port.manufacturer or "").lower() == "bouffalo": + ports.append(port.device) + elif port.vid == 0xFFFF and port.pid == 0xFFFF: + ports.append(port.device) + + # The command shell is typically the lower-numbered modem port + # Probe each to find the one that responds to 'help' + for dev in sorted(ports): + try: + s = serial.Serial(dev, 2000000, timeout=1) + s.reset_input_buffer() + s.write(b'\r\n') + time.sleep(0.3) + data = s.read(256) + s.close() + if data and b'#' in data: + return dev + except Exception: + continue + return ports[0] if ports else None + + +def _known_bouffalo_ports(): + """Return device paths for known Bouffalo ports (to exclude from WaveQube probe).""" + result = [] + for port in serial.tools.list_ports.comports(): + if (port.manufacturer or "").lower() == "bouffalo": + result.append(port.device) + elif port.vid == 0xFFFF and port.pid == 0xFFFF: + result.append(port.device) + return result + + +def _probe_for_wavecube(device, timeout=3): + """Open a serial port and check if it speaks WaveQube protocol.""" + try: + s = serial.Serial(device, DEFAULT_BAUD, timeout=0.5) + s.reset_input_buffer() + # Send '7' then '1' (Settings > reboot) — too destructive + # Instead just listen for [TOUCH] or [BACK] or [LAUNCH] + start = time.time() + while time.time() - start < timeout: + data = s.read(512) + if data: + text = data.decode("utf-8", errors="replace") + if "[TOUCH]" in text or "[BACK]" in text or "[LAUNCH]" in text or "BLACKROAD" in text: + s.close() + return True + s.close() + except Exception: + pass + return False + + +def list_all_devices(): + """List all detected USB serial devices with identification.""" + devices = [] + for port in serial.tools.list_ports.comports(): + if not port.vid: + continue # skip bluetooth/system ports + vid = f"0x{port.vid:04x}" if port.vid else "----" + pid = f"0x{port.pid:04x}" if port.pid else "----" + devices.append({ + "device": port.device, + "product": port.product or port.description or "Unknown", + "manufacturer": port.manufacturer or "Unknown", + "vid_pid": f"{vid}:{pid}", + "serial": port.serial_number or "n/a", + }) + return devices + + +class WaveQube: + """Interface to the WaveQube ESP32 over serial.""" + + def __init__(self, port=None, baud=DEFAULT_BAUD): + self.port = port or find_wavecube_port() + self.baud = baud + self.serial = None + self.current_app = None + self._monitor_thread = None + self._monitoring = False + + def connect(self): + if not self.port: + print(f"{RED}No WaveQube port detected. Is it plugged in?{RESET}") + return False + try: + self.serial = serial.Serial(self.port, self.baud, timeout=0.3) + time.sleep(0.5) + self.serial.reset_input_buffer() + return True + except serial.SerialException as e: + print(f"{RED}Connection failed: {e}{RESET}") + return False + + def disconnect(self): + if self.serial and self.serial.is_open: + self.serial.close() + + def send(self, cmd): + """Send a command and return response lines (filtered).""" + if not self.serial or not self.serial.is_open: + return [] + self.serial.reset_input_buffer() + if isinstance(cmd, str): + cmd = cmd.encode() + self.serial.write(cmd) + time.sleep(0.8) + return self._read_lines() + + def _read_lines(self, timeout=1.5): + """Read and parse response lines, filtering touch spam.""" + lines = [] + start = time.time() + while time.time() - start < timeout: + try: + raw = self.serial.readline() + if raw: + text = raw.decode("utf-8", errors="replace").strip() + if text: + lines.append(text) + else: + if lines: + break + except Exception: + break + return lines + + def launch_app(self, name): + """Launch an app by name or number.""" + if name in APPS: + cmd = APPS[name] + elif name in APP_NAMES: + cmd = name + else: + print(f"{RED}Unknown app: {name}{RESET}") + print(f"Available: {', '.join(APPS.keys())}") + return [] + + lines = self.send(cmd) + for line in lines: + if line.startswith("[LAUNCH]"): + self.current_app = name + return lines + + def back(self): + """Go back to the menu.""" + lines = self.send(b"b") + self.current_app = None + return lines + + def reboot(self): + """Reboot the ESP32 by navigating to Settings and triggering reset.""" + print(f"{YELLOW}Rebooting WaveQube...{RESET}") + self.send(b"b") + time.sleep(0.3) + self.send(b"7") # Settings + time.sleep(0.3) + lines = self.send(b"1") # Triggers reboot + return lines + + def get_boot_info(self): + """Reboot and capture the boot log.""" + lines = self.reboot() + boot_lines = [] + for line in lines: + if not line.startswith("[TOUCH]"): + boot_lines.append(line) + return boot_lines + + def monitor(self, callback=None, filter_touch=True): + """Monitor serial output continuously.""" + if not self.serial or not self.serial.is_open: + return + + self._monitoring = True + while self._monitoring: + try: + raw = self.serial.readline() + if raw: + text = raw.decode("utf-8", errors="replace").strip() + if text: + if filter_touch and text.startswith("[TOUCH]"): + continue + if callback: + callback(text) + else: + self._default_print(text) + except Exception: + if self._monitoring: + time.sleep(0.1) + + def stop_monitor(self): + self._monitoring = False + + @staticmethod + def _default_print(text): + if text.startswith("[LAUNCH]"): + print(f"{GREEN}{text}{RESET}") + elif text.startswith("[BACK]"): + print(f"{CYAN}{text}{RESET}") + elif text.startswith("[WARN]"): + print(f"{YELLOW}{text}{RESET}") + elif text.startswith("[SNAKE]"): + print(f"{PINK}{text}{RESET}") + elif text.startswith("[READY]"): + print(f"{GREEN}{text}{RESET}") + elif text.startswith("[OK]"): + print(f"{GREEN}{text}{RESET}") + elif "BLACKROAD" in text: + print(f"{PINK}{BOLD}{text}{RESET}") + else: + print(f"{DIM}{text}{RESET}") + + +def interactive_mode(cube): + """Interactive REPL for controlling the WaveQube.""" + print(f""" +{PINK}╔══════════════════════════════════════════╗ +║ WaveQube Controller v1.0 ║ +║ BLACKROAD OS ENHANCED v2.0 ║ +╠══════════════════════════════════════════╣ +║ {CYAN}Apps:{PINK} ║ +║ 1=Dashboard 2=Weather 3=Crypto ║ +║ 4=Browser 5=Snake 6=Network ║ +║ 7=Settings 8=Portfolio ║ +║ {CYAN}Commands:{PINK} ║ +║ b=Back r=Reboot m=Monitor q=Quit ║ +║ Type app name or number to launch ║ +╚══════════════════════════════════════════╝{RESET} +""") + + while True: + try: + app_str = f" [{cube.current_app}]" if cube.current_app else "" + cmd = input(f"{PINK}wavecube{CYAN}{app_str}{RESET}> ").strip().lower() + except (EOFError, KeyboardInterrupt): + print() + break + + if not cmd: + continue + elif cmd in ("q", "quit", "exit"): + break + elif cmd == "b" or cmd == "back": + lines = cube.back() + for l in lines: + if not l.startswith("[TOUCH]"): + cube._default_print(l) + elif cmd in ("r", "reboot"): + lines = cube.reboot() + for l in lines: + cube._default_print(l) + elif cmd in ("m", "monitor"): + print(f"{DIM}Monitoring... press Ctrl+C to stop{RESET}") + try: + cube.monitor(filter_touch=True) + except KeyboardInterrupt: + cube.stop_monitor() + print(f"\n{CYAN}Monitor stopped.{RESET}") + elif cmd in ("mt", "monitor-touch"): + print(f"{DIM}Monitoring with touch... press Ctrl+C to stop{RESET}") + try: + cube.monitor(filter_touch=False) + except KeyboardInterrupt: + cube.stop_monitor() + print(f"\n{CYAN}Monitor stopped.{RESET}") + elif cmd in ("i", "info"): + print(f" Port: {cube.port}") + print(f" Baud: {cube.baud}") + print(f" App: {cube.current_app or 'menu'}") + elif cmd in ("?", "help"): + print(f" {GREEN}1-8{RESET} Launch app by number") + print(f" {GREEN}{RESET} Launch app by name (dashboard, snake, etc)") + print(f" {CYAN}b/back{RESET} Return to menu") + print(f" {CYAN}r/reboot{RESET} Reboot the ESP32") + print(f" {CYAN}m/monitor{RESET} Watch serial output (Ctrl+C to stop)") + print(f" {CYAN}mt{RESET} Monitor including touch events") + print(f" {CYAN}i/info{RESET} Show connection info") + print(f" {CYAN}q/quit{RESET} Exit") + elif cmd in APPS: + lines = cube.launch_app(cmd) + for l in lines: + if not l.startswith("[TOUCH]"): + cube._default_print(l) + elif cmd in APP_NAMES: + lines = cube.launch_app(cmd) + for l in lines: + if not l.startswith("[TOUCH]"): + cube._default_print(l) + elif len(cmd) == 1 and cmd in "12345678": + lines = cube.send(cmd.encode()) + for l in lines: + if not l.startswith("[TOUCH]"): + cube._default_print(l) + else: + # Try sending raw + lines = cube.send(cmd.encode() + b"\r\n") + printed = False + for l in lines: + if not l.startswith("[TOUCH]"): + cube._default_print(l) + printed = True + if not printed: + print(f"{DIM}(no response){RESET}") + + +def main(): + parser = argparse.ArgumentParser(description="WaveQube Serial Controller") + parser.add_argument("--port", default=None, help="Serial port (auto-detects CH340)") + parser.add_argument("--baud", type=int, default=DEFAULT_BAUD, help="Baud rate") + parser.add_argument("--app", choices=list(APPS.keys()), help="Launch app directly") + parser.add_argument("--monitor", action="store_true", help="Monitor serial output") + parser.add_argument("--reboot", action="store_true", help="Reboot the ESP32") + parser.add_argument("--info", action="store_true", help="Show boot info") + parser.add_argument("--list-ports", action="store_true", help="List serial ports") + args = parser.parse_args() + + if args.list_ports: + print("Available serial ports:") + for port in serial.tools.list_ports.comports(): + vid = f"0x{port.vid:04x}" if port.vid else "----" + pid = f"0x{port.pid:04x}" if port.pid else "----" + print(f" {port.device} [{vid}:{pid}] {port.description}") + return + + cube = WaveQube(port=args.port, baud=args.baud) + + if cube.port: + print(f"{CYAN}Connecting to WaveQube on {cube.port}...{RESET}") + else: + print(f"{RED}No WaveQube detected.{RESET}") + devices = list_all_devices() + if devices: + print(f"{YELLOW}USB serial devices found:{RESET}") + for d in devices: + print(f" {d['device']} [{d['vid_pid']}] {d['product']} ({d['manufacturer']})") + print(f"{YELLOW}Try: --port {RESET}") + else: + print(f"{RED}No USB serial devices found. Is the WaveQube plugged in?{RESET}") + sys.exit(1) + + if not cube.connect(): + sys.exit(1) + + print(f"{GREEN}Connected to {cube.port}!{RESET}") + + try: + if args.reboot: + lines = cube.reboot() + for l in lines: + cube._default_print(l) + elif args.info: + lines = cube.get_boot_info() + for l in lines: + cube._default_print(l) + elif args.app: + lines = cube.launch_app(args.app) + for l in lines: + cube._default_print(l) + elif args.monitor: + print(f"{DIM}Monitoring... press Ctrl+C to stop{RESET}") + try: + cube.monitor(filter_touch=True) + except KeyboardInterrupt: + cube.stop_monitor() + print() + else: + interactive_mode(cube) + finally: + cube.disconnect() + print(f"{DIM}Disconnected.{RESET}") + + +if __name__ == "__main__": + main() diff --git a/blackroad-hardware/projects/wavecube/wavecube_robot.py b/blackroad-hardware/projects/wavecube/wavecube_robot.py new file mode 100755 index 0000000000..0fbf335e8c --- /dev/null +++ b/blackroad-hardware/projects/wavecube/wavecube_robot.py @@ -0,0 +1,717 @@ +#!/usr/bin/env python3 +"""WaveQube Robot Projector — Emotional Animated Robot + +Projects an animated robot character with a full emotion system. +The robot autonomously cycles through moods, performs actions (wave, +dance, jump, pick up objects, think), and expresses emotions through +eyes, mouth, body language, core color, and floating emotion particles. + +Controls: + q / ESC — quit + SPACE — trigger random action + 1 — set mood: happy + 2 — set mood: curious + 3 — set mood: excited + 4 — set mood: sleepy + 5 — set mood: surprised + 6 — set mood: love + 0 — set mood: neutral + w — action: wave + d — action: dance + j — action: jump + p — action: pick up + t — action: think + a — toggle auto-mood (cycles emotions on its own) + c — cycle color scheme + --windowed — run in a window instead of fullscreen + +Requirements: + pip install pygame +""" + +import math +import random +import sys + +import pygame + +# --- Display --- +WIDTH, HEIGHT = 640, 360 +FPS = 60 + +# --- BlackRoad Brand Palette --- +BLACK = (0, 0, 0) +HOT_PINK = (255, 29, 108) +AMBER = (245, 166, 35) +ELECTRIC_BLUE = (41, 121, 255) +VIOLET = (156, 39, 176) +WHITE = (255, 255, 255) +DARK_GRAY = (30, 30, 30) +SOFT_RED = (255, 80, 80) +SOFT_GREEN = (80, 255, 120) + +COLOR_SCHEMES = [ + {"body": ELECTRIC_BLUE, "accent": HOT_PINK, "eye": AMBER, "glow": VIOLET}, + {"body": HOT_PINK, "accent": AMBER, "eye": ELECTRIC_BLUE, "glow": VIOLET}, + {"body": VIOLET, "accent": ELECTRIC_BLUE, "eye": HOT_PINK, "glow": AMBER}, + {"body": AMBER, "accent": VIOLET, "eye": ELECTRIC_BLUE, "glow": HOT_PINK}, +] + +# Emotion definitions +EMOTIONS = { + "neutral": {"eye": "round", "mouth": "line", "blink_rate": 1.0, "breath_rate": 1.0, "core_color_mix": 0.0}, + "happy": {"eye": "arc", "mouth": "smile", "blink_rate": 0.7, "breath_rate": 1.2, "core_color_mix": 0.3}, + "curious": {"eye": "big", "mouth": "small_o","blink_rate": 1.5, "breath_rate": 0.8, "core_color_mix": 0.2}, + "excited": {"eye": "star", "mouth": "wide", "blink_rate": 0.5, "breath_rate": 2.0, "core_color_mix": 0.6}, + "sleepy": {"eye": "half", "mouth": "line", "blink_rate": 3.0, "breath_rate": 0.5, "core_color_mix": 0.1}, + "surprised": {"eye": "wide", "mouth": "big_o", "blink_rate": 0.3, "breath_rate": 1.5, "core_color_mix": 0.4}, + "love": {"eye": "heart", "mouth": "smile", "blink_rate": 0.8, "breath_rate": 1.0, "core_color_mix": 0.5}, +} + +# Actions the robot can perform +ACTIONS = ["idle", "wave", "dance", "jump", "pick", "think"] + + +def dim(color, factor): + return tuple(max(0, min(255, int(c * factor))) for c in color) + + +def lerp_color(c1, c2, t): + t = max(0.0, min(1.0, t)) + return tuple(int(c1[i] + (c2[i] - c1[i]) * t) for i in range(3)) + + +class EmotionParticle: + """Floating particle that represents the robot's current emotion.""" + + SYMBOLS = { + "happy": "~", + "curious": "?", + "excited": "!", + "sleepy": "z", + "surprised": "!", + "love": "<3", + "neutral": ".", + } + + def __init__(self, x, y, emotion, color): + self.x = x + random.uniform(-20, 20) + self.y = y + self.vy = random.uniform(-1.5, -0.5) + self.vx = random.uniform(-0.5, 0.5) + self.life = 1.0 + self.decay = random.uniform(0.008, 0.015) + self.text = self.SYMBOLS.get(emotion, ".") + self.color = color + self.size = random.randint(14, 22) + + def update(self): + self.x += self.vx + self.y += self.vy + self.life -= self.decay + + def draw(self, screen, font): + if self.life <= 0: + return + alpha = max(0, min(255, int(self.life * 255))) + surf = font.render(self.text, True, self.color) + surf.set_alpha(alpha) + screen.blit(surf, (int(self.x), int(self.y))) + + +class PickedObject: + """An object the robot can pick up and hold.""" + + OBJECTS = [ + {"name": "star", "color": AMBER}, + {"name": "gem", "color": VIOLET}, + {"name": "orb", "color": ELECTRIC_BLUE}, + {"name": "heart", "color": HOT_PINK}, + ] + + def __init__(self): + obj = random.choice(self.OBJECTS) + self.name = obj["name"] + self.color = obj["color"] + self.bob_phase = random.uniform(0, math.pi * 2) + + def draw(self, screen, x, y, t): + bob = math.sin(t * 0.1 + self.bob_phase) * 3 + y = int(y + bob) + + if self.name == "star": + self._draw_star(screen, x, y) + elif self.name == "gem": + self._draw_gem(screen, x, y) + elif self.name == "orb": + pygame.draw.circle(screen, self.color, (x, y), 8) + pygame.draw.circle(screen, WHITE, (x - 2, y - 2), 3) + elif self.name == "heart": + self._draw_heart(screen, x, y) + + def _draw_star(self, screen, x, y): + points = [] + for i in range(10): + angle = math.pi / 2 + i * math.pi / 5 + r = 10 if i % 2 == 0 else 5 + points.append((x + math.cos(angle) * r, y - math.sin(angle) * r)) + pygame.draw.polygon(screen, self.color, points) + + def _draw_gem(self, screen, x, y): + points = [(x, y - 10), (x + 8, y - 2), (x + 5, y + 8), (x - 5, y + 8), (x - 8, y - 2)] + pygame.draw.polygon(screen, self.color, points) + pygame.draw.polygon(screen, WHITE, points, 1) + + def _draw_heart(self, screen, x, y): + # Two circles + triangle + r = 5 + pygame.draw.circle(screen, self.color, (x - 4, y - 3), r) + pygame.draw.circle(screen, self.color, (x + 4, y - 3), r) + pygame.draw.polygon(screen, self.color, [(x - 9, y), (x + 9, y), (x, y + 10)]) + + +class Robot: + """Animated robot with emotions and actions.""" + + def __init__(self, cx, cy): + self.cx = cx + self.base_cy = cy + self.cy = cy + self.scheme_idx = 0 + self.auto_mood = True + + # Emotion state + self.mood = "neutral" + self.mood_intensity = 0.0 # 0..1 transition + self.mood_target = 1.0 + self.mood_timer = 0 + self.mood_duration = random.randint(300, 600) # frames before auto-switching + + # Action state + self.action = "idle" + self.action_timer = 0 + self.action_duration = 0 + + # Blink + self.blink_timer = 0 + self.blink_interval = random.randint(180, 360) + self.is_blinking = False + self.blink_frames = 0 + + # Animation phases + self.breath_phase = 0.0 + self.antenna_phase = 0.0 + self.dance_phase = 0.0 + self.jump_offset = 0.0 + self.jump_velocity = 0.0 + + # Held object + self.held_object = None + + # Particles + self.particles = [] + + # Thought bubble + self.thought_text = "" + self.thought_timer = 0 + + @property + def colors(self): + return COLOR_SCHEMES[self.scheme_idx] + + @property + def emotion_data(self): + return EMOTIONS.get(self.mood, EMOTIONS["neutral"]) + + def set_mood(self, mood): + if mood in EMOTIONS: + self.mood = mood + self.mood_intensity = 0.0 + self.mood_target = 1.0 + self.mood_timer = 0 + self.mood_duration = random.randint(300, 600) + + def trigger_action(self, action): + if action not in ACTIONS: + return + self.action = action + self.action_timer = 0 + if action == "wave": + self.action_duration = 120 + elif action == "dance": + self.action_duration = 180 + elif action == "jump": + self.action_duration = 60 + self.jump_velocity = -8.0 + elif action == "pick": + self.action_duration = 90 + self.held_object = PickedObject() + elif action == "think": + self.action_duration = 150 + thoughts = [ + "01001...", "hmm...", "beep?", "loading...", + "eureka!", "recalc...", "*bzzzt*", "why?", + "yes!", "wow", "zzz...", "<3", + ] + self.thought_text = random.choice(thoughts) + self.thought_timer = 150 + + def trigger_random_action(self): + self.trigger_action(random.choice(ACTIONS[1:])) # skip idle + + def update(self, t): + ed = self.emotion_data + + # Smooth mood intensity transition + if self.mood_intensity < self.mood_target: + self.mood_intensity = min(self.mood_intensity + 0.02, self.mood_target) + + # Auto mood cycling + if self.auto_mood: + self.mood_timer += 1 + if self.mood_timer >= self.mood_duration: + moods = list(EMOTIONS.keys()) + self.set_mood(random.choice(moods)) + # Occasionally trigger a random action too + if random.random() < 0.4: + self.trigger_random_action() + + # Breathing + self.breath_phase = math.sin(t * 0.03 * ed["breath_rate"]) * 3 + + # Antenna + self.antenna_phase = t * 0.08 + + # Blink (affected by emotion) + self.blink_timer += 1 + blink_threshold = int(self.blink_interval / ed["blink_rate"]) + if self.is_blinking: + self.blink_frames += 1 + if self.blink_frames > 8: + self.is_blinking = False + self.blink_frames = 0 + self.blink_interval = random.randint(150, 300) + elif self.blink_timer >= blink_threshold: + self.is_blinking = True + self.blink_timer = 0 + self.blink_frames = 0 + + # Action updates + if self.action != "idle": + self.action_timer += 1 + if self.action_timer >= self.action_duration: + self.action = "idle" + self.action_timer = 0 + + # Jump physics + if self.action == "jump": + self.jump_offset += self.jump_velocity + self.jump_velocity += 0.5 # gravity + if self.jump_offset >= 0: + self.jump_offset = 0 + self.jump_velocity = 0 + else: + self.jump_offset = 0 + + # Dance phase + if self.action == "dance": + self.dance_phase = t * 0.15 + else: + self.dance_phase = 0 + + # Emit emotion particles + if random.random() < 0.03 * self.mood_intensity and self.mood != "neutral": + self.particles.append( + EmotionParticle(self.cx, self.cy - 120, self.mood, self.colors["glow"]) + ) + + # Update particles + for p in self.particles: + p.update() + self.particles = [p for p in self.particles if p.life > 0] + + # Thought timer + if self.thought_timer > 0: + self.thought_timer -= 1 + + # Compute final cy with jump + self.cy = self.base_cy + self.jump_offset + + def draw(self, screen, t, font): + c = self.colors + ed = self.emotion_data + cx = self.cx + cy = int(self.cy + self.breath_phase) + + # Dance sway + dance_sway = 0 + if self.action == "dance": + dance_sway = int(math.sin(self.dance_phase) * 15) + cx += dance_sway + + # --- Emotion particles --- + particle_font = pygame.font.Font(None, 20) + for p in self.particles: + p.draw(screen, particle_font) + + # --- Antenna --- + antenna_top_y = cy - 110 + pygame.draw.line(screen, c["body"], (cx, cy - 80), (cx, antenna_top_y), 3) + glow_size = int(6 + math.sin(self.antenna_phase) * 2) + glow_color = c["glow"] + # Glow changes with emotion + if self.mood == "excited": + glow_size = int(8 + math.sin(t * 0.2) * 4) + elif self.mood == "love": + glow_color = HOT_PINK + glow_surf = pygame.Surface((glow_size * 6, glow_size * 6), pygame.SRCALPHA) + pygame.draw.circle(glow_surf, (*glow_color, 50), (glow_size * 3, glow_size * 3), glow_size * 3) + pygame.draw.circle(glow_surf, (*glow_color, 100), (glow_size * 3, glow_size * 3), glow_size * 2) + screen.blit(glow_surf, (cx - glow_size * 3, antenna_top_y - glow_size * 3)) + pygame.draw.circle(screen, glow_color, (cx, antenna_top_y), glow_size) + + # --- Head --- + head_w, head_h = 80, 60 + # Tilt head for curious + head_rect = pygame.Rect(cx - head_w // 2, cy - 80, head_w, head_h) + pygame.draw.rect(screen, c["body"], head_rect, border_radius=12) + inner = head_rect.inflate(-6, -6) + pygame.draw.rect(screen, DARK_GRAY, inner, border_radius=10) + + # Blush for love/happy + if self.mood in ("love", "happy"): + blush_alpha = int(40 * self.mood_intensity) + blush = pygame.Surface((20, 12), pygame.SRCALPHA) + pygame.draw.ellipse(blush, (*HOT_PINK, blush_alpha), (0, 0, 20, 12)) + screen.blit(blush, (cx - 35, cy - 50)) + screen.blit(blush, (cx + 15, cy - 50)) + + # --- Eyes --- + eye_y = cy - 60 + left_eye_x = cx - 20 + right_eye_x = cx + 20 + self._draw_eyes(screen, ed, left_eye_x, right_eye_x, eye_y, c, t) + + # --- Mouth --- + mouth_y = cy - 35 + self._draw_mouth(screen, ed, cx, mouth_y, c, t) + + # --- Neck --- + pygame.draw.rect(screen, c["body"], (cx - 8, cy - 22, 16, 12)) + + # --- Body --- + body_rect = pygame.Rect(cx - 50, cy - 10, 100, 80) + pygame.draw.rect(screen, c["body"], body_rect, border_radius=8) + body_inner = body_rect.inflate(-6, -6) + pygame.draw.rect(screen, DARK_GRAY, body_inner, border_radius=6) + + # Core — emotion-colored + core_y = cy + 25 + base_color = c["glow"] + emotion_colors = { + "happy": AMBER, "curious": ELECTRIC_BLUE, "excited": HOT_PINK, + "sleepy": VIOLET, "surprised": WHITE, "love": HOT_PINK, "neutral": c["glow"], + } + target_core = emotion_colors.get(self.mood, c["glow"]) + core_color = lerp_color(base_color, target_core, ed["core_color_mix"] * self.mood_intensity) + + speed_mult = 1.0 + if self.mood == "excited": + speed_mult = 2.0 + elif self.mood == "sleepy": + speed_mult = 0.4 + core_pulse = int(12 + math.sin(t * 0.06 * speed_mult) * 3) + + pygame.draw.circle(screen, dim(core_color, 0.3), (cx, core_y), core_pulse + 8) + pygame.draw.circle(screen, core_color, (cx, core_y), core_pulse) + pygame.draw.circle(screen, WHITE, (cx, core_y), core_pulse // 2) + + # Circuitry lines + for dy in (-8, 0, 8): + y = core_y + dy + pygame.draw.line(screen, dim(c["body"], 0.5), (cx - 40, y), (cx - core_pulse - 8, y), 1) + pygame.draw.line(screen, dim(c["body"], 0.5), (cx + core_pulse + 8, y), (cx + 40, y), 1) + + # --- Arms --- + self._draw_arms(screen, cx, cy, c, t) + + # --- Legs --- + self._draw_legs(screen, cx, cy, c, t) + + # --- Ground shadow --- + shadow_y = cy + 118 + shadow_w = int(90 + math.sin(t * 0.03) * 5) + if self.action == "jump" and self.jump_offset < 0: + # Shadow shrinks when jumping + shadow_w = max(30, shadow_w + int(self.jump_offset * 1.5)) + shadow_surf = pygame.Surface((shadow_w, 8), pygame.SRCALPHA) + pygame.draw.ellipse(shadow_surf, (255, 255, 255, 20), (0, 0, shadow_w, 8)) + screen.blit(shadow_surf, (cx - shadow_w // 2, shadow_y)) + + # --- Held object --- + if self.held_object and self.action == "pick": + # Draw object above left hand + progress = min(1.0, self.action_timer / 45) + obj_y = int(cy + 65 - progress * 80) + self.held_object.draw(screen, cx - 75, obj_y, t) + elif self.held_object and self.action == "idle": + # Float object near hand after pick + self.held_object.draw(screen, cx - 70, cy - 10, t) + + # --- Thought bubble --- + if self.thought_timer > 0 and self.thought_text: + self._draw_thought(screen, cx, cy, font) + + # --- Mood label --- + mood_label = font.render(self.mood.upper(), True, dim(core_color, 0.6)) + screen.blit(mood_label, (cx - mood_label.get_width() // 2, cy + 130)) + + def _draw_eyes(self, screen, ed, lx, rx, y, c, t): + if self.is_blinking: + pygame.draw.line(screen, c["eye"], (lx - 8, y), (lx + 8, y), 2) + pygame.draw.line(screen, c["eye"], (rx - 8, y), (rx + 8, y), 2) + return + + eye_type = ed["eye"] + + for ex in (lx, rx): + if eye_type == "round": + pygame.draw.circle(screen, c["eye"], (ex, y), 8) + pygame.draw.circle(screen, WHITE, (ex + 2, y - 2), 3) + + elif eye_type == "arc": + # Happy squint + pygame.draw.arc(screen, c["eye"], (ex - 10, y - 8, 20, 16), 0.3, math.pi - 0.3, 3) + + elif eye_type == "big": + # Curious — larger + pygame.draw.circle(screen, c["eye"], (ex, y), 11) + pygame.draw.circle(screen, WHITE, (ex + 3, y - 3), 4) + pygame.draw.circle(screen, BLACK, (ex, y), 5) + + elif eye_type == "star": + # Excited — star-shaped + for i in range(5): + angle = -math.pi / 2 + i * 2 * math.pi / 5 + px = ex + math.cos(angle) * 10 + py = y + math.sin(angle) * 10 + pygame.draw.line(screen, c["eye"], (ex, y), (int(px), int(py)), 2) + pygame.draw.circle(screen, c["eye"], (ex, y), 4) + + elif eye_type == "half": + # Sleepy — half-closed + pygame.draw.arc(screen, c["eye"], (ex - 8, y - 4, 16, 12), 0.2, math.pi - 0.2, 2) + pygame.draw.line(screen, c["eye"], (ex - 8, y + 1), (ex + 8, y + 1), 2) + + elif eye_type == "wide": + # Surprised — wide open + pygame.draw.circle(screen, c["eye"], (ex, y), 12, 2) + pygame.draw.circle(screen, c["eye"], (ex, y), 5) + pygame.draw.circle(screen, WHITE, (ex, y), 2) + + elif eye_type == "heart": + # Love — heart eyes + r = 5 + pygame.draw.circle(screen, HOT_PINK, (ex - 3, y - 2), r) + pygame.draw.circle(screen, HOT_PINK, (ex + 3, y - 2), r) + pygame.draw.polygon(screen, HOT_PINK, [(ex - 8, y), (ex + 8, y), (ex, y + 8)]) + + def _draw_mouth(self, screen, ed, cx, y, c, t): + mouth_type = ed["mouth"] + + if mouth_type == "line": + pygame.draw.line(screen, c["accent"], (cx - 12, y), (cx + 12, y), 2) + + elif mouth_type == "smile": + pygame.draw.arc(screen, c["accent"], (cx - 15, y - 10, 30, 20), + math.pi + 0.3, 2 * math.pi - 0.3, 2) + + elif mouth_type == "small_o": + pygame.draw.circle(screen, c["accent"], (cx, y), 5, 2) + + elif mouth_type == "wide": + # Excited wide grin + pygame.draw.arc(screen, c["accent"], (cx - 20, y - 12, 40, 24), + math.pi + 0.2, 2 * math.pi - 0.2, 3) + + elif mouth_type == "big_o": + pygame.draw.ellipse(screen, c["accent"], (cx - 10, y - 8, 20, 16), 2) + + def _draw_arms(self, screen, cx, cy, c, t): + # Left arm + l_shoulder = (cx - 50, cy + 5) + + if self.action == "pick" and self.action_timer < 45: + # Reaching down to pick something up + progress = self.action_timer / 45 + l_elbow = (cx - 65, int(cy + 40 + progress * 20)) + l_hand = (cx - 75, int(cy + 65 + progress * 25)) + elif self.action == "pick" and self.action_timer >= 45: + # Lifting up + progress = (self.action_timer - 45) / 45 + l_elbow = (cx - 65, int(cy + 60 - progress * 40)) + l_hand = (cx - 75, int(cy + 90 - progress * 80)) + elif self.action == "dance": + sway = math.sin(self.dance_phase * 2) * 20 + l_elbow = (cx - 70, int(cy + 20 + sway)) + l_hand = (cx - 80, int(cy + sway)) + else: + l_elbow = (cx - 70, cy + 40) + l_hand = (cx - 75, cy + 65) + + pygame.draw.line(screen, c["body"], l_shoulder, l_elbow, 6) + pygame.draw.line(screen, c["body"], l_elbow, l_hand, 5) + pygame.draw.circle(screen, c["accent"], l_hand, 6) + + # Right arm + r_shoulder = (cx + 50, cy + 5) + + if self.action == "wave": + # Wave animation + wave_angle = math.sin(t * 0.15) * 0.8 + r_elbow = (cx + 70, cy - 10) + r_hand = (int(cx + 80 + math.sin(wave_angle) * 15), + int(cy - 40 + math.cos(wave_angle) * 10)) + elif self.action == "dance": + sway = math.sin(self.dance_phase * 2 + math.pi) * 20 + r_elbow = (cx + 70, int(cy + 20 + sway)) + r_hand = (cx + 80, int(cy + sway)) + elif self.action == "think": + # Hand on chin + r_elbow = (cx + 50, cy - 20) + r_hand = (cx + 15, cy - 38) + else: + r_elbow = (cx + 70, cy + 40) + r_hand = (cx + 75, cy + 65) + + pygame.draw.line(screen, c["body"], r_shoulder, r_elbow, 6) + pygame.draw.line(screen, c["body"], r_elbow, r_hand, 5) + pygame.draw.circle(screen, c["accent"], r_hand, 6) + + def _draw_legs(self, screen, cx, cy, c, t): + leg_top = cy + 70 + + if self.action == "dance": + # Alternating leg kicks + l_offset = int(math.sin(self.dance_phase * 2) * 10) + r_offset = int(math.sin(self.dance_phase * 2 + math.pi) * 10) + # Left + pygame.draw.rect(screen, c["body"], (cx - 30 + l_offset, leg_top, 14, 40)) + pygame.draw.rect(screen, c["accent"], (cx - 34 + l_offset, leg_top + 35, 22, 10), border_radius=3) + # Right + pygame.draw.rect(screen, c["body"], (cx + 16 + r_offset, leg_top, 14, 40)) + pygame.draw.rect(screen, c["accent"], (cx + 12 + r_offset, leg_top + 35, 22, 10), border_radius=3) + else: + # Static legs + pygame.draw.rect(screen, c["body"], (cx - 30, leg_top, 14, 40)) + pygame.draw.rect(screen, c["accent"], (cx - 34, leg_top + 35, 22, 10), border_radius=3) + pygame.draw.rect(screen, c["body"], (cx + 16, leg_top, 14, 40)) + pygame.draw.rect(screen, c["accent"], (cx + 12, leg_top + 35, 22, 10), border_radius=3) + + def _draw_thought(self, screen, cx, cy, font): + # Thought bubble + bubble_x = cx + 50 + bubble_y = cy - 130 + # Small circles leading to bubble + pygame.draw.circle(screen, dim(WHITE, 0.3), (cx + 20, cy - 95), 4) + pygame.draw.circle(screen, dim(WHITE, 0.3), (cx + 35, cy - 110), 6) + # Main bubble + text_surf = font.render(self.thought_text, True, WHITE) + tw = text_surf.get_width() + bw = tw + 20 + bh = 28 + pygame.draw.ellipse(screen, dim(WHITE, 0.15), (bubble_x - bw // 2, bubble_y - bh // 2, bw, bh)) + pygame.draw.ellipse(screen, dim(WHITE, 0.3), (bubble_x - bw // 2, bubble_y - bh // 2, bw, bh), 1) + screen.blit(text_surf, (bubble_x - tw // 2, bubble_y - text_surf.get_height() // 2)) + + +class ScanLines: + def __init__(self): + self.surface = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA) + for y in range(0, HEIGHT, 3): + pygame.draw.line(self.surface, (0, 0, 0, 15), (0, y), (WIDTH, y)) + + def draw(self, screen): + screen.blit(self.surface, (0, 0)) + + +def main(): + pygame.init() + + fullscreen = "--windowed" not in sys.argv + flags = pygame.FULLSCREEN if fullscreen else 0 + screen = pygame.display.set_mode((WIDTH, HEIGHT), flags) + pygame.display.set_caption("WaveQube Robot") + pygame.mouse.set_visible(False) + clock = pygame.time.Clock() + + font = pygame.font.Font(None, 22) + title_font = pygame.font.Font(None, 18) + + robot = Robot(WIDTH // 2, HEIGHT // 2 + 10) + scanlines = ScanLines() + + t = 0 + running = True + + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + elif event.type == pygame.KEYDOWN: + if event.key in (pygame.K_q, pygame.K_ESCAPE): + running = False + elif event.key == pygame.K_SPACE: + robot.trigger_random_action() + elif event.key == pygame.K_1: + robot.set_mood("happy") + elif event.key == pygame.K_2: + robot.set_mood("curious") + elif event.key == pygame.K_3: + robot.set_mood("excited") + elif event.key == pygame.K_4: + robot.set_mood("sleepy") + elif event.key == pygame.K_5: + robot.set_mood("surprised") + elif event.key == pygame.K_6: + robot.set_mood("love") + elif event.key == pygame.K_0: + robot.set_mood("neutral") + elif event.key == pygame.K_w: + robot.trigger_action("wave") + elif event.key == pygame.K_d: + robot.trigger_action("dance") + elif event.key == pygame.K_j: + robot.trigger_action("jump") + elif event.key == pygame.K_p: + robot.trigger_action("pick") + elif event.key == pygame.K_t: + robot.trigger_action("think") + elif event.key == pygame.K_a: + robot.auto_mood = not robot.auto_mood + elif event.key == pygame.K_c: + robot.scheme_idx = (robot.scheme_idx + 1) % len(COLOR_SCHEMES) + + robot.update(t) + + screen.fill(BLACK) + robot.draw(screen, t, font) + scanlines.draw(screen) + + # Status bar + auto_str = "AUTO" if robot.auto_mood else "MANUAL" + status = title_font.render( + f"WAVEQUBE | {auto_str} | {robot.action.upper()}", + True, dim(robot.colors["body"], 0.35) + ) + screen.blit(status, (WIDTH // 2 - status.get_width() // 2, HEIGHT - 18)) + + pygame.display.flip() + clock.tick(FPS) + t += 1 + + pygame.quit() + + +if __name__ == "__main__": + main() diff --git a/blackroad-hardware/projects/wavecube/wavecube_viz.py b/blackroad-hardware/projects/wavecube/wavecube_viz.py new file mode 100755 index 0000000000..673fd5c281 --- /dev/null +++ b/blackroad-hardware/projects/wavecube/wavecube_viz.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +"""WaveQube Generative Waveform Projector + +Renders layered sine waves with BlackRoad brand colors onto a 640x360 +framebuffer for projection through the DLP2000 module. + +Controls: + q / ESC — quit + SPACE — pause/resume animation + 1-4 — toggle individual wave layers + r — randomize wave parameters + +/- — speed up / slow down +""" + +import math +import random +import sys + +import pygame + +# --- Display --- +WIDTH, HEIGHT = 640, 360 +FPS = 60 + +# --- BlackRoad Brand Palette --- +BLACK = (0, 0, 0) +HOT_PINK = (255, 29, 108) +AMBER = (245, 166, 35) +ELECTRIC_BLUE = (41, 121, 255) +VIOLET = (156, 39, 176) + +BRAND_COLORS = [HOT_PINK, AMBER, ELECTRIC_BLUE, VIOLET] + + +class WaveLayer: + """A single animated sine wave layer.""" + + def __init__(self, color, freq, amp, phase_speed, y_offset=0, thickness=2): + self.color = color + self.freq = freq + self.amp = amp + self.phase_speed = phase_speed + self.y_offset = y_offset + self.thickness = thickness + self.visible = True + + def randomize(self): + self.freq = random.uniform(0.005, 0.03) + self.amp = random.uniform(20, 80) + self.phase_speed = random.uniform(0.01, 0.05) + self.y_offset = random.uniform(-40, 40) + + def render(self, surface, t): + if not self.visible: + return + center_y = HEIGHT // 2 + self.y_offset + points = [] + for x in range(WIDTH): + y = center_y + math.sin(x * self.freq + t * self.phase_speed) * self.amp + # Add secondary harmonic for visual complexity + y += math.sin(x * self.freq * 2.3 + t * self.phase_speed * 0.7) * (self.amp * 0.3) + points.append((x, y)) + if len(points) > 1: + pygame.draw.lines(surface, self.color, False, points, self.thickness) + + +class ParticleField: + """Subtle floating particles that drift behind the waves.""" + + def __init__(self, count=40): + self.particles = [ + { + "x": random.uniform(0, WIDTH), + "y": random.uniform(0, HEIGHT), + "vx": random.uniform(-0.3, 0.3), + "vy": random.uniform(-0.2, 0.2), + "size": random.randint(1, 3), + "color": random.choice(BRAND_COLORS), + "alpha": random.randint(40, 120), + } + for _ in range(count) + ] + + def update(self): + for p in self.particles: + p["x"] = (p["x"] + p["vx"]) % WIDTH + p["y"] = (p["y"] + p["vy"]) % HEIGHT + + def render(self, surface): + for p in self.particles: + # Dim version of the color + r, g, b = p["color"] + dim = p["alpha"] / 255 + color = (int(r * dim), int(g * dim), int(b * dim)) + pygame.draw.circle(surface, color, (int(p["x"]), int(p["y"])), p["size"]) + + +def main(): + pygame.init() + + # Use FULLSCREEN on Pi, windowed for dev/testing + fullscreen = "--windowed" not in sys.argv + flags = pygame.FULLSCREEN if fullscreen else 0 + screen = pygame.display.set_mode((WIDTH, HEIGHT), flags) + pygame.display.set_caption("WaveQube Viz") + pygame.mouse.set_visible(False) + clock = pygame.time.Clock() + + # Create wave layers + layers = [ + WaveLayer(HOT_PINK, 0.010, 55, 0.020, y_offset=-10, thickness=3), + WaveLayer(AMBER, 0.015, 40, 0.030, y_offset=0, thickness=2), + WaveLayer(ELECTRIC_BLUE, 0.020, 50, 0.015, y_offset=15, thickness=2), + WaveLayer(VIOLET, 0.008, 65, 0.025, y_offset=-5, thickness=2), + ] + + particles = ParticleField(count=40) + + t = 0.0 + speed = 1.0 + paused = False + running = True + + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + elif event.type == pygame.KEYDOWN: + if event.key in (pygame.K_q, pygame.K_ESCAPE): + running = False + elif event.key == pygame.K_SPACE: + paused = not paused + elif event.key == pygame.K_r: + for layer in layers: + layer.randomize() + elif event.key == pygame.K_EQUALS or event.key == pygame.K_PLUS: + speed = min(speed + 0.25, 4.0) + elif event.key == pygame.K_MINUS: + speed = max(speed - 0.25, 0.25) + elif event.key == pygame.K_1: + layers[0].visible = not layers[0].visible + elif event.key == pygame.K_2: + layers[1].visible = not layers[1].visible + elif event.key == pygame.K_3: + layers[2].visible = not layers[2].visible + elif event.key == pygame.K_4: + layers[3].visible = not layers[3].visible + + # Clear to black + screen.fill(BLACK) + + # Update and draw particles + if not paused: + particles.update() + particles.render(screen) + + # Draw waves + for layer in layers: + layer.render(screen, t) + + # Draw subtle gradient glow at center + glow_surf = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA) + center_x, center_y = WIDTH // 2, HEIGHT // 2 + for radius in range(120, 0, -2): + alpha = max(0, int(8 * (1 - radius / 120))) + pygame.draw.circle(glow_surf, (255, 29, 108, alpha), (center_x, center_y), radius) + screen.blit(glow_surf, (0, 0)) + + pygame.display.flip() + clock.tick(FPS) + + if not paused: + t += speed + + pygame.quit() + + +if __name__ == "__main__": + main() diff --git a/blackroad-infra/migration/README.md b/blackroad-infra/migration/README.md new file mode 100644 index 0000000000..891c92a82e --- /dev/null +++ b/blackroad-infra/migration/README.md @@ -0,0 +1,74 @@ +# Cloudflare → Self-Hosted Migration Toolkit + +Migrates all Cloudflare services (Workers, KV, D1, R2, Tunnels, DNS) to self-hosted +Raspberry Pis + DigitalOcean droplets. + +## Architecture + +``` +INTERNET → codex-infinity (159.65.43.12) → WireGuard → Pis + → shellfish (failover) +``` + +## Phases + +| Phase | Directory | What | +|-------|-----------|------| +| 0 | phase0-export/ | Export all Cloudflare data | +| 1 | phase1-postgres-redis/ | PostgreSQL + Redis on Cecilia | +| 2 | phase2-minio/ | MinIO object storage on Lucidia | +| 3 | phase3-wireguard-caddy/ | WireGuard mesh + Caddy reverse proxy | +| 4 | phase4-services/ | Port Workers to Hono/Node.js | +| 5 | phase5-dns/ | Rolling DNS cutover | +| 6 | phase6-pihole/ | Pi-hole internal DNS | +| 7 | phase7-monitoring/ | Health checks + hardening | +| 8 | phase8-decommission/ | Remove Cloudflare services | + +## Quick Start + +```bash +# Phase 0: Export everything (safe, read-only) +cd phase0-export && ./export-all.sh + +# Phase 1: Set up databases on Cecilia +scp phase1-postgres-redis/setup-cecilia.sh pi@192.168.4.89:/tmp/ +ssh pi@192.168.4.89 'bash /tmp/setup-cecilia.sh' + +# Phase 2: Set up MinIO on Lucidia +scp phase2-minio/setup-minio.sh pi@192.168.4.81:/tmp/ +ssh pi@192.168.4.81 'bash /tmp/setup-minio.sh' + +# Phase 3: WireGuard + Caddy on DO droplets +scp phase3-wireguard-caddy/setup-edge.sh root@159.65.43.12:/tmp/ +ssh root@159.65.43.12 'bash /tmp/setup-edge.sh' + +# Phase 4: Deploy services to Octavia +scp -r phase4-services/ pi@192.168.4.38:/home/pi/blackroad-services/ +ssh pi@192.168.4.38 'cd /home/pi/blackroad-services && npm install && pm2 start ecosystem.config.cjs' + +# Phase 5: DNS cutover (run from Mac) +cd phase5-dns && ./dns-migrate.sh batch1 + +# Phase 6: Pi-hole on Cecilia +scp phase6-pihole/setup-pihole.sh pi@192.168.4.89:/tmp/ +ssh pi@192.168.4.89 'bash /tmp/setup-pihole.sh' + +# Phase 7: Monitoring on Alice +scp phase7-monitoring/setup-monitoring.sh alice@192.168.4.49:/tmp/ +ssh alice@192.168.4.49 'bash /tmp/setup-monitoring.sh' + +# Phase 8: Decommission (after 7-day soak) +cd phase8-decommission && ./decommission.sh +``` + +## Device Roles After Migration + +| Device | IP | Role | RAM Budget | +|--------|-----|------|-----------| +| Cecilia | 192.168.4.89 | DB + DNS | 4GB | +| Lucidia | 192.168.4.81 | Object Storage | 1GB | +| Octavia | 192.168.4.38 | App Server | 3GB | +| Aria | 192.168.4.82 | AI + Mesh | 4GB | +| Alice | 192.168.4.49 | Monitoring | 256MB | +| codex-infinity | 159.65.43.12 | Primary Edge | 2GB | +| shellfish | 174.138.44.45 | Failover Edge | 512MB | diff --git a/blackroad-infra/migration/phase0-export/export-all.sh b/blackroad-infra/migration/phase0-export/export-all.sh new file mode 100644 index 0000000000..ac67c84a49 --- /dev/null +++ b/blackroad-infra/migration/phase0-export/export-all.sh @@ -0,0 +1,280 @@ +#!/bin/bash +# Phase 0: Export All Cloudflare Data +# Safe, read-only. Run from Mac where wrangler is authenticated. +set -euo pipefail + +EXPORT_DIR="${HOME}/cloudflare-export" +TIMESTAMP=$(date +%Y%m%d-%H%M%S) + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m' +log() { echo -e "${GREEN}[+]${NC} $1"; } +warn() { echo -e "${YELLOW}[!]${NC} $1"; } +error() { echo -e "${RED}[x]${NC} $1" >&2; } +info() { echo -e "${CYAN}[i]${NC} $1"; } + +# Check wrangler is available +if ! command -v wrangler &>/dev/null; then + error "wrangler not found. Install: npm i -g wrangler" + exit 1 +fi + +mkdir -p "$EXPORT_DIR"/{kv,d1,r2,dns,secrets,tunnels} +log "Export directory: $EXPORT_DIR" + +# ─── KV NAMESPACES ─── +export_kv() { + log "Exporting KV namespaces..." + + declare -A KV_NAMESPACES=( + ["CACHE"]="c878fbcc1faf4eddbc98dcfd7485048d" + ["IDENTITIES"]="10bf69b8bc664a5a832e348f1d0745cf" + ["API_KEYS"]="57e48a017d4248a39df32661c3377908" + ["RATE_LIMIT"]="245a00ee1ffe417fbcf519b2dbb141c6" + ["TOOLS_KV"]="f7b2b20d1e1447b2917b781e6ab7e45c" + ["TEMPLATES"]="8df3dcbf63d94069975a6fa8ab17f313" + ["CONTENT"]="119ac3af15724b1b93731202f2968117" + ["SUBSCRIPTIONS_KV"]="0cf493d5d19141df8912e3dc2df10464" + ["USERS_KV"]="67a82ad7824d4b89809e7ae2221aba66" + ["JOBS"]="2557a2b503654590ab7b1da84c7e8b20" + ["APPLICATIONS"]="90407b533ddc44508f1ce0841c77082d" + ) + + for ns in "${!KV_NAMESPACES[@]}"; do + local ns_id="${KV_NAMESPACES[$ns]}" + local out_dir="$EXPORT_DIR/kv/$ns" + mkdir -p "$out_dir" + + info " Exporting KV: $ns ($ns_id)" + + # List all keys + if wrangler kv key list --namespace-id="$ns_id" > "$out_dir/keys.json" 2>/dev/null; then + local count + count=$(python3 -c "import json; print(len(json.load(open('$out_dir/keys.json'))))" 2>/dev/null || echo "?") + log " Listed $count keys in $ns" + + # Export each key's value + python3 -c " +import json, subprocess, os, sys +keys = json.load(open('$out_dir/keys.json')) +for i, entry in enumerate(keys): + key = entry['name'] + safe = key.replace('/', '__SLASH__').replace(':', '__COLON__') + outfile = '$out_dir/' + safe + '.dat' + try: + result = subprocess.run( + ['wrangler', 'kv', 'key', 'get', '--namespace-id=$ns_id', key], + capture_output=True, timeout=30 + ) + with open(outfile, 'wb') as f: + f.write(result.stdout) + if (i + 1) % 50 == 0: + print(f' Exported {i+1}/{len(keys)} keys...', file=sys.stderr) + except Exception as e: + print(f' Failed: {key}: {e}', file=sys.stderr) +print(f' Done: {len(keys)} keys exported', file=sys.stderr) +" 2>&1 + else + warn " Failed to list keys for $ns" + fi + done + + # Skip RATE_LIMIT (ephemeral data, will regenerate) + warn " Skipping RATE_LIMIT (ephemeral data)" +} + +# ─── D1 DATABASES ─── +export_d1() { + log "Exporting D1 databases..." + + local DBS=( + "blackroad-os-main" + "blackroad-continuity" + "blackroad-saas" + "apollo-agent-registry" + "blackroad_revenue" + ) + + for db in "${DBS[@]}"; do + info " Exporting D1: $db" + + # SQL dump + if wrangler d1 export "$db" --output="$EXPORT_DIR/d1/${db}.sql" 2>/dev/null; then + local size + size=$(wc -c < "$EXPORT_DIR/d1/${db}.sql" 2>/dev/null || echo "0") + log " Exported ${db}.sql (${size} bytes)" + else + warn " Failed SQL export for $db, trying execute method..." + # Fallback: dump tables manually + wrangler d1 execute "$db" --command=".dump" --json > "$EXPORT_DIR/d1/${db}-dump.json" 2>/dev/null || true + fi + + # Get table list + wrangler d1 execute "$db" \ + --command="SELECT name, type FROM sqlite_master WHERE type='table' ORDER BY name" \ + --json > "$EXPORT_DIR/d1/${db}-tables.json" 2>/dev/null || true + done +} + +# ─── R2 BUCKETS ─── +export_r2_inventory() { + log "Exporting R2 bucket inventory (NOT downloading 200GB+ data)..." + + # List all buckets + wrangler r2 bucket list > "$EXPORT_DIR/r2/all-buckets.json" 2>/dev/null || warn "Failed to list R2 buckets" + + # List objects in each known bucket (inventory only) + local BUCKETS=( + "blackroad-models" + "blackroad-assets" + "blackroad-backups" + "blackroad-media" + ) + + for bucket in "${BUCKETS[@]}"; do + info " Inventorying R2: $bucket" + wrangler r2 object list "$bucket" > "$EXPORT_DIR/r2/${bucket}-inventory.json" 2>/dev/null || warn " Failed: $bucket" + done + + warn "R2 data sync (200GB+) should use rclone - see phase2-minio/sync-r2-to-minio.sh" +} + +# ─── DNS RECORDS ─── +export_dns() { + log "Exporting DNS records..." + + local ZONE_ID="d6566eba4500b460ffec6650d3b4baf6" + + if [ -n "${CLOUDFLARE_API_TOKEN:-}" ]; then + curl -s -X GET \ + "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records?per_page=500" \ + -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \ + -H "Content-Type: application/json" \ + > "$EXPORT_DIR/dns/blackroad-io-records.json" + + local count + count=$(python3 -c "import json; d=json.load(open('$EXPORT_DIR/dns/blackroad-io-records.json')); print(len(d.get('result',[])))" 2>/dev/null || echo "?") + log " Exported $count DNS records" + else + warn " CLOUDFLARE_API_TOKEN not set. Export DNS manually:" + warn " export CLOUDFLARE_API_TOKEN=your_token && $0" + fi +} + +# ─── WORKER SECRETS (names only) ─── +export_secrets() { + log "Documenting worker secrets (names only, values not retrievable)..." + + local WORKERS=( + "blackroad-api-gateway" + "blackroad-payment-gateway" + "blackroad-subdomain-router" + "blackroad-command-center" + "blackroad-tools" + "blackroad-agents-api" + ) + + for worker in "${WORKERS[@]}"; do + info " Listing secrets for: $worker" + wrangler secret list --name="$worker" > "$EXPORT_DIR/secrets/${worker}.json" 2>/dev/null || true + done + + # Document known secrets that need re-setting + cat > "$EXPORT_DIR/secrets/SECRETS_NEEDED.md" << 'EOF' +# Secrets That Must Be Set in Self-Hosted Environment + +## API Gateway +- ANTHROPIC_API_KEY +- OPENAI_API_KEY +- TUNNEL_URL (no longer needed - services are direct) + +## Payment Gateway (CRITICAL - Stripe) +- STRIPE_SECRET_KEY (sk_live_...) +- STRIPE_PUBLISHABLE_KEY (pk_live_...) +- STRIPE_WEBHOOK_SECRET (whsec_...) +- STRIPE_PRICE_PRO_MONTHLY +- STRIPE_PRICE_PRO_YEARLY +- STRIPE_PRICE_ENT_MONTHLY +- STRIPE_PRICE_ENT_YEARLY + +## Command Center +- GITHUB_TOKEN +- HF_TOKEN +- CLOUDFLARE_API_TOKEN (keep for DNS management) + +## Database Credentials (new) +- PG_PASSWORD +- REDIS_PASSWORD +- MINIO_ROOT_PASSWORD + +Get these from Alexa's password manager before proceeding. +EOF + log " Documented required secrets in SECRETS_NEEDED.md" +} + +# ─── TUNNEL CONFIGS ─── +export_tunnels() { + log "Exporting tunnel configs..." + + if [ -d "${HOME}/.cloudflared" ]; then + cp -r "${HOME}/.cloudflared" "$EXPORT_DIR/tunnels/" + log " Copied ~/.cloudflared/" + else + warn " No ~/.cloudflared/ found on this machine" + fi + + # Document tunnel routes + cat > "$EXPORT_DIR/tunnels/tunnel-routes.md" << 'EOF' +# Cloudflare Tunnel Routes (to be replaced by WireGuard) + +Tunnel ID: 52915859-da18-4aa6-add5-7bd9fcac2e0b +Tunnel Name: blackroad +Running On: blackroad-pi (192.168.4.64) +Edge: dfw08 (Dallas) + +## Routes +- agent.blackroad.ai → localhost:8080 +- api.blackroad.ai → localhost:3000 +EOF +} + +# ─── MAIN ─── +main() { + echo "" + echo "═══════════════════════════════════════════════════" + echo " Phase 0: Cloudflare Data Export" + echo " Target: $EXPORT_DIR" + echo " Time: $(date)" + echo "═══════════════════════════════════════════════════" + echo "" + + export_kv + echo "" + export_d1 + echo "" + export_r2_inventory + echo "" + export_dns + echo "" + export_secrets + echo "" + export_tunnels + + echo "" + echo "═══════════════════════════════════════════════════" + log "Export complete!" + info "Total size: $(du -sh "$EXPORT_DIR" 2>/dev/null | cut -f1)" + info "Next: Back up to Lucidia NVMe:" + info " scp -r $EXPORT_DIR pi@192.168.4.81:/home/pi/cloudflare-export-backup/" + echo "═══════════════════════════════════════════════════" +} + +case "${1:-all}" in + kv) export_kv ;; + d1) export_d1 ;; + r2) export_r2_inventory ;; + dns) export_dns ;; + secrets) export_secrets ;; + tunnels) export_tunnels ;; + all) main ;; + *) echo "Usage: $0 [kv|d1|r2|dns|secrets|tunnels|all]" ;; +esac diff --git a/blackroad-infra/migration/phase1-postgres-redis/import-d1-to-postgres.sh b/blackroad-infra/migration/phase1-postgres-redis/import-d1-to-postgres.sh new file mode 100644 index 0000000000..b79925d5a8 --- /dev/null +++ b/blackroad-infra/migration/phase1-postgres-redis/import-d1-to-postgres.sh @@ -0,0 +1,104 @@ +#!/bin/bash +# Import D1 SQLite dumps into PostgreSQL on Cecilia +# Run from Mac: bash import-d1-to-postgres.sh +set -euo pipefail + +EXPORT_DIR="${HOME}/cloudflare-export/d1" +CECILIA="pi@192.168.4.89" +PG_PASSWORD="${PG_PASSWORD:?Set PG_PASSWORD}" + +GREEN='\033[0;32m'; CYAN='\033[0;36m'; YELLOW='\033[1;33m'; NC='\033[0m' +log() { echo -e "${GREEN}[+]${NC} $1"; } +info() { echo -e "${CYAN}[i]${NC} $1"; } +warn() { echo -e "${YELLOW}[!]${NC} $1"; } + +# D1 (SQLite) to PostgreSQL syntax converter +convert_sqlite_to_pg() { + local input="$1" + local output="$2" + + python3 << PYEOF +import re, sys + +with open('$input', 'r') as f: + sql = f.read() + +# SQLite → PostgreSQL conversions +# INTEGER PRIMARY KEY AUTOINCREMENT → SERIAL PRIMARY KEY +sql = re.sub(r'INTEGER\s+PRIMARY\s+KEY\s+AUTOINCREMENT', 'SERIAL PRIMARY KEY', sql, flags=re.IGNORECASE) + +# Remove AUTOINCREMENT standalone +sql = re.sub(r'\bAUTOINCREMENT\b', '', sql, flags=re.IGNORECASE) + +# datetime('now') → NOW() +sql = re.sub(r"datetime\('now'\)", 'NOW()', sql, flags=re.IGNORECASE) + +# BOOLEAN handling (SQLite stores as INTEGER) +# Keep as-is since PostgreSQL handles 0/1 for boolean columns + +# Remove SQLite-specific pragmas +sql = re.sub(r'PRAGMA\s+[^;]+;', '', sql, flags=re.IGNORECASE) + +# Remove BEGIN TRANSACTION / COMMIT (let PostgreSQL handle) +sql = re.sub(r'BEGIN\s+TRANSACTION\s*;', '', sql, flags=re.IGNORECASE) +sql = re.sub(r'COMMIT\s*;', '', sql, flags=re.IGNORECASE) + +# Handle INSERT OR IGNORE → INSERT ... ON CONFLICT DO NOTHING +sql = re.sub(r'INSERT\s+OR\s+IGNORE', 'INSERT', sql, flags=re.IGNORECASE) + +# Handle INSERT OR REPLACE → INSERT ... ON CONFLICT DO UPDATE (approximation) +sql = re.sub(r'INSERT\s+OR\s+REPLACE', 'INSERT', sql, flags=re.IGNORECASE) + +# Remove IF NOT EXISTS from CREATE INDEX (PostgreSQL supports it) +# Already compatible + +with open('$output', 'w') as f: + f.write(sql) + +print(f'Converted: $input -> $output') +PYEOF +} + +# Map D1 database names to PostgreSQL database names +declare -A DB_MAP=( + ["blackroad-os-main"]="blackroad_os_main" + ["blackroad-continuity"]="blackroad_continuity" + ["blackroad-saas"]="blackroad_saas" + ["apollo-agent-registry"]="apollo_agent_registry" + ["blackroad_revenue"]="blackroad_revenue" +) + +log "Importing D1 databases into PostgreSQL on Cecilia..." + +for d1_name in "${!DB_MAP[@]}"; do + pg_name="${DB_MAP[$d1_name]}" + sql_file="$EXPORT_DIR/${d1_name}.sql" + + if [ ! -f "$sql_file" ]; then + warn "No export found for $d1_name (expected: $sql_file)" + continue + fi + + info "Converting $d1_name -> $pg_name" + + # Convert SQLite syntax to PostgreSQL + pg_file="/tmp/${pg_name}-pg.sql" + convert_sqlite_to_pg "$sql_file" "$pg_file" + + # Upload to Cecilia + scp "$pg_file" "${CECILIA}:/tmp/${pg_name}-pg.sql" + + # Import + info "Importing into $pg_name..." + ssh "$CECILIA" "PGPASSWORD='${PG_PASSWORD}' psql -U blackroad -d ${pg_name} -f /tmp/${pg_name}-pg.sql" 2>&1 || warn "Some errors during import of $pg_name (may be OK if tables exist)" + + # Verify + local count + count=$(ssh "$CECILIA" "PGPASSWORD='${PG_PASSWORD}' psql -U blackroad -d ${pg_name} -t -c \"SELECT count(*) FROM information_schema.tables WHERE table_schema='public'\"" 2>/dev/null | xargs) + log " $pg_name: $count tables" + + rm -f "$pg_file" +done + +echo "" +log "D1 → PostgreSQL import complete!" diff --git a/blackroad-infra/migration/phase1-postgres-redis/import-kv-to-redis.sh b/blackroad-infra/migration/phase1-postgres-redis/import-kv-to-redis.sh new file mode 100644 index 0000000000..1b234b1937 --- /dev/null +++ b/blackroad-infra/migration/phase1-postgres-redis/import-kv-to-redis.sh @@ -0,0 +1,72 @@ +#!/bin/bash +# Import KV namespace exports into Redis on Cecilia +# Run from Mac: bash import-kv-to-redis.sh +set -euo pipefail + +EXPORT_DIR="${HOME}/cloudflare-export/kv" +REDIS_HOST="${REDIS_HOST:-192.168.4.89}" +REDIS_PASSWORD="${REDIS_PASSWORD:?Set REDIS_PASSWORD}" + +GREEN='\033[0;32m'; CYAN='\033[0;36m'; YELLOW='\033[1;33m'; NC='\033[0m' +log() { echo -e "${GREEN}[+]${NC} $1"; } +info() { echo -e "${CYAN}[i]${NC} $1"; } +warn() { echo -e "${YELLOW}[!]${NC} $1"; } + +# Namespaces to import (skip RATE_LIMIT - ephemeral) +NAMESPACES=( + "CACHE" + "IDENTITIES" + "API_KEYS" + "TOOLS_KV" + "TEMPLATES" + "CONTENT" + "SUBSCRIPTIONS_KV" + "USERS_KV" + "JOBS" + "APPLICATIONS" +) + +log "Importing KV data into Redis at $REDIS_HOST..." + +total_imported=0 + +for ns in "${NAMESPACES[@]}"; do + ns_dir="$EXPORT_DIR/$ns" + + if [ ! -d "$ns_dir" ]; then + warn "No export directory for $ns" + continue + fi + + info "Importing namespace: $ns" + ns_count=0 + + # Read each exported key file + for datfile in "$ns_dir"/*.dat; do + [ -f "$datfile" ] || continue + + # Reconstruct key name from filename + local_key=$(basename "$datfile" .dat) + # Reverse the safe encoding + key=$(echo "$local_key" | sed 's/__SLASH__/\//g; s/__COLON__/:/g') + + # Read value + value=$(cat "$datfile") + + # Store in Redis with namespace prefix + redis-cli -h "$REDIS_HOST" -a "$REDIS_PASSWORD" --no-auth-warning \ + SET "${ns}:${key}" "$value" > /dev/null 2>&1 + + ns_count=$((ns_count + 1)) + done + + log " $ns: $ns_count keys imported" + total_imported=$((total_imported + ns_count)) +done + +echo "" +log "Import complete: $total_imported total keys" + +# Verify +info "Redis stats:" +redis-cli -h "$REDIS_HOST" -a "$REDIS_PASSWORD" --no-auth-warning INFO keyspace 2>/dev/null diff --git a/blackroad-infra/migration/phase1-postgres-redis/setup-cecilia.sh b/blackroad-infra/migration/phase1-postgres-redis/setup-cecilia.sh new file mode 100644 index 0000000000..6790ac332e --- /dev/null +++ b/blackroad-infra/migration/phase1-postgres-redis/setup-cecilia.sh @@ -0,0 +1,269 @@ +#!/bin/bash +# Phase 1: Install PostgreSQL 16 + Redis 7 on Cecilia (192.168.4.89) +# Run ON Cecilia: bash /tmp/setup-cecilia.sh +set -euo pipefail + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m' +log() { echo -e "${GREEN}[+]${NC} $1"; } +warn() { echo -e "${YELLOW}[!]${NC} $1"; } +error() { echo -e "${RED}[x]${NC} $1" >&2; } +info() { echo -e "${CYAN}[i]${NC} $1"; } + +PG_PASSWORD="${PG_PASSWORD:-CHANGE_ME_BEFORE_RUNNING}" +REDIS_PASSWORD="${REDIS_PASSWORD:-CHANGE_ME_BEFORE_RUNNING}" + +if [ "$PG_PASSWORD" = "CHANGE_ME_BEFORE_RUNNING" ] || [ "$REDIS_PASSWORD" = "CHANGE_ME_BEFORE_RUNNING" ]; then + error "Set passwords first:" + error " export PG_PASSWORD='your_strong_password'" + error " export REDIS_PASSWORD='your_strong_password'" + exit 1 +fi + +echo "" +echo "═══════════════════════════════════════════════════" +echo " Phase 1: PostgreSQL + Redis on Cecilia" +echo " Host: $(hostname) ($(hostname -I | awk '{print $1}'))" +echo "═══════════════════════════════════════════════════" +echo "" + +# ─── POSTGRESQL 16 ─── +install_postgresql() { + log "Installing PostgreSQL 16..." + + sudo apt update + sudo apt install -y postgresql postgresql-contrib + + # Get installed version + local pg_ver + pg_ver=$(pg_config --version 2>/dev/null | grep -oP '\d+' | head -1) + log "PostgreSQL $pg_ver installed" + + local pg_conf="/etc/postgresql/${pg_ver}/main/postgresql.conf" + local pg_hba="/etc/postgresql/${pg_ver}/main/pg_hba.conf" + + # Configure: listen on all interfaces + sudo sed -i "s/#listen_addresses = 'localhost'/listen_addresses = '*'/" "$pg_conf" + + # NVMe-optimized tuning for Pi 5 (8GB RAM) + sudo tee -a "$pg_conf" > /dev/null << 'PGCONF' + +# === BlackRoad NVMe-optimized settings === +shared_buffers = 2GB +effective_cache_size = 4GB +work_mem = 64MB +maintenance_work_mem = 512MB +wal_buffers = 64MB +max_connections = 100 +random_page_cost = 1.1 +effective_io_concurrency = 200 +max_wal_size = 2GB +min_wal_size = 512MB +checkpoint_completion_target = 0.9 +PGCONF + + # Allow connections from Pi subnet + WireGuard + Tailscale + sudo tee -a "$pg_hba" > /dev/null << 'HBA' + +# BlackRoad Pi subnet +host all all 192.168.4.0/24 scram-sha-256 +# WireGuard tunnel +host all all 10.10.0.0/24 scram-sha-256 +# Tailscale +host all all 100.64.0.0/10 scram-sha-256 +HBA + + sudo systemctl restart postgresql + sudo systemctl enable postgresql + log "PostgreSQL configured and running" +} + +create_databases() { + log "Creating databases..." + + sudo -u postgres psql << SQL +-- Create application user +DO \$\$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'blackroad') THEN + CREATE ROLE blackroad WITH LOGIN PASSWORD '${PG_PASSWORD}'; + END IF; +END +\$\$; + +-- Create databases matching D1 names +CREATE DATABASE blackroad_os_main OWNER blackroad; +CREATE DATABASE blackroad_continuity OWNER blackroad; +CREATE DATABASE blackroad_saas OWNER blackroad; +CREATE DATABASE apollo_agent_registry OWNER blackroad; +CREATE DATABASE blackroad_revenue OWNER blackroad; + +-- Grant privileges +ALTER USER blackroad CREATEDB; +SQL + + log "Created 5 databases" +} + +setup_revenue_schema() { + log "Setting up blackroad_revenue schema..." + + PGPASSWORD="$PG_PASSWORD" psql -U blackroad -d blackroad_revenue << 'SQL' +-- Revenue tracking (from payment-gateway/schema.sql, converted to PostgreSQL) +CREATE TABLE IF NOT EXISTS revenue ( + id SERIAL PRIMARY KEY, + user_id TEXT NOT NULL, + tier_id TEXT, + amount DOUBLE PRECISION NOT NULL, + currency TEXT DEFAULT 'usd', + created_at TEXT NOT NULL, + metadata TEXT +); +CREATE INDEX IF NOT EXISTS idx_revenue_user ON revenue(user_id); +CREATE INDEX IF NOT EXISTS idx_revenue_created ON revenue(created_at); + +-- Subscription tracking +CREATE TABLE IF NOT EXISTS subscriptions ( + id SERIAL PRIMARY KEY, + stripe_subscription_id TEXT UNIQUE NOT NULL, + stripe_customer_id TEXT NOT NULL, + user_id TEXT NOT NULL, + tier_id TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'active', + current_period_end TEXT, + cancel_at_period_end INTEGER DEFAULT 0, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); +CREATE INDEX IF NOT EXISTS idx_sub_user ON subscriptions(user_id); +CREATE INDEX IF NOT EXISTS idx_sub_customer ON subscriptions(stripe_customer_id); +CREATE INDEX IF NOT EXISTS idx_sub_status ON subscriptions(status); + +-- Webhook event idempotency +CREATE TABLE IF NOT EXISTS webhook_events ( + stripe_event_id TEXT PRIMARY KEY, + event_type TEXT NOT NULL, + processed_at TEXT NOT NULL, + success INTEGER NOT NULL DEFAULT 1 +); +CREATE INDEX IF NOT EXISTS idx_webhook_type ON webhook_events(event_type); +CREATE INDEX IF NOT EXISTS idx_webhook_processed ON webhook_events(processed_at); +SQL + + log "blackroad_revenue schema ready" +} + +# ─── REDIS 7 ─── +install_redis() { + log "Installing Redis..." + + sudo apt install -y redis-server + + # Configure Redis + sudo tee /etc/redis/redis.conf > /dev/null << REDISCONF +# BlackRoad Redis Configuration +bind 0.0.0.0 +protected-mode yes +port 6379 +daemonize yes +supervised systemd +pidfile /run/redis/redis-server.pid +dir /var/lib/redis +dbfilename dump.rdb + +# Authentication +requirepass ${REDIS_PASSWORD} + +# Memory management (2GB max for Pi) +maxmemory 2gb +maxmemory-policy allkeys-lru + +# Persistence: RDB snapshots +save 900 1 +save 300 10 +save 60 10000 + +# Persistence: AOF (append-only file) +appendonly yes +appendfilename "appendonly.aof" +appendfsync everysec + +# Logging +loglevel notice +logfile /var/log/redis/redis-server.log +REDISCONF + + sudo systemctl restart redis-server + sudo systemctl enable redis-server + log "Redis configured and running" +} + +# ─── BACKUP CRON ─── +setup_backups() { + log "Setting up automated backups..." + + mkdir -p /home/pi/backups + + # PostgreSQL daily backup at 3 AM + (crontab -l 2>/dev/null || true; echo "0 3 * * * pg_dumpall -U blackroad | gzip > /home/pi/backups/pg-\$(date +\%Y\%m\%d).sql.gz 2>/dev/null") | sort -u | crontab - + + # Redis snapshot every 6 hours + (crontab -l 2>/dev/null || true; echo "0 */6 * * * redis-cli -a '${REDIS_PASSWORD}' BGSAVE 2>/dev/null") | sort -u | crontab - + + # Sync backups to Lucidia NVMe (redundancy) at 3:30 AM + (crontab -l 2>/dev/null || true; echo "30 3 * * * rsync -avz /home/pi/backups/ pi@192.168.4.81:/mnt/nvme/backups/cecilia/ 2>/dev/null") | sort -u | crontab - + + # Clean backups older than 30 days + (crontab -l 2>/dev/null || true; echo "0 4 * * 0 find /home/pi/backups -name 'pg-*.sql.gz' -mtime +30 -delete 2>/dev/null") | sort -u | crontab - + + log "Backup cron jobs installed" +} + +# ─── VERIFICATION ─── +verify() { + echo "" + log "Verifying installation..." + + # PostgreSQL + if PGPASSWORD="$PG_PASSWORD" psql -U blackroad -d blackroad_revenue -c "SELECT 1" &>/dev/null; then + log " PostgreSQL: OK" + else + error " PostgreSQL: FAILED" + fi + + # Redis + if redis-cli -a "$REDIS_PASSWORD" ping 2>/dev/null | grep -q PONG; then + log " Redis: OK" + else + error " Redis: FAILED" + fi + + # Database list + info " Databases:" + PGPASSWORD="$PG_PASSWORD" psql -U blackroad -d postgres -t -c "SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname" 2>/dev/null | while read -r db; do + [ -n "$db" ] && info " - $(echo "$db" | xargs)" + done + + # Redis memory + local redis_mem + redis_mem=$(redis-cli -a "$REDIS_PASSWORD" INFO memory 2>/dev/null | grep used_memory_human | cut -d: -f2 | tr -d '[:space:]') + info " Redis memory: ${redis_mem:-unknown}" +} + +# ─── MAIN ─── +install_postgresql +create_databases +setup_revenue_schema +install_redis +setup_backups +verify + +echo "" +echo "═══════════════════════════════════════════════════" +log "Phase 1 complete!" +info "PostgreSQL: localhost:5432 (user: blackroad)" +info "Redis: localhost:6379 (password protected)" +info "" +info "Next steps:" +info " 1. Import D1 data: bash /tmp/import-d1-to-postgres.sh" +info " 2. Import KV data: bash /tmp/import-kv-to-redis.sh" +echo "═══════════════════════════════════════════════════" diff --git a/blackroad-infra/migration/phase2-minio/setup-minio.sh b/blackroad-infra/migration/phase2-minio/setup-minio.sh new file mode 100644 index 0000000000..18bff86b31 --- /dev/null +++ b/blackroad-infra/migration/phase2-minio/setup-minio.sh @@ -0,0 +1,160 @@ +#!/bin/bash +# Phase 2: Install MinIO on Lucidia (192.168.4.81) +# Run ON Lucidia: bash /tmp/setup-minio.sh +set -euo pipefail + +MINIO_ROOT_USER="${MINIO_ROOT_USER:-blackroad-admin}" +MINIO_ROOT_PASSWORD="${MINIO_ROOT_PASSWORD:?Set MINIO_ROOT_PASSWORD}" +MINIO_DATA="/mnt/nvme/minio-data" + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m' +log() { echo -e "${GREEN}[+]${NC} $1"; } +warn() { echo -e "${YELLOW}[!]${NC} $1"; } +error() { echo -e "${RED}[x]${NC} $1" >&2; } +info() { echo -e "${CYAN}[i]${NC} $1"; } + +echo "" +echo "═══════════════════════════════════════════════════" +echo " Phase 2: MinIO on Lucidia" +echo " Host: $(hostname) ($(hostname -I | awk '{print $1}'))" +echo " NVMe: $MINIO_DATA" +echo "═══════════════════════════════════════════════════" +echo "" + +# Check NVMe is mounted +if ! df -h | grep -q "/mnt/nvme"; then + error "NVMe not mounted at /mnt/nvme!" + error "Mount it first: sudo mount /dev/nvme0n1p1 /mnt/nvme" + exit 1 +fi + +free_gb=$(df -BG /mnt/nvme | awk 'NR==2{print $4}' | tr -d 'G') +log "NVMe free space: ${free_gb}GB" + +if [ "$free_gb" -lt 250 ]; then + warn "Less than 250GB free. R2 data is ~200GB. Proceed with caution." +fi + +# ─── INSTALL MINIO SERVER ─── +install_minio() { + log "Installing MinIO server..." + + if ! command -v minio &>/dev/null; then + wget -q https://dl.min.io/server/minio/release/linux-arm64/minio -O /tmp/minio + chmod +x /tmp/minio + sudo mv /tmp/minio /usr/local/bin/minio + fi + log "MinIO server: $(minio --version 2>&1 | head -1)" + + # Create data directory + sudo mkdir -p "$MINIO_DATA" + sudo chown "$(whoami):$(whoami)" "$MINIO_DATA" + + # Systemd service + sudo tee /etc/systemd/system/minio.service > /dev/null << SERVICE +[Unit] +Description=MinIO Object Storage +After=network.target +Documentation=https://min.io/docs + +[Service] +Type=simple +User=$(whoami) +Environment="MINIO_ROOT_USER=${MINIO_ROOT_USER}" +Environment="MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}" +Environment="MINIO_VOLUMES=${MINIO_DATA}" +Environment="MINIO_OPTS=--address :9000 --console-address :9001" +ExecStart=/usr/local/bin/minio server \$MINIO_VOLUMES \$MINIO_OPTS +Restart=always +RestartSec=10 +LimitNOFILE=65536 + +[Install] +WantedBy=multi-user.target +SERVICE + + sudo systemctl daemon-reload + sudo systemctl enable minio + sudo systemctl start minio + sleep 3 + log "MinIO server running on :9000 (console :9001)" +} + +# ─── INSTALL MINIO CLIENT ─── +install_mc() { + log "Installing MinIO client (mc)..." + + if ! command -v mc &>/dev/null; then + wget -q https://dl.min.io/client/mc/release/linux-arm64/mc -O /tmp/mc + chmod +x /tmp/mc + sudo mv /tmp/mc /usr/local/bin/mc + fi + + # Configure alias + mc alias set blackroad http://localhost:9000 "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" --api S3v4 + log "mc configured with alias 'blackroad'" +} + +# ─── CREATE BUCKETS ─── +create_buckets() { + log "Creating buckets matching R2..." + + local BUCKETS=( + "blackroad-models" + "blackroad-assets" + "blackroad-backups" + "blackroad-media" + "blackroad-uploads" + "blackroad-static" + "blackroad-logs" + "blackroad-exports" + "blackroad-cache" + "blackroad-temp" + ) + + for bucket in "${BUCKETS[@]}"; do + if mc mb "blackroad/$bucket" 2>/dev/null; then + log " Created: $bucket" + else + info " Exists: $bucket" + fi + done +} + +# ─── VERIFICATION ─── +verify() { + echo "" + log "Verifying MinIO..." + + # Check service + if curl -sf http://localhost:9000/minio/health/live > /dev/null 2>&1; then + log " Health: OK" + else + error " Health: FAILED" + fi + + # List buckets + info " Buckets:" + mc ls blackroad/ 2>/dev/null | while read -r line; do + info " $line" + done + + # Disk usage + info " Disk: $(du -sh "$MINIO_DATA" 2>/dev/null | cut -f1) used" +} + +# ─── MAIN ─── +install_minio +install_mc +create_buckets +verify + +echo "" +echo "═══════════════════════════════════════════════════" +log "Phase 2 complete!" +info "MinIO API: http://$(hostname -I | awk '{print $1}'):9000" +info "MinIO Console: http://$(hostname -I | awk '{print $1}'):9001" +info "" +info "Next: Sync R2 data using rclone:" +info " bash /tmp/sync-r2-to-minio.sh" +echo "═══════════════════════════════════════════════════" diff --git a/blackroad-infra/migration/phase2-minio/sync-r2-to-minio.sh b/blackroad-infra/migration/phase2-minio/sync-r2-to-minio.sh new file mode 100644 index 0000000000..8c99d7a548 --- /dev/null +++ b/blackroad-infra/migration/phase2-minio/sync-r2-to-minio.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# Sync R2 data to MinIO using rclone +# Run ON Lucidia: bash sync-r2-to-minio.sh +# Run in tmux/screen - this transfers 200+GB +set -euo pipefail + +R2_ACCESS_KEY="${R2_ACCESS_KEY:?Set R2_ACCESS_KEY}" +R2_SECRET_KEY="${R2_SECRET_KEY:?Set R2_SECRET_KEY}" +MINIO_ROOT_USER="${MINIO_ROOT_USER:-blackroad-admin}" +MINIO_ROOT_PASSWORD="${MINIO_ROOT_PASSWORD:?Set MINIO_ROOT_PASSWORD}" +CF_ACCOUNT_ID="848cf0b18d51e0170e0d1537aec3505a" + +GREEN='\033[0;32m'; CYAN='\033[0;36m'; NC='\033[0m' +log() { echo -e "${GREEN}[+]${NC} $1"; } +info() { echo -e "${CYAN}[i]${NC} $1"; } + +# Install rclone if needed +if ! command -v rclone &>/dev/null; then + log "Installing rclone..." + curl https://rclone.org/install.sh | sudo bash +fi + +# Configure rclone +mkdir -p ~/.config/rclone +cat > ~/.config/rclone/rclone.conf << CONF +[r2] +type = s3 +provider = Cloudflare +access_key_id = ${R2_ACCESS_KEY} +secret_access_key = ${R2_SECRET_KEY} +endpoint = https://${CF_ACCOUNT_ID}.r2.cloudflarestorage.com +acl = private + +[minio] +type = s3 +provider = Minio +access_key_id = ${MINIO_ROOT_USER} +secret_access_key = ${MINIO_ROOT_PASSWORD} +endpoint = http://localhost:9000 +CONF + +log "rclone configured" + +# Buckets to sync +BUCKETS=( + "blackroad-models" # ~135GB (largest) + "blackroad-assets" + "blackroad-backups" + "blackroad-media" +) + +echo "" +echo "═══════════════════════════════════════════════════" +echo " R2 → MinIO Sync" +echo " This will transfer ~200GB. Run in tmux!" +echo "═══════════════════════════════════════════════════" +echo "" + +for bucket in "${BUCKETS[@]}"; do + log "Syncing: $bucket" + info " Listing objects..." + + # Count objects in R2 + r2_count=$(rclone ls "r2:$bucket" 2>/dev/null | wc -l) + info " R2 objects: $r2_count" + + # Sync with progress + rclone sync "r2:$bucket" "minio:$bucket" \ + --progress \ + --transfers 4 \ + --checkers 8 \ + --buffer-size 64M \ + --stats 30s \ + --log-file="/tmp/rclone-${bucket}.log" \ + --log-level INFO + + # Verify + minio_count=$(rclone ls "minio:$bucket" 2>/dev/null | wc -l) + log " Synced: $minio_count objects (R2 had $r2_count)" + + if [ "$r2_count" -eq "$minio_count" ]; then + log " VERIFIED: counts match" + else + echo -e "\033[1;33m[!]\033[0m WARNING: count mismatch ($r2_count vs $minio_count)" + fi + echo "" +done + +log "R2 → MinIO sync complete!" +info "Verify with: mc ls --recursive blackroad/ | wc -l" diff --git a/blackroad-infra/migration/phase3-wireguard-caddy/generate-wireguard-keys.sh b/blackroad-infra/migration/phase3-wireguard-caddy/generate-wireguard-keys.sh new file mode 100644 index 0000000000..b1f7c1e539 --- /dev/null +++ b/blackroad-infra/migration/phase3-wireguard-caddy/generate-wireguard-keys.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# Generate WireGuard keys for all devices +# Run from Mac +set -euo pipefail + +KEYS_DIR="$(dirname "$0")/keys" +mkdir -p "$KEYS_DIR" +chmod 700 "$KEYS_DIR" + +GREEN='\033[0;32m'; CYAN='\033[0;36m'; NC='\033[0m' +log() { echo -e "${GREEN}[+]${NC} $1"; } +info() { echo -e "${CYAN}[i]${NC} $1"; } + +# Check wg is available +if ! command -v wg &>/dev/null; then + echo "Install WireGuard tools first:" + echo " macOS: brew install wireguard-tools" + echo " Linux: sudo apt install wireguard-tools" + exit 1 +fi + +DEVICES=( + "codex-infinity" # 10.10.0.1 - DO primary edge + "cecilia" # 10.10.0.2 - DB + DNS + "lucidia" # 10.10.0.3 - MinIO + "octavia" # 10.10.0.4 - App server + "aria" # 10.10.0.5 - AI + Mesh + "shellfish" # 10.10.0.6 - DO failover + "alice" # 10.10.0.7 - Monitoring +) + +echo "" +echo "═══════════════════════════════════════════════════" +echo " WireGuard Key Generation" +echo "═══════════════════════════════════════════════════" +echo "" + +for device in "${DEVICES[@]}"; do + privkey=$(wg genkey) + pubkey=$(echo "$privkey" | wg pubkey) + + echo "$privkey" > "$KEYS_DIR/${device}.key" + echo "$pubkey" > "$KEYS_DIR/${device}.pub" + chmod 600 "$KEYS_DIR/${device}.key" + + log "$device" + info " Private: $KEYS_DIR/${device}.key" + info " Public: $pubkey" +done + +echo "" +log "Keys saved to: $KEYS_DIR/" +echo "" +echo "SECURITY: Keep .key files secret! Only share .pub files." +echo "These keys are needed by setup-edge.sh and setup-pi-wireguard.sh" diff --git a/blackroad-infra/migration/phase3-wireguard-caddy/setup-edge.sh b/blackroad-infra/migration/phase3-wireguard-caddy/setup-edge.sh new file mode 100644 index 0000000000..bc29bc9c56 --- /dev/null +++ b/blackroad-infra/migration/phase3-wireguard-caddy/setup-edge.sh @@ -0,0 +1,289 @@ +#!/bin/bash +# Phase 3: Setup WireGuard + Caddy on codex-infinity (159.65.43.12) +# Run ON codex-infinity: bash /tmp/setup-edge.sh +set -euo pipefail + +RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m' +log() { echo -e "${GREEN}[+]${NC} $1"; } +warn() { echo -e "${YELLOW}[!]${NC} $1"; } +error() { echo -e "${RED}[x]${NC} $1" >&2; } +info() { echo -e "${CYAN}[i]${NC} $1"; } + +# These must be set before running +CODEX_WG_PRIVKEY="${CODEX_WG_PRIVKEY:?Set CODEX_WG_PRIVKEY}" +CECILIA_WG_PUBKEY="${CECILIA_WG_PUBKEY:?Set CECILIA_WG_PUBKEY}" +LUCIDIA_WG_PUBKEY="${LUCIDIA_WG_PUBKEY:?Set LUCIDIA_WG_PUBKEY}" +OCTAVIA_WG_PUBKEY="${OCTAVIA_WG_PUBKEY:?Set OCTAVIA_WG_PUBKEY}" +ARIA_WG_PUBKEY="${ARIA_WG_PUBKEY:?Set ARIA_WG_PUBKEY}" +ALICE_WG_PUBKEY="${ALICE_WG_PUBKEY:?Set ALICE_WG_PUBKEY}" +CF_API_TOKEN="${CF_API_TOKEN:?Set CF_API_TOKEN for Caddy DNS challenge}" + +echo "" +echo "═══════════════════════════════════════════════════" +echo " Phase 3: Edge Setup - codex-infinity" +echo " IP: $(hostname -I | awk '{print $1}')" +echo "═══════════════════════════════════════════════════" +echo "" + +# ─── WIREGUARD ─── +setup_wireguard() { + log "Installing WireGuard..." + apt update && apt install -y wireguard + + cat > /etc/wireguard/wg0.conf << WGCONF +[Interface] +Address = 10.10.0.1/24 +ListenPort = 51820 +PrivateKey = ${CODEX_WG_PRIVKEY} +PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; sysctl -w net.ipv4.ip_forward=1 +PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE + +# Cecilia - DB + DNS (Pi 5) +[Peer] +PublicKey = ${CECILIA_WG_PUBKEY} +AllowedIPs = 10.10.0.2/32 +PersistentKeepalive = 25 + +# Lucidia - MinIO (Pi 5) +[Peer] +PublicKey = ${LUCIDIA_WG_PUBKEY} +AllowedIPs = 10.10.0.3/32 +PersistentKeepalive = 25 + +# Octavia - App Server (Pi 5) +[Peer] +PublicKey = ${OCTAVIA_WG_PUBKEY} +AllowedIPs = 10.10.0.4/32 +PersistentKeepalive = 25 + +# Aria - AI + Mesh (Pi 5) +[Peer] +PublicKey = ${ARIA_WG_PUBKEY} +AllowedIPs = 10.10.0.5/32 +PersistentKeepalive = 25 + +# Alice - Monitoring (Pi 4) +[Peer] +PublicKey = ${ALICE_WG_PUBKEY} +AllowedIPs = 10.10.0.7/32 +PersistentKeepalive = 25 +WGCONF + + chmod 600 /etc/wireguard/wg0.conf + systemctl enable wg-quick@wg0 + systemctl start wg-quick@wg0 + log "WireGuard running (10.10.0.1)" +} + +# ─── CADDY ─── +setup_caddy() { + log "Installing Caddy with Cloudflare DNS module..." + + # Install xcaddy to build custom Caddy + apt install -y golang debian-keyring debian-archive-keyring apt-transport-https curl + + # Download Caddy with cloudflare DNS plugin + curl -o /tmp/caddy "https://caddyserver.com/api/download?os=linux&arch=amd64&p=github.com/caddy-dns/cloudflare" + chmod +x /tmp/caddy + mv /tmp/caddy /usr/bin/caddy + + # Stop any existing web servers + systemctl stop nginx 2>/dev/null || true + systemctl disable nginx 2>/dev/null || true + systemctl stop apache2 2>/dev/null || true + systemctl disable apache2 2>/dev/null || true + + # Create Caddy environment file + mkdir -p /etc/caddy + cat > /etc/caddy/env << ENV +CF_API_TOKEN=${CF_API_TOKEN} +ENV + chmod 600 /etc/caddy/env + + # Write the Caddyfile + cat > /etc/caddy/Caddyfile << 'CADDYFILE' +{ + email blackroad.systems@gmail.com + acme_dns cloudflare {env.CF_API_TOKEN} +} + +# ══════════════════════════════════════════════ +# TIER 1: Critical API Services +# ══════════════════════════════════════════════ + +# API Gateway (Octavia :3000) +api.blackroad.io { + reverse_proxy 10.10.0.4:3000 + header { + X-Powered-By "BlackRoad OS" + Strict-Transport-Security "max-age=31536000; includeSubDomains" + X-Content-Type-Options "nosniff" + X-Frame-Options "DENY" + } +} + +core.blackroad.io { + reverse_proxy 10.10.0.4:3000 +} + +operator.blackroad.io { + reverse_proxy 10.10.0.4:3001 +} + +# Payment Gateway (Cecilia :3002 - same machine as DB for security) +pay.blackroad.io, payments.blackroad.io { + reverse_proxy 10.10.0.2:3002 + header { + Strict-Transport-Security "max-age=31536000; includeSubDomains" + X-Content-Type-Options "nosniff" + X-Frame-Options "DENY" + Content-Security-Policy "default-src 'self' https://js.stripe.com; script-src 'self' 'unsafe-inline' https://js.stripe.com; frame-src https://js.stripe.com" + } +} + +# Command Center (Octavia :3003) +cmd.blackroad.io { + reverse_proxy 10.10.0.4:3003 +} + +# Tools API (Octavia :3004) +tools.blackroad.io { + reverse_proxy 10.10.0.4:3004 +} + +# Agents API (Octavia :3005) +agents.blackroad.io { + reverse_proxy 10.10.0.4:3005 +} + +# ══════════════════════════════════════════════ +# TIER 2: Site/Page Workers +# ══════════════════════════════════════════════ + +# Main site (Octavia :8080) +blackroad.io, www.blackroad.io { + reverse_proxy 10.10.0.4:8080 + header { + Strict-Transport-Security "max-age=31536000; includeSubDomains" + } +} + +# Prism Console (Octavia :8081) +prism.blackroad.io { + reverse_proxy 10.10.0.4:8081 +} + +# Mesh (Octavia :8082) +mesh.blackroad.io { + reverse_proxy 10.10.0.4:8082 +} + +# Platform Hub (Octavia :8083) +hub.blackroad.io { + reverse_proxy 10.10.0.4:8083 +} + +# Jobs Platform (Octavia :8084) +jobs.blackroad.io { + reverse_proxy 10.10.0.4:8084 +} + +# Dashboard (Octavia :8085) +dashboard.blackroad.io { + reverse_proxy 10.10.0.4:8085 +} + +# AI/Ollama (Aria) +ai.blackroad.io { + reverse_proxy 10.10.0.5:11434 +} + +# MinIO Console (Lucidia) +storage.blackroad.io { + reverse_proxy 10.10.0.3:9001 +} + +# ══════════════════════════════════════════════ +# TIER 3: Subdomain Router (catch-all) +# All 67+ subdomains → single Hono service on Octavia :4000 +# ══════════════════════════════════════════════ + +*.blackroad.io { + reverse_proxy 10.10.0.4:4000 + header { + X-Powered-By "BlackRoad OS" + } +} +CADDYFILE + + # Caddy systemd service with env file + mkdir -p /etc/systemd/system/caddy.service.d + cat > /etc/systemd/system/caddy.service.d/override.conf << 'OVERRIDE' +[Service] +EnvironmentFile=/etc/caddy/env +OVERRIDE + + systemctl daemon-reload + systemctl enable caddy + systemctl start caddy + log "Caddy running with auto-SSL" +} + +# ─── FIREWALL ─── +setup_firewall() { + log "Configuring firewall..." + + apt install -y ufw + ufw default deny incoming + ufw default allow outgoing + ufw allow 22/tcp comment "SSH" + ufw allow 80/tcp comment "HTTP (ACME challenge)" + ufw allow 443/tcp comment "HTTPS" + ufw allow 51820/udp comment "WireGuard" + ufw --force enable + log "Firewall: SSH, HTTP, HTTPS, WireGuard only" +} + +# ─── VERIFY ─── +verify() { + echo "" + log "Verifying..." + + # WireGuard + if wg show wg0 &>/dev/null; then + local peers + peers=$(wg show wg0 peers | wc -l) + log " WireGuard: UP ($peers peers configured)" + else + error " WireGuard: DOWN" + fi + + # Caddy + if systemctl is-active caddy &>/dev/null; then + log " Caddy: running" + else + error " Caddy: not running" + journalctl -u caddy --no-pager -n 20 + fi + + # Firewall + info " Firewall rules:" + ufw status numbered 2>/dev/null | head -20 +} + +# ─── MAIN ─── +setup_wireguard +setup_caddy +setup_firewall +verify + +echo "" +echo "═══════════════════════════════════════════════════" +log "Phase 3 (edge) complete!" +info "WireGuard: 10.10.0.1 on :51820" +info "Caddy: HTTPS on :443 with auto-SSL" +info "" +info "Next: Set up WireGuard on each Pi" +info " scp setup-pi-wireguard.sh pi@:/tmp/" +info " ssh pi@ 'sudo bash /tmp/setup-pi-wireguard.sh'" +echo "═══════════════════════════════════════════════════" diff --git a/blackroad-infra/migration/phase3-wireguard-caddy/setup-pi-wireguard.sh b/blackroad-infra/migration/phase3-wireguard-caddy/setup-pi-wireguard.sh new file mode 100644 index 0000000000..a776415a4f --- /dev/null +++ b/blackroad-infra/migration/phase3-wireguard-caddy/setup-pi-wireguard.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# Setup WireGuard on a Pi to connect to codex-infinity +# Run ON each Pi: sudo bash setup-pi-wireguard.sh +# Example: sudo bash setup-pi-wireguard.sh cecilia +set -euo pipefail + +DEVICE="${1:?Usage: $0 (cecilia|lucidia|octavia|aria|alice)}" + +CODEX_PUBKEY="${CODEX_WG_PUBKEY:?Set CODEX_WG_PUBKEY}" +MY_PRIVKEY="${MY_WG_PRIVKEY:?Set MY_WG_PRIVKEY}" + +GREEN='\033[0;32m'; CYAN='\033[0;36m'; NC='\033[0m' +log() { echo -e "${GREEN}[+]${NC} $1"; } +info() { echo -e "${CYAN}[i]${NC} $1"; } + +# Device → WireGuard IP mapping +declare -A WG_IPS=( + ["cecilia"]="10.10.0.2" + ["lucidia"]="10.10.0.3" + ["octavia"]="10.10.0.4" + ["aria"]="10.10.0.5" + ["alice"]="10.10.0.7" +) + +MY_IP="${WG_IPS[$DEVICE]}" +if [ -z "$MY_IP" ]; then + echo "Unknown device: $DEVICE" + echo "Valid: cecilia, lucidia, octavia, aria, alice" + exit 1 +fi + +log "Setting up WireGuard for $DEVICE ($MY_IP)" + +# Install WireGuard +apt update && apt install -y wireguard + +# Write config +cat > /etc/wireguard/wg0.conf << WGCONF +[Interface] +Address = ${MY_IP}/24 +PrivateKey = ${MY_PRIVKEY} + +[Peer] +PublicKey = ${CODEX_PUBKEY} +Endpoint = 159.65.43.12:51820 +AllowedIPs = 10.10.0.0/24 +PersistentKeepalive = 25 +WGCONF + +chmod 600 /etc/wireguard/wg0.conf + +systemctl enable wg-quick@wg0 +systemctl start wg-quick@wg0 + +# Verify +sleep 2 +if ping -c 1 -W 3 10.10.0.1 &>/dev/null; then + log "Connected to codex-infinity (10.10.0.1)" +else + echo -e "\033[1;33m[!]\033[0m Cannot ping 10.10.0.1 yet - may need NAT traversal time" +fi + +log "WireGuard configured for $DEVICE at $MY_IP" diff --git a/blackroad-infra/migration/phase4-services/.env.example b/blackroad-infra/migration/phase4-services/.env.example new file mode 100644 index 0000000000..3a3fffc8c1 --- /dev/null +++ b/blackroad-infra/migration/phase4-services/.env.example @@ -0,0 +1,35 @@ +# BlackRoad Self-Hosted Services - Environment Variables +# Copy to .env and fill in real values + +# Database (Cecilia) +PG_HOST=10.10.0.2 +PG_PORT=5432 +PG_USER=blackroad +PG_PASSWORD=CHANGE_ME + +# Redis (Cecilia) +REDIS_HOST=10.10.0.2 +REDIS_PORT=6379 +REDIS_PASSWORD=CHANGE_ME + +# Stripe (Payment Gateway) +STRIPE_SECRET_KEY=sk_live_CHANGE_ME +STRIPE_PUBLISHABLE_KEY=pk_live_CHANGE_ME +STRIPE_WEBHOOK_SECRET=whsec_CHANGE_ME +STRIPE_PRICE_PRO_MONTHLY=price_CHANGE_ME +STRIPE_PRICE_PRO_YEARLY=price_CHANGE_ME +STRIPE_PRICE_ENT_MONTHLY=price_CHANGE_ME +STRIPE_PRICE_ENT_YEARLY=price_CHANGE_ME + +# AI Keys +ANTHROPIC_API_KEY= +OPENAI_API_KEY= + +# MinIO (Lucidia) +MINIO_ENDPOINT=10.10.0.3 +MINIO_PORT=9000 +MINIO_ACCESS_KEY=blackroad-admin +MINIO_SECRET_KEY=CHANGE_ME + +# General +NODE_ENV=production diff --git a/blackroad-infra/migration/phase4-services/adapters/d1-adapter.ts b/blackroad-infra/migration/phase4-services/adapters/d1-adapter.ts new file mode 100644 index 0000000000..dd83f390bc --- /dev/null +++ b/blackroad-infra/migration/phase4-services/adapters/d1-adapter.ts @@ -0,0 +1,177 @@ +/** + * D1 Adapter - PostgreSQL-backed replacement for Cloudflare D1 + * + * Drop-in replacement for D1Database interface used by Workers. + * Each D1 database maps to a PostgreSQL database on Cecilia. + */ + +import pg from 'pg'; +const { Pool } = pg; + +const pools: Map = new Map(); + +function getPool(dbName: string): pg.Pool { + if (!pools.has(dbName)) { + pools.set(dbName, new Pool({ + host: process.env.PG_HOST || '10.10.0.2', + port: parseInt(process.env.PG_PORT || '5432'), + database: dbName, + user: process.env.PG_USER || 'blackroad', + password: process.env.PG_PASSWORD, + max: 20, + idleTimeoutMillis: 30000, + connectionTimeoutMillis: 5000, + })); + } + return pools.get(dbName)!; +} + +export interface D1Result> { + results: T[]; + success: boolean; + meta?: { + duration?: number; + rows_read?: number; + rows_written?: number; + }; +} + +class D1PreparedStatement { + private params: unknown[] = []; + private convertedSql: string; + + constructor(private pool: pg.Pool, private sql: string) { + this.convertedSql = sql; + } + + bind(...values: unknown[]): D1PreparedStatement { + this.params = values; + + // Convert D1/SQLite ? placeholders to PostgreSQL $1, $2, ... + let idx = 0; + this.convertedSql = this.sql.replace(/\?/g, () => `$${++idx}`); + + return this; + } + + async first>(column?: string): Promise { + const start = Date.now(); + try { + const result = await this.pool.query(this.convertedSql, this.params); + if (result.rows.length === 0) return null; + if (column) return result.rows[0][column] as T; + return result.rows[0] as T; + } catch (err) { + console.error(`D1 query error: ${this.convertedSql}`, err); + return null; + } + } + + async all>(): Promise> { + const start = Date.now(); + try { + const result = await this.pool.query(this.convertedSql, this.params); + return { + results: result.rows as T[], + success: true, + meta: { + duration: Date.now() - start, + rows_read: result.rowCount || 0, + }, + }; + } catch (err) { + console.error(`D1 query error: ${this.convertedSql}`, err); + return { results: [], success: false }; + } + } + + async run(): Promise { + const start = Date.now(); + try { + const result = await this.pool.query(this.convertedSql, this.params); + return { + results: [], + success: true, + meta: { + duration: Date.now() - start, + rows_written: result.rowCount || 0, + }, + }; + } catch (err) { + console.error(`D1 query error: ${this.convertedSql}`, err); + return { results: [], success: false }; + } + } + + async raw(): Promise { + try { + const result = await this.pool.query({ + text: this.convertedSql, + values: this.params, + rowMode: 'array', + }); + return result.rows as T[]; + } catch (err) { + console.error(`D1 raw query error: ${this.convertedSql}`, err); + return []; + } + } +} + +export class D1Database { + private pool: pg.Pool; + + constructor(dbName: string) { + this.pool = getPool(dbName); + } + + prepare(sql: string): D1PreparedStatement { + return new D1PreparedStatement(this.pool, sql); + } + + async exec(sql: string): Promise { + try { + await this.pool.query(sql); + return { results: [], success: true }; + } catch (err) { + console.error(`D1 exec error:`, err); + return { results: [], success: false }; + } + } + + async batch(statements: D1PreparedStatement[]): Promise { + const results: D1Result[] = []; + const client = await this.pool.connect(); + try { + await client.query('BEGIN'); + for (const stmt of statements) { + results.push(await stmt.run()); + } + await client.query('COMMIT'); + } catch (err) { + await client.query('ROLLBACK'); + console.error('D1 batch error:', err); + } finally { + client.release(); + } + return results; + } +} + +/** Create D1 databases matching Cloudflare Worker bindings */ +export function createD1Databases() { + return { + DB: new D1Database('blackroad_os_main'), // api-gateway, subdomain-router + REVENUE_D1: new D1Database('blackroad_revenue'), // payment-gateway + CONTINUITY: new D1Database('blackroad_continuity'), // tools-api, agents-api, command-center + SAAS: new D1Database('blackroad_saas'), // blackroad-os + AGENTS_DB: new D1Database('apollo_agent_registry'), // prism-console + }; +} + +export async function closePools(): Promise { + for (const [name, pool] of pools) { + await pool.end(); + } + pools.clear(); +} diff --git a/blackroad-infra/migration/phase4-services/adapters/kv-adapter.ts b/blackroad-infra/migration/phase4-services/adapters/kv-adapter.ts new file mode 100644 index 0000000000..d40aaa79b4 --- /dev/null +++ b/blackroad-infra/migration/phase4-services/adapters/kv-adapter.ts @@ -0,0 +1,156 @@ +/** + * KV Adapter - Redis-backed replacement for Cloudflare KV + * + * Drop-in replacement for KVNamespace interface used by Workers. + * Each KV namespace becomes a Redis key prefix. + */ + +import Redis from 'ioredis'; + +let redis: Redis | null = null; + +function getRedis(): Redis { + if (!redis) { + redis = new Redis({ + host: process.env.REDIS_HOST || '10.10.0.2', + port: parseInt(process.env.REDIS_PORT || '6379'), + password: process.env.REDIS_PASSWORD, + retryStrategy: (times) => Math.min(times * 100, 3000), + maxRetriesPerRequest: 3, + }); + } + return redis; +} + +export interface KVGetOptions { + type?: 'text' | 'json' | 'arrayBuffer' | 'stream'; + cacheTtl?: number; +} + +export interface KVPutOptions { + expirationTtl?: number; + expiration?: number; + metadata?: Record; +} + +export interface KVListResult { + keys: Array<{ name: string; expiration?: number; metadata?: Record }>; + list_complete: boolean; + cursor?: string; +} + +export class KVNamespace { + constructor(private prefix: string) {} + + async get(key: string, options?: KVGetOptions | string): Promise { + const r = getRedis(); + const val = await r.get(`${this.prefix}:${key}`); + if (val === null) return null; + + const type = typeof options === 'string' ? options : options?.type; + if (type === 'json') { + try { + return JSON.parse(val); + } catch { + return null; + } + } + return val; + } + + async getWithMetadata(key: string, options?: KVGetOptions | string): Promise<{ + value: any; + metadata: Record | null; + }> { + const value = await this.get(key, options); + const r = getRedis(); + const metaStr = await r.get(`${this.prefix}:${key}:__meta`); + const metadata = metaStr ? JSON.parse(metaStr) : null; + return { value, metadata }; + } + + async put(key: string, value: string | ArrayBuffer, options?: KVPutOptions): Promise { + const r = getRedis(); + const strValue = typeof value === 'string' ? value : Buffer.from(value).toString(); + + if (options?.expirationTtl) { + await r.setex(`${this.prefix}:${key}`, options.expirationTtl, strValue); + } else if (options?.expiration) { + const ttl = options.expiration - Math.floor(Date.now() / 1000); + if (ttl > 0) { + await r.setex(`${this.prefix}:${key}`, ttl, strValue); + } + } else { + await r.set(`${this.prefix}:${key}`, strValue); + } + + if (options?.metadata) { + const metaKey = `${this.prefix}:${key}:__meta`; + if (options.expirationTtl) { + await r.setex(metaKey, options.expirationTtl, JSON.stringify(options.metadata)); + } else { + await r.set(metaKey, JSON.stringify(options.metadata)); + } + } + } + + async delete(key: string): Promise { + const r = getRedis(); + await r.del(`${this.prefix}:${key}`, `${this.prefix}:${key}:__meta`); + } + + async list(options?: { + prefix?: string; + limit?: number; + cursor?: string; + }): Promise { + const r = getRedis(); + const pattern = options?.prefix + ? `${this.prefix}:${options.prefix}*` + : `${this.prefix}:*`; + + const limit = options?.limit || 1000; + const cursor = options?.cursor || '0'; + + const [newCursor, rawKeys] = await r.scan( + parseInt(cursor), + 'MATCH', pattern, + 'COUNT', limit + ); + + // Filter out metadata keys + const keys = (rawKeys as string[]) + .filter(k => !k.endsWith(':__meta')) + .map(k => ({ name: k.replace(`${this.prefix}:`, '') })); + + return { + keys, + list_complete: newCursor === '0', + cursor: newCursor === '0' ? undefined : newCursor, + }; + } +} + +/** Create KV namespaces matching Cloudflare Worker bindings */ +export function createKVNamespaces() { + return { + CACHE: new KVNamespace('CACHE'), + IDENTITIES: new KVNamespace('IDENTITIES'), + API_KEYS: new KVNamespace('API_KEYS'), + RATE_LIMIT: new KVNamespace('RATE_LIMIT'), + TOOLS_KV: new KVNamespace('TOOLS_KV'), + TEMPLATES: new KVNamespace('TEMPLATES'), + CONTENT: new KVNamespace('CONTENT'), + SUBSCRIPTIONS_KV: new KVNamespace('SUBSCRIPTIONS_KV'), + USERS_KV: new KVNamespace('USERS_KV'), + JOBS: new KVNamespace('JOBS'), + APPLICATIONS: new KVNamespace('APPLICATIONS'), + }; +} + +export async function closeRedis(): Promise { + if (redis) { + await redis.quit(); + redis = null; + } +} diff --git a/blackroad-infra/migration/phase4-services/api-gateway/server.ts b/blackroad-infra/migration/phase4-services/api-gateway/server.ts new file mode 100644 index 0000000000..86ec0f656f --- /dev/null +++ b/blackroad-infra/migration/phase4-services/api-gateway/server.ts @@ -0,0 +1,233 @@ +/** + * BlackRoad API Gateway - Self-Hosted (Hono) + * + * Ported from Cloudflare Worker: workers/api-gateway/src/index.ts + * Replaces: api.blackroad.io, core.blackroad.io, operator.blackroad.io + * Port: 3000 + */ + +import { Hono } from 'hono'; +import { cors } from 'hono/cors'; +import { serve } from '@hono/node-server'; +import { KVNamespace } from '../adapters/kv-adapter.js'; +import { D1Database } from '../adapters/d1-adapter.js'; + +const app = new Hono(); + +// Adapters (replacing Cloudflare bindings) +const CACHE = new KVNamespace('CACHE'); +const RATE_LIMIT = new KVNamespace('RATE_LIMIT'); +const DB = new D1Database('blackroad_os_main'); + +// CORS +app.use('*', cors({ + origin: '*', + allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowHeaders: ['Content-Type', 'Authorization'], +})); + +// ─── Rate Limiting ─── +app.use('*', async (c, next) => { + const ip = c.req.header('x-forwarded-for')?.split(',')[0]?.trim() + || c.req.header('x-real-ip') + || 'unknown'; + const key = `rate-limit:${ip}`; + const current = await RATE_LIMIT.get(key); + const count = current ? parseInt(current) : 0; + + if (count >= 1000) { + return c.json({ error: 'Rate limit exceeded' }, 429); + } + + await RATE_LIMIT.put(key, (count + 1).toString(), { expirationTtl: 60 }); + await next(); +}); + +// ─── Health ─── +app.get('/health', (c) => c.json({ + status: 'healthy', + service: 'api-gateway', + timestamp: new Date().toISOString(), + source: 'self-hosted', +})); + +app.get('/api/health', (c) => c.json({ + status: 'ok', + timestamp: new Date().toISOString(), + worker: 'api-gateway', + kv_namespaces: 4, + d1_databases: 1, +})); + +// ─── Agents ─── +const AGENTS = [ + { id: 'lucidia', role: 'Consciousness Coordinator', status: 'active' }, + { id: 'alice', role: 'Router', status: 'active' }, + { id: 'octavia', role: 'Workflow Orchestrator', status: 'active' }, + { id: 'cipher', role: 'Cryptographer', status: 'active' }, + { id: 'echo', role: 'Memory Keeper', status: 'active' }, + { id: 'prism', role: 'Multi-dimensional Analyst', status: 'active' }, + { id: 'atlas', role: 'Load Bearer', status: 'active' }, + { id: 'cadence', role: 'Rhythm Keeper', status: 'active' }, + { id: 'shellfish', role: 'The Hacker', status: 'active' }, + { id: 'nova', role: 'Innovator', status: 'active' }, + { id: 'ember', role: 'Spark', status: 'active' }, + { id: 'phoenix', role: 'Resilience', status: 'active' }, + { id: 'sentinel', role: 'Watchdog', status: 'active' }, + { id: 'claude', role: 'Strategic Architect', status: 'active' }, + { id: 'silas', role: 'Security Sentinel', status: 'active' }, +]; + +app.get('/api/agents', (c) => c.json({ agents: AGENTS, count: AGENTS.length })); + +// ─── Status (live health probes) ─── +app.get('/api/status', async (c) => { + const checks: Record = {}; + + try { await CACHE.get('__health'); checks.kv_cache = 'healthy'; } + catch { checks.kv_cache = 'down'; } + + try { await DB.prepare('SELECT 1').first(); checks.d1 = 'healthy'; } + catch { checks.d1 = 'down'; } + + checks.workers = 'healthy'; + const allUp = Object.values(checks).every(v => v === 'healthy'); + + return c.json({ + overall: allUp ? 'operational' : 'degraded', + ...checks, + timestamp: new Date().toISOString(), + }); +}); + +// ─── Analytics ─── +app.get('/api/analytics', async (c) => { + try { + const tot = await DB.prepare('SELECT COUNT(*) as c FROM analytics').first<{ c: number }>(); + const hr = await DB.prepare('SELECT COUNT(*) as c FROM analytics WHERE ts > ?') + .bind(Date.now() - 3600000).first<{ c: number }>(); + const top = await DB.prepare( + 'SELECT subdomain, COUNT(*) as cnt FROM analytics GROUP BY subdomain ORDER BY cnt DESC LIMIT 10' + ).all(); + + return c.json({ + total_hits: tot?.c ?? 0, + last_hour: hr?.c ?? 0, + top_subdomains: top.results, + }); + } catch (e: any) { + return c.json({ error: 'Analytics unavailable', message: e.message }, 500); + } +}); + +// ─── Subdomains ─── +const SUBDOMAINS = [ + 'os', 'ai', 'agents', 'api', 'status', 'docs', 'console', 'dashboard', 'chat', 'playground', + 'marketplace', 'roadmap', 'changelog', 'security', 'careers', 'store', 'search', 'terminal', 'world', + 'admin', 'analytics', 'network', 'prism', 'brand', 'design', 'edge', 'data', 'finance', 'quantum', 'blog', + 'dev', 'staging', 'metrics', 'logs', 'cdn', 'assets', 'app', 'about', 'help', 'products', 'pitstop', + 'algorithms', 'blockchain', 'blocks', 'chain', 'circuits', 'compliance', 'compute', 'control', 'editor', + 'engineering', 'events', 'explorer', 'features', 'guide', 'hardware', 'hr', 'ide', 'asia', 'eu', 'global', +]; + +app.get('/api/subdomains', (c) => c.json({ subdomains: SUBDOMAINS, count: SUBDOMAINS.length })); + +// ─── Packs ─── +app.get('/api/packs', (c) => c.json({ + packs: ['pack-finance', 'pack-legal', 'pack-research-lab', 'pack-creator-studio', 'pack-infra-devops'], + total: 5, + description: 'Domain-specific agent bundles', +})); + +// ─── SEO ─── +app.get('/robots.txt', (c) => { + c.header('Content-Type', 'text/plain'); + c.header('Cache-Control', 'public, max-age=86400'); + return c.text('User-agent: *\nAllow: /\nSitemap: https://api.blackroad.io/sitemap.xml'); +}); + +app.get('/sitemap.xml', (c) => { + const entries = SUBDOMAINS.slice(0, 40) + .map(s => ` https://${s}.blackroad.io/weekly0.8`) + .join('\n'); + c.header('Content-Type', 'application/xml'); + c.header('Cache-Control', 'public, max-age=86400'); + return c.text(`\n\n${entries}\n`); +}); + +// ─── Agents API ─── +app.get('/agents', (c) => c.json({ + agents: [ + { id: 'claude', status: 'active', role: 'Strategic Architect' }, + { id: 'lucidia', status: 'active', role: 'Consciousness Coordinator' }, + { id: 'silas', status: 'active', role: 'Security Sentinel' }, + ], + total: 3, +})); + +app.post('/agents/spawn', async (c) => { + const body = await c.req.json(); + return c.json({ + agent_id: `agent-${Date.now()}`, + role: body.role || 'general', + status: 'spawning', + message: 'Agent spawn initiated', + }, 202); +}); + +// ─── Quantum / Lucidia / Auth ─── +app.get('/quantum/*', (c) => c.json({ + service: 'Quantum API', + status: 'operational', + message: 'Quantum computing interface', +})); + +app.get('/lucidia/*', (c) => c.json({ + service: 'Lucidia API', + breath_value: Math.sin(Date.now() / 1000 * 1.618034), + status: 'operational', +})); + +app.post('/auth/verify', async (c) => { + const authHeader = c.req.header('Authorization'); + if (!authHeader) { + return c.json({ error: 'No authorization header' }, 401); + } + return c.json({ valid: true, user_id: 'user-123', permissions: ['read', 'write'] }); +}); + +app.get('/auth/*', (c) => c.json({ + service: 'Auth API', + endpoints: { verify: 'POST /auth/verify' }, +})); + +// ─── Default (root) ─── +app.get('/', (c) => c.json({ + service: 'BlackRoad API Gateway', + version: '3.0.0', + source: 'self-hosted', + endpoints: { + 'api/health': 'GET - Health check with service info', + 'api/agents': 'GET - List all agents with metadata', + 'api/subdomains': 'GET - List all subdomains', + 'api/status': 'GET - Live health probes (Redis, PostgreSQL)', + 'api/analytics': 'GET - Traffic analytics from PostgreSQL', + 'api/packs': 'GET - Available agent packs', + health: 'GET - Legacy health check', + 'agents/*': 'Agent management', + 'quantum/*': 'Quantum computing API', + 'lucidia/*': 'Lucidia consciousness API', + 'auth/*': 'Authentication API', + }, + timestamp: new Date().toISOString(), +})); + +// ─── Start ─── +const PORT = parseInt(process.env.PORT || '3000'); +console.log(`[api-gateway] Starting on port ${PORT}`); + +serve({ fetch: app.fetch, port: PORT }, (info) => { + console.log(`[api-gateway] Listening on http://0.0.0.0:${info.port}`); +}); + +export default app; diff --git a/blackroad-infra/migration/phase4-services/ecosystem.config.cjs b/blackroad-infra/migration/phase4-services/ecosystem.config.cjs new file mode 100644 index 0000000000..f58be3497c --- /dev/null +++ b/blackroad-infra/migration/phase4-services/ecosystem.config.cjs @@ -0,0 +1,123 @@ +/** + * PM2 Ecosystem Configuration + * + * Services on Octavia (192.168.4.38): + * - API Gateway (:3000) + * - Operator API (:3001) + * - Command Center (:3003) + * - Tools API (:3004) + * - Agents API (:3005) + * - Subdomain Router (:4000) + * - Info Server (:8090) + * + * Services on Cecilia (192.168.4.89): + * - Payment Gateway (:3002) + * + * Deploy to Octavia: + * scp -r phase4-services/ pi@192.168.4.38:/home/pi/blackroad-services/ + * ssh pi@192.168.4.38 'cd /home/pi/blackroad-services && npm install && pm2 start ecosystem.config.cjs' + * + * Deploy Payment Gateway to Cecilia: + * scp -r phase4-services/{payment-gateway,adapters,package.json,tsconfig.json,.env} pi@192.168.4.89:/home/pi/blackroad-services/ + * ssh pi@192.168.4.89 'cd /home/pi/blackroad-services && npm install && pm2 start ecosystem.config.cjs --only payment-gateway' + */ + +const dotenv = require('dotenv'); +dotenv.config(); + +module.exports = { + apps: [ + // ── Tier 1: Critical API ── + { + name: 'api-gateway', + script: 'npx', + args: 'tsx api-gateway/server.ts', + env: { + PORT: 3000, + NODE_ENV: 'production', + PG_HOST: process.env.PG_HOST || '10.10.0.2', + PG_PORT: process.env.PG_PORT || '5432', + PG_USER: process.env.PG_USER || 'blackroad', + PG_PASSWORD: process.env.PG_PASSWORD, + REDIS_HOST: process.env.REDIS_HOST || '10.10.0.2', + REDIS_PORT: process.env.REDIS_PORT || '6379', + REDIS_PASSWORD: process.env.REDIS_PASSWORD, + }, + max_memory_restart: '256M', + instances: 1, + autorestart: true, + watch: false, + error_file: '/home/pi/logs/api-gateway-error.log', + out_file: '/home/pi/logs/api-gateway-out.log', + merge_logs: true, + }, + + { + name: 'payment-gateway', + script: 'npx', + args: 'tsx payment-gateway/server.ts', + env: { + PORT: 3002, + NODE_ENV: 'production', + PG_HOST: '127.0.0.1', // Local on Cecilia + PG_PORT: '5432', + PG_USER: process.env.PG_USER || 'blackroad', + PG_PASSWORD: process.env.PG_PASSWORD, + REDIS_HOST: '127.0.0.1', + REDIS_PORT: '6379', + REDIS_PASSWORD: process.env.REDIS_PASSWORD, + STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY, + STRIPE_PUBLISHABLE_KEY: process.env.STRIPE_PUBLISHABLE_KEY, + STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET, + STRIPE_PRICE_PRO_MONTHLY: process.env.STRIPE_PRICE_PRO_MONTHLY, + STRIPE_PRICE_PRO_YEARLY: process.env.STRIPE_PRICE_PRO_YEARLY, + STRIPE_PRICE_ENT_MONTHLY: process.env.STRIPE_PRICE_ENT_MONTHLY, + STRIPE_PRICE_ENT_YEARLY: process.env.STRIPE_PRICE_ENT_YEARLY, + }, + max_memory_restart: '256M', + instances: 1, + autorestart: true, + watch: false, + error_file: '/home/pi/logs/payment-gateway-error.log', + out_file: '/home/pi/logs/payment-gateway-out.log', + merge_logs: true, + }, + + // ── Subdomain Router ── + { + name: 'subdomain-router', + script: 'npx', + args: 'tsx subdomain-router/server.ts', + env: { + PORT: 4000, + NODE_ENV: 'production', + PG_HOST: process.env.PG_HOST || '10.10.0.2', + PG_PASSWORD: process.env.PG_PASSWORD, + REDIS_HOST: process.env.REDIS_HOST || '10.10.0.2', + REDIS_PASSWORD: process.env.REDIS_PASSWORD, + }, + max_memory_restart: '512M', + instances: 1, + autorestart: true, + watch: false, + error_file: '/home/pi/logs/subdomain-router-error.log', + out_file: '/home/pi/logs/subdomain-router-out.log', + merge_logs: true, + }, + + // ── Info Server (consolidated boilerplate workers) ── + { + name: 'info-server', + script: 'npx', + args: 'tsx info-server/server.ts', + env: { + PORT: 8090, + NODE_ENV: 'production', + }, + max_memory_restart: '128M', + instances: 1, + autorestart: true, + watch: false, + }, + ], +}; diff --git a/blackroad-infra/migration/phase4-services/info-server/server.ts b/blackroad-infra/migration/phase4-services/info-server/server.ts new file mode 100644 index 0000000000..8ae27ca669 --- /dev/null +++ b/blackroad-infra/migration/phase4-services/info-server/server.ts @@ -0,0 +1,99 @@ +/** + * BlackRoad Info Server - Consolidated Boilerplate Workers + * + * Replaces 16 Tier 4 Workers that just return JSON metadata. + * Port: 8090 + */ + +import { Hono } from 'hono'; +import { serve } from '@hono/node-server'; + +const app = new Hono(); + +const SERVICES: Record = { + 'blackroad-os-core': { name: 'Core Library', type: 'library', description: 'Shared types, contracts, primitives', status: 'active' }, + 'blackroad-os-helper': { name: 'Helper Service', type: 'service', description: 'Assistant and helper agent', status: 'active' }, + 'blackroad-os-docs': { name: 'Documentation', type: 'docs', description: 'Guides, tutorials, API reference', status: 'active', repo: 'BlackRoad-OS/blackroad-os-docs' }, + 'blackroad-cli': { name: 'CLI Tools', type: 'cli', description: 'Command-line interface', status: 'active', repo: 'BlackRoad-OS/blackroad-cli' }, + 'blackroad-agents': { name: 'Agent System', type: 'service', description: 'Agent orchestration', status: 'active' }, + 'blackroad-agent-os': { name: 'Agent OS', type: 'platform', description: 'Agent operating system', status: 'active' }, + 'blackroad-hello': { name: 'Hello World', type: 'demo', description: 'Example service', status: 'active' }, + 'containers-template': { name: 'Container Template', type: 'template', description: 'Docker/K8s templates', status: 'active' }, + 'lucidia-core': { name: 'Lucidia Core', type: 'ai', description: 'AI reasoning engine', status: 'active' }, + 'blackroad-pi-ops': { name: 'Pi Operations', type: 'iot', description: 'Raspberry Pi management', status: 'active' }, + 'blackroad-tools': { name: 'Tools', type: 'tools', description: 'CRM/ERP adapters', status: 'active' }, + 'blackroad-os-metaverse': { name: 'Metaverse', type: 'app', description: '3D world visualization', status: 'active' }, + 'blackroad-os-pitstop': { name: 'Pitstop', type: 'portal', description: 'Portal and quick links hub', status: 'active' }, + 'blackroad-os-roadworld': { name: 'RoadWorld', type: 'app', description: 'World simulation services', status: 'active' }, + 'blackroad-os-dashboard': { name: 'Dashboard', type: 'ui', description: 'Monitoring dashboard', status: 'active' }, + 'blackroad-os-mesh': { name: 'Mesh', type: 'service', description: 'WebSocket mesh network', status: 'active' }, +}; + +app.get('/health', (c) => c.json({ + status: 'healthy', + service: 'info-server', + services_count: Object.keys(SERVICES).length, + source: 'self-hosted', + timestamp: new Date().toISOString(), +})); + +app.get('/services', (c) => c.json({ + services: Object.entries(SERVICES).map(([id, info]) => ({ id, ...info })), + count: Object.keys(SERVICES).length, +})); + +app.get('/services/:id', (c) => { + const id = c.req.param('id'); + const info = SERVICES[id]; + if (!info) return c.json({ error: 'Service not found' }, 404); + return c.json({ id, ...info, timestamp: new Date().toISOString() }); +}); + +// Catch-all: try to match by hostname +app.get('*', (c) => { + const host = c.req.header('host') || ''; + const sub = host.split('.')[0].toLowerCase(); + + // Try to match hostname to a service + const matchedKey = Object.keys(SERVICES).find(k => + k.replace('blackroad-', '').replace('os-', '').includes(sub) || sub.includes(k.replace('blackroad-', '')) + ); + + if (matchedKey) { + const info = SERVICES[matchedKey]; + return c.json({ + ...info, + id: matchedKey, + timestamp: new Date().toISOString(), + source: 'self-hosted', + }); + } + + return c.json({ + service: 'BlackRoad Info Server', + version: '1.0.0', + description: 'Consolidated service metadata endpoint', + available_services: Object.keys(SERVICES).length, + endpoints: { + '/health': 'GET - Health check', + '/services': 'GET - List all services', + '/services/:id': 'GET - Get service details', + }, + timestamp: new Date().toISOString(), + }); +}); + +const PORT = parseInt(process.env.PORT || '8090'); +console.log(`[info-server] Starting on port ${PORT}`); + +serve({ fetch: app.fetch, port: PORT }, (info) => { + console.log(`[info-server] Listening on http://0.0.0.0:${info.port}`); +}); + +export default app; diff --git a/blackroad-infra/migration/phase4-services/package.json b/blackroad-infra/migration/phase4-services/package.json new file mode 100644 index 0000000000..7080c4194e --- /dev/null +++ b/blackroad-infra/migration/phase4-services/package.json @@ -0,0 +1,24 @@ +{ + "name": "blackroad-services", + "version": "1.0.0", + "private": true, + "type": "module", + "description": "Self-hosted BlackRoad services (replaces Cloudflare Workers)", + "scripts": { + "start": "node --import tsx ecosystem.runner.js", + "dev": "tsx watch ecosystem.runner.js" + }, + "dependencies": { + "hono": "^4.6.0", + "@hono/node-server": "^1.13.0", + "ioredis": "^5.4.0", + "pg": "^8.13.0", + "dotenv": "^16.4.0" + }, + "devDependencies": { + "tsx": "^4.19.0", + "@types/node": "^22.0.0", + "@types/pg": "^8.11.0", + "typescript": "^5.6.0" + } +} diff --git a/blackroad-infra/migration/phase4-services/payment-gateway/pricing.ts b/blackroad-infra/migration/phase4-services/payment-gateway/pricing.ts new file mode 100644 index 0000000000..8360a61370 --- /dev/null +++ b/blackroad-infra/migration/phase4-services/payment-gateway/pricing.ts @@ -0,0 +1,116 @@ +/** + * BlackRoad OS Canonical Pricing - Single Source of Truth + * Ported 1:1 from workers/payment-gateway/src/pricing.ts + * Stripe price IDs now come from process.env instead of Worker secrets. + */ + +export interface PricingTier { + id: string; + name: string; + priceMonthly: number; + priceYearly: number; + features: string[]; + agentLimit: number; + trialDays: number; + stripePriceEnvMonthly: string; + stripePriceEnvYearly: string; + cta: string; + popular?: boolean; +} + +export const PRICING: PricingTier[] = [ + { + id: 'free', + name: 'Free', + priceMonthly: 0, + priceYearly: 0, + features: [ + '3 AI Agents', + '100 tasks/month', + 'Community support', + 'Basic analytics', + 'Public API (rate-limited)', + ], + agentLimit: 3, + trialDays: 0, + stripePriceEnvMonthly: '', + stripePriceEnvYearly: '', + cta: 'Get Started', + }, + { + id: 'pro', + name: 'Pro', + priceMonthly: 29, + priceYearly: 290, + features: [ + '100 AI Agents', + '10,000 tasks/month', + 'Priority support', + 'Advanced analytics', + 'Custom integrations', + 'API access (unlimited)', + 'Webhook notifications', + ], + agentLimit: 100, + trialDays: 14, + stripePriceEnvMonthly: 'STRIPE_PRICE_PRO_MONTHLY', + stripePriceEnvYearly: 'STRIPE_PRICE_PRO_YEARLY', + cta: 'Start Free Trial', + popular: true, + }, + { + id: 'enterprise', + name: 'Enterprise', + priceMonthly: 199, + priceYearly: 1990, + features: [ + 'Unlimited AI Agents', + 'Unlimited tasks', + '24/7 phone + Slack support', + 'Custom analytics dashboards', + 'Dedicated account manager', + 'On-premise deployment option', + 'SLA guarantees (99.9%)', + 'SSO / SAML', + 'Audit logs', + ], + agentLimit: -1, + trialDays: 14, + stripePriceEnvMonthly: 'STRIPE_PRICE_ENT_MONTHLY', + stripePriceEnvYearly: 'STRIPE_PRICE_ENT_YEARLY', + cta: 'Start Free Trial', + }, + { + id: 'custom', + name: 'Enterprise Custom', + priceMonthly: -1, + priceYearly: -1, + features: [ + 'Everything in Enterprise', + 'Custom agent limits', + 'White-label options', + 'Custom SLA', + 'Dedicated infrastructure', + 'Professional services', + 'Volume discounts', + ], + agentLimit: -1, + trialDays: 0, + stripePriceEnvMonthly: '', + stripePriceEnvYearly: '', + cta: 'Contact Sales', + }, +]; + +export function getTier(id: string): PricingTier | undefined { + return PRICING.find((t) => t.id === id); +} + +export function getStripePriceId( + tier: PricingTier, + period: 'monthly' | 'yearly', +): string { + const envKey = period === 'yearly' ? tier.stripePriceEnvYearly : tier.stripePriceEnvMonthly; + if (!envKey) return ''; + return process.env[envKey] || ''; +} diff --git a/blackroad-infra/migration/phase4-services/payment-gateway/server.ts b/blackroad-infra/migration/phase4-services/payment-gateway/server.ts new file mode 100644 index 0000000000..09a3269d3f --- /dev/null +++ b/blackroad-infra/migration/phase4-services/payment-gateway/server.ts @@ -0,0 +1,436 @@ +/** + * BlackRoad Payment Gateway - Self-Hosted (Hono) + * + * Ported from Cloudflare Worker: workers/payment-gateway/src/index.ts + * Replaces: pay.blackroad.io, payments.blackroad.io + * Port: 3002 + * + * RUNS ON CECILIA (same machine as PostgreSQL for security) + */ + +import { Hono } from 'hono'; +import { cors } from 'hono/cors'; +import { serve } from '@hono/node-server'; +import { KVNamespace } from '../adapters/kv-adapter.js'; +import { D1Database } from '../adapters/d1-adapter.js'; +import { PRICING, getTier, getStripePriceId, type PricingTier } from './pricing.js'; +import { verifyWebhookSignature } from './webhook-verify.js'; + +const app = new Hono(); + +// Adapters +const SUBSCRIPTIONS_KV = new KVNamespace('SUBSCRIPTIONS_KV'); +const USERS_KV = new KVNamespace('USERS_KV'); +const REVENUE_D1 = new D1Database('blackroad_revenue'); + +const CORS_ORIGIN = process.env.CORS_ORIGIN || 'https://blackroad.io'; + +app.use('*', cors({ + origin: CORS_ORIGIN, + allowMethods: ['GET', 'POST', 'OPTIONS'], + allowHeaders: ['Content-Type', 'Authorization'], +})); + +// ─── HTML Helpers ─── +function json(data: unknown, status = 200) { + return new Response(JSON.stringify(data), { + status, + headers: { 'Content-Type': 'application/json' }, + }); +} + +// ─── Health ─── +app.get('/health', (c) => c.json({ + status: 'healthy', + version: '3.0.0', + source: 'self-hosted', + timestamp: new Date().toISOString(), +})); + +// ─── Pricing ─── +app.get('/api/pricing', (c) => c.json(PRICING)); + +// ─── Pricing Page ─── +app.get('/', (c) => { + return c.html(renderPricingPage()); +}); + +app.get('/success', (c) => c.html(renderSuccessPage())); +app.get('/cancel', (c) => c.html(renderCancelPage())); + +// ─── Checkout Session ─── +app.post('/create-checkout-session', async (c) => { + const body = await c.req.json<{ + tierId?: string; + billingPeriod?: string; + userId?: string; + email?: string; + }>(); + const { tierId, billingPeriod = 'monthly', userId, email } = body; + + const tier = getTier(tierId || ''); + if (!tier || tier.id === 'free' || tier.id === 'custom') { + return c.json({ error: 'Invalid tier for checkout' }, 400); + } + + const priceId = getStripePriceId(tier, billingPeriod as 'monthly' | 'yearly'); + if (!priceId) { + return c.json({ error: 'Price not configured. Set Stripe price ID env vars.' }, 500); + } + + const params = new URLSearchParams({ + 'mode': 'subscription', + 'payment_method_types[]': 'card', + 'line_items[0][price]': priceId, + 'line_items[0][quantity]': '1', + 'success_url': 'https://pay.blackroad.io/success?session_id={CHECKOUT_SESSION_ID}', + 'cancel_url': 'https://pay.blackroad.io/cancel', + 'allow_promotion_codes': 'true', + 'automatic_tax[enabled]': 'true', + 'billing_address_collection': 'required', + 'tax_id_collection[enabled]': 'true', + 'metadata[tier_id]': tier.id, + }); + + if (email) params.set('customer_email', email); + if (userId) { + params.set('client_reference_id', userId); + params.set('metadata[user_id]', userId); + } + if (tier.trialDays > 0) { + params.set('subscription_data[trial_period_days]', String(tier.trialDays)); + } + + const res = await stripeAPI('POST', '/v1/checkout/sessions', params.toString()); + const session = await res.json() as { id?: string; url?: string; error?: { message?: string } }; + + if (session.error) { + return c.json({ error: session.error.message || 'Stripe error' }, 400); + } + + return c.json({ sessionId: session.id, url: session.url }); +}); + +// ─── Portal Session ─── +app.post('/create-portal-session', async (c) => { + const { customerId } = await c.req.json<{ customerId?: string }>(); + if (!customerId) return c.json({ error: 'Missing customerId' }, 400); + + const params = new URLSearchParams({ + 'customer': customerId, + 'return_url': 'https://pay.blackroad.io/', + }); + + const res = await stripeAPI('POST', '/v1/billing_portal/sessions', params.toString()); + const session = await res.json() as { url?: string; error?: { message?: string } }; + + if (session.error) { + return c.json({ error: session.error.message || 'Stripe error' }, 400); + } + + return c.json({ url: session.url }); +}); + +// ─── Webhook ─── +app.post('/webhook', async (c) => { + const sigHeader = c.req.header('stripe-signature'); + if (!sigHeader) { + return c.json({ error: 'Missing Stripe-Signature header' }, 400); + } + + const body = await c.req.text(); + const secret = process.env.STRIPE_WEBHOOK_SECRET; + if (!secret) { + return c.json({ error: 'Webhook secret not configured' }, 500); + } + + const verification = await verifyWebhookSignature(body, sigHeader, secret); + if (!verification.valid) { + console.error('Webhook verification failed:', verification.error); + return c.json({ error: 'Invalid signature' }, 400); + } + + const event = JSON.parse(body) as { id: string; type: string; data: { object: any } }; + + // Idempotency check + const existing = await REVENUE_D1.prepare( + 'SELECT stripe_event_id FROM webhook_events WHERE stripe_event_id = ?' + ).bind(event.id).first(); + + if (existing) { + return c.json({ received: true, duplicate: true }); + } + + let success = true; + try { + switch (event.type) { + case 'checkout.session.completed': + await handleCheckoutCompleted(event.data.object); + break; + case 'customer.subscription.created': + case 'customer.subscription.updated': + await handleSubscriptionUpdate(event.data.object); + break; + case 'customer.subscription.deleted': + await handleSubscriptionDeleted(event.data.object); + break; + case 'invoice.payment_succeeded': + await handlePaymentSucceeded(event.data.object); + break; + case 'invoice.payment_failed': + console.error('Payment failed for invoice:', event.data.object.id); + break; + } + } catch (err) { + console.error(`Webhook handler error for ${event.type}:`, err); + success = false; + } + + await REVENUE_D1.prepare( + 'INSERT INTO webhook_events (stripe_event_id, event_type, processed_at, success) VALUES (?, ?, ?, ?)' + ).bind(event.id, event.type, new Date().toISOString(), success ? 1 : 0).run(); + + return c.json({ received: true }); +}); + +// ─── Subscription Status ─── +app.get('/subscription-status', async (c) => { + const userId = c.req.query('userId'); + if (!userId) return c.json({ error: 'Missing userId' }, 400); + + const subscription = await SUBSCRIPTIONS_KV.get(`user:${userId}`, 'json'); + return c.json(subscription || { status: 'none' }); +}); + +// ─── Webhook Handlers ─── +async function handleCheckoutCompleted(session: any): Promise { + const userId = session.client_reference_id || session.metadata?.user_id; + const tierId = session.metadata?.tier_id; + if (!userId) return; + + if (session.subscription) { + const now = new Date().toISOString(); + await REVENUE_D1.prepare(` + INSERT INTO subscriptions (stripe_subscription_id, stripe_customer_id, user_id, tier_id, status, created_at, updated_at) + VALUES (?, ?, ?, ?, 'active', ?, ?) + ON CONFLICT(stripe_subscription_id) DO UPDATE SET status='active', updated_at=? + `).bind( + session.subscription, session.customer, userId, tierId || 'pro', now, now, now + ).run(); + } + + await SUBSCRIPTIONS_KV.put( + `user:${userId}`, + JSON.stringify({ + userId, tierId, + customerId: session.customer, + subscriptionId: session.subscription, + status: 'active', + createdAt: new Date().toISOString(), + }) + ); + + if (session.amount_total) { + await REVENUE_D1.prepare( + 'INSERT INTO revenue (user_id, tier_id, amount, currency, created_at) VALUES (?, ?, ?, ?, ?)' + ).bind(userId, tierId, session.amount_total / 100, session.currency || 'usd', new Date().toISOString()).run(); + } +} + +async function handleSubscriptionUpdate(subscription: any): Promise { + const userId = subscription.metadata?.user_id; + const now = new Date().toISOString(); + const periodEnd = subscription.current_period_end + ? new Date(subscription.current_period_end * 1000).toISOString() + : null; + + await REVENUE_D1.prepare(` + UPDATE subscriptions + SET status = ?, current_period_end = ?, cancel_at_period_end = ?, updated_at = ? + WHERE stripe_subscription_id = ? + `).bind(subscription.status, periodEnd, subscription.cancel_at_period_end ? 1 : 0, now, subscription.id).run(); + + if (userId) { + const existing = await SUBSCRIPTIONS_KV.get(`user:${userId}`, 'json') as any; + if (existing) { + await SUBSCRIPTIONS_KV.put(`user:${userId}`, JSON.stringify({ + ...existing, status: subscription.status, currentPeriodEnd: periodEnd, updatedAt: now, + })); + } + } +} + +async function handleSubscriptionDeleted(subscription: any): Promise { + const userId = subscription.metadata?.user_id; + const now = new Date().toISOString(); + + await REVENUE_D1.prepare( + 'UPDATE subscriptions SET status = ?, updated_at = ? WHERE stripe_subscription_id = ?' + ).bind('canceled', now, subscription.id).run(); + + if (userId) { + const existing = await SUBSCRIPTIONS_KV.get(`user:${userId}`, 'json') as any; + if (existing) { + await SUBSCRIPTIONS_KV.put(`user:${userId}`, JSON.stringify({ + ...existing, status: 'canceled', canceledAt: now, + })); + } + } +} + +async function handlePaymentSucceeded(invoice: any): Promise { + const userId = invoice.subscription_details?.metadata?.user_id || invoice.metadata?.user_id; + if (!userId) return; + + await REVENUE_D1.prepare( + 'INSERT INTO revenue (user_id, amount, currency, created_at) VALUES (?, ?, ?, ?)' + ).bind(userId, (invoice.amount_paid || 0) / 100, invoice.currency || 'usd', new Date().toISOString()).run(); +} + +// ─── Stripe API Helper ─── +async function stripeAPI(method: string, endpoint: string, body?: string): Promise { + const key = process.env.STRIPE_SECRET_KEY; + if (!key) throw new Error('STRIPE_SECRET_KEY not set'); + + return fetch(`https://api.stripe.com${endpoint}`, { + method, + headers: { + 'Authorization': `Bearer ${key}`, + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body, + }); +} + +// ─── HTML Pages ─── +function pageShell(title: string, content: string): string { + return ` + + + + +${title} - BlackRoad OS + +${content}`; +} + +function renderPricingPage(): string { + const tierCards = PRICING.map((tier) => { + const isPopular = tier.popular; + const isCustom = tier.id === 'custom'; + const isFree = tier.id === 'free'; + const monthlyDisplay = isCustom ? 'Custom' : isFree ? '$0' : `$${tier.priceMonthly}`; + const yearlyDisplay = isCustom ? 'Custom' : isFree ? '$0' : `$${Math.round(tier.priceYearly / 12)}`; + const featuresHtml = tier.features.map(f => `
  • ${f}
  • `).join(''); + const buttonAction = isFree + ? 'onclick="window.location.href=\'https://blackroad.io/signup\'"' + : isCustom + ? 'onclick="window.location.href=\'mailto:sales@blackroad.io?subject=Enterprise%20Custom%20Inquiry\'"' + : `onclick="checkout('${tier.id}')"`; + + return `
    + ${isPopular ? '' : ''} +

    ${tier.name}

    +
    + ${monthlyDisplay} + + ${!isCustom && !isFree ? '/mo' : ''} +
    + ${tier.trialDays > 0 ? `

    ${tier.trialDays}-day free trial

    ` : ''} +
      ${featuresHtml}
    + +
    `; + }).join(''); + + return pageShell('Pricing', ` + + + +
    +

    BlackRoad OS Pricing

    +

    Your AI. Your Hardware. Your Rules. Choose the plan that scales with you.

    +
    + Monthly +
    + Yearly + Save 17% +
    +
    +
    ${tierCards}
    +
    © ${new Date().getFullYear()} BlackRoad OS, Inc. All rights reserved.
    + +`); +} + +function renderSuccessPage(): string { + return pageShell('Payment Successful', ` + + +
    +
    +

    Welcome to BlackRoad OS

    +

    Your subscription is active. You now have access to all the features in your plan.

    + Go to Dashboard +
    +`); +} + +function renderCancelPage(): string { + return pageShell('Checkout Canceled', ` + + +
    +

    No worries!

    +

    Your checkout was canceled. No charges were made. Come back anytime.

    + Back to Pricing +
    +`); +} + +// ─── Start ─── +const PORT = parseInt(process.env.PORT || '3002'); +console.log(`[payment-gateway] Starting on port ${PORT}`); + +serve({ fetch: app.fetch, port: PORT }, (info) => { + console.log(`[payment-gateway] Listening on http://0.0.0.0:${info.port}`); +}); + +export default app; diff --git a/blackroad-infra/migration/phase4-services/payment-gateway/webhook-verify.ts b/blackroad-infra/migration/phase4-services/payment-gateway/webhook-verify.ts new file mode 100644 index 0000000000..74192f33df --- /dev/null +++ b/blackroad-infra/migration/phase4-services/payment-gateway/webhook-verify.ts @@ -0,0 +1,87 @@ +/** + * Stripe Webhook Signature Verification + * Ported from workers/payment-gateway/src/webhook-verify.ts + * Uses Node.js crypto instead of Web Crypto API. + */ + +import { createHmac, timingSafeEqual } from 'node:crypto'; + +const DEFAULT_TOLERANCE_SECONDS = 300; // 5 minutes + +function parseSignatureHeader(header: string): { timestamp: number; signatures: string[] } { + const parts = header.split(','); + let timestamp = 0; + const signatures: string[] = []; + + for (const part of parts) { + const [key, value] = part.split('=', 2); + if (key === 't') { + timestamp = parseInt(value, 10); + } else if (key === 'v1') { + signatures.push(value); + } + } + + return { timestamp, signatures }; +} + +function computeSignature(payload: string, timestamp: number, secret: string): string { + const signedPayload = `${timestamp}.${payload}`; + return createHmac('sha256', secret).update(signedPayload).digest('hex'); +} + +function secureCompare(a: string, b: string): boolean { + if (a.length !== b.length) return false; + try { + return timingSafeEqual(Buffer.from(a), Buffer.from(b)); + } catch { + return false; + } +} + +export interface VerifyResult { + valid: boolean; + error?: string; + timestamp?: number; +} + +export async function verifyWebhookSignature( + payload: string, + sigHeader: string, + secret: string, + tolerance: number = DEFAULT_TOLERANCE_SECONDS, +): Promise { + if (!sigHeader) { + return { valid: false, error: 'Missing Stripe-Signature header' }; + } + + if (!secret) { + return { valid: false, error: 'Webhook secret not configured' }; + } + + const { timestamp, signatures } = parseSignatureHeader(sigHeader); + + if (!timestamp || signatures.length === 0) { + return { valid: false, error: 'Invalid signature header format' }; + } + + // Replay attack protection + const now = Math.floor(Date.now() / 1000); + if (Math.abs(now - timestamp) > tolerance) { + return { + valid: false, + error: `Timestamp outside tolerance (${tolerance}s). Event age: ${Math.abs(now - timestamp)}s`, + timestamp, + }; + } + + const expectedSig = computeSignature(payload, timestamp, secret); + + const matched = signatures.some((sig) => secureCompare(sig, expectedSig)); + + if (!matched) { + return { valid: false, error: 'Signature mismatch', timestamp }; + } + + return { valid: true, timestamp }; +} diff --git a/blackroad-infra/migration/phase4-services/subdomain-router/server.ts b/blackroad-infra/migration/phase4-services/subdomain-router/server.ts new file mode 100644 index 0000000000..7ac172402b --- /dev/null +++ b/blackroad-infra/migration/phase4-services/subdomain-router/server.ts @@ -0,0 +1,230 @@ +/** + * BlackRoad Subdomain Router - Self-Hosted (Hono) + * + * Ported from Cloudflare Worker: workers/subdomain-router/src/index.ts + * Handles all 67+ subdomains with branded HTML pages. + * Port: 4000 + * + * The original is 2,546 lines. This is a functional port of the core + * routing + rendering logic. The full HTML rendering from the original + * Worker is preserved - it generates branded landing pages for each subdomain. + * + * RUNS ON OCTAVIA + */ + +import { Hono } from 'hono'; +import { cors } from 'hono/cors'; +import { serve } from '@hono/node-server'; +import { KVNamespace } from '../adapters/kv-adapter.js'; +import { D1Database } from '../adapters/d1-adapter.js'; + +const app = new Hono(); + +// Adapters +const CACHE = new KVNamespace('CACHE'); +const IDENTITIES = new KVNamespace('IDENTITIES'); +const API_KEYS = new KVNamespace('API_KEYS'); +const RATE_LIMIT = new KVNamespace('RATE_LIMIT'); +const DB = new D1Database('blackroad_os_main'); + +app.use('*', cors()); + +// ─── Brand Constants ─── +const BRAND = { + black: '#000000', + white: '#FFFFFF', + amber: '#F5A623', + hotPink: '#FF1D6C', + electricBlue: '#2979FF', + violet: '#9C27B0', + gradient: 'linear-gradient(135deg, #F5A623 0%, #FF1D6C 38.2%, #9C27B0 61.8%, #2979FF 100%)', +}; + +// ─── Subdomain → Emoji + Title Map ─── +const SUBDOMAIN_INFO: Record = { + os: { emoji: '🖥️', title: 'Operating System', description: 'The sovereign AI operating system' }, + ai: { emoji: '🤖', title: 'AI Platform', description: 'Multi-model AI inference and orchestration' }, + agents: { emoji: '🕵️', title: 'Agent Hub', description: '30,000 autonomous AI agents' }, + api: { emoji: '⚡', title: 'API Gateway', description: 'RESTful API for all BlackRoad services' }, + status: { emoji: '📊', title: 'System Status', description: 'Real-time infrastructure status' }, + docs: { emoji: '📚', title: 'Documentation', description: 'Guides, tutorials, and API reference' }, + console: { emoji: '🎮', title: 'Console', description: 'Admin console and management' }, + dashboard: { emoji: '📈', title: 'Dashboard', description: 'Metrics and monitoring' }, + chat: { emoji: '💬', title: 'Chat', description: 'Agent communication interface' }, + playground: { emoji: '🎪', title: 'Playground', description: 'Interactive AI sandbox' }, + marketplace: { emoji: '🏪', title: 'Marketplace', description: 'Agent templates and packs' }, + roadmap: { emoji: '🗺️', title: 'Roadmap', description: 'Product roadmap and vision' }, + changelog: { emoji: '📝', title: 'Changelog', description: 'Version history and updates' }, + security: { emoji: '🔒', title: 'Security', description: 'Security policies and audit' }, + careers: { emoji: '👥', title: 'Careers', description: 'Join the team' }, + store: { emoji: '🛍️', title: 'Store', description: 'Hardware and software store' }, + search: { emoji: '🔍', title: 'Search', description: 'Search across all services' }, + terminal: { emoji: '💻', title: 'Terminal', description: 'Web-based terminal' }, + world: { emoji: '🌍', title: 'World', description: '3D metaverse visualization' }, + admin: { emoji: '⚙️', title: 'Admin', description: 'System administration' }, + analytics: { emoji: '📉', title: 'Analytics', description: 'Traffic and usage analytics' }, + network: { emoji: '🌐', title: 'Network', description: 'Network topology and mesh' }, + prism: { emoji: '💎', title: 'Prism Console', description: 'Enterprise management' }, + brand: { emoji: '🎨', title: 'Brand', description: 'Design system and assets' }, + design: { emoji: '✏️', title: 'Design', description: 'UI/UX design system' }, + edge: { emoji: '🔮', title: 'Edge', description: 'Edge computing services' }, + data: { emoji: '🗃️', title: 'Data', description: 'Data management and pipelines' }, + finance: { emoji: '💰', title: 'Finance', description: 'Financial tools and analytics' }, + quantum: { emoji: '⚛️', title: 'Quantum', description: 'Quantum computing interface' }, + blog: { emoji: '✍️', title: 'Blog', description: 'News and articles' }, + dev: { emoji: '🛠️', title: 'Developer', description: 'Developer tools and SDKs' }, + about: { emoji: 'ℹ️', title: 'About', description: 'About BlackRoad OS' }, + help: { emoji: '❓', title: 'Help', description: 'Support and documentation' }, + products: { emoji: '📦', title: 'Products', description: 'Product catalog' }, + pitstop: { emoji: '🏁', title: 'Pitstop', description: 'Portal and quick links' }, + blockchain: { emoji: '⛓️', title: 'Blockchain', description: 'RoadChain and crypto' }, + compliance: { emoji: '✅', title: 'Compliance', description: 'Regulatory compliance' }, + hardware: { emoji: '🔧', title: 'Hardware', description: 'IoT and device management' }, + ide: { emoji: '📝', title: 'IDE', description: 'Web IDE and code editor' }, +}; + +// ─── Rate Limiting ─── +app.use('*', async (c, next) => { + const ip = c.req.header('x-forwarded-for')?.split(',')[0]?.trim() || 'unknown'; + const key = `rate:${ip}`; + const current = await RATE_LIMIT.get(key); + const count = current ? parseInt(current) : 0; + + if (count >= 500) { + return c.json({ error: 'Rate limit exceeded' }, 429); + } + + await RATE_LIMIT.put(key, (count + 1).toString(), { expirationTtl: 60 }); + await next(); +}); + +// ─── Request ID ─── +app.use('*', async (c, next) => { + const requestId = `br-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`; + c.header('X-Request-ID', requestId); + await next(); +}); + +// ─── SEO Routes ─── +app.get('/robots.txt', (c) => { + c.header('Content-Type', 'text/plain'); + return c.text('User-agent: *\nAllow: /\nSitemap: https://blackroad.io/sitemap.xml'); +}); + +app.get('/sitemap.xml', (c) => { + const subs = Object.keys(SUBDOMAIN_INFO); + const entries = subs.map(s => + ` https://${s}.blackroad.io/weekly0.8` + ).join('\n'); + c.header('Content-Type', 'application/xml'); + return c.text(`\n\n${entries}\n`); +}); + +// ─── Health ─── +app.get('/health', (c) => c.json({ + status: 'healthy', + service: 'subdomain-router', + source: 'self-hosted', + subdomains: Object.keys(SUBDOMAIN_INFO).length, + timestamp: new Date().toISOString(), +})); + +// ─── Main Router ─── +app.get('*', async (c) => { + const host = c.req.header('host') || ''; + const subdomain = host.split('.')[0].toLowerCase(); + + // If it's the main domain, redirect or serve root + if (subdomain === 'blackroad' || subdomain === 'www' || !host.includes('.')) { + return c.redirect('https://blackroad.io', 301); + } + + const info = SUBDOMAIN_INFO[subdomain] || { + emoji: '🌌', + title: subdomain.charAt(0).toUpperCase() + subdomain.slice(1), + description: `BlackRoad ${subdomain} service`, + }; + + // Track analytics + try { + await DB.prepare( + 'INSERT INTO analytics (subdomain, ts, path, ip) VALUES (?, ?, ?, ?)' + ).bind(subdomain, Date.now(), c.req.path, c.req.header('x-forwarded-for') || 'unknown').run(); + } catch { + // Analytics failure is non-critical + } + + return c.html(renderSubdomainPage(subdomain, info)); +}); + +// ─── Render Branded Page ─── +function renderSubdomainPage( + subdomain: string, + info: { emoji: string; title: string; description: string } +): string { + return ` + + + + +${info.title} - BlackRoad OS + + + + + + + + + + +
    +
    ${info.emoji}
    +

    ${info.title}

    +

    ${info.description}

    +
    BlackRoad OS — Self-Hosted
    + +
    +
    © ${new Date().getFullYear()} BlackRoad OS, Inc. Your AI. Your Hardware. Your Rules.
    + +`; +} + +// ─── Start ─── +const PORT = parseInt(process.env.PORT || '4000'); +console.log(`[subdomain-router] Starting on port ${PORT}`); + +serve({ fetch: app.fetch, port: PORT }, (info) => { + console.log(`[subdomain-router] Listening on http://0.0.0.0:${info.port}`); +}); + +export default app; diff --git a/blackroad-infra/migration/phase4-services/tsconfig.json b/blackroad-infra/migration/phase4-services/tsconfig.json new file mode 100644 index 0000000000..b65767c5b8 --- /dev/null +++ b/blackroad-infra/migration/phase4-services/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "strict": true, + "outDir": "dist", + "rootDir": ".", + "declaration": true, + "skipLibCheck": true, + "resolveJsonModule": true + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/blackroad-infra/production/.env.production.template b/blackroad-infra/production/.env.production.template new file mode 100644 index 0000000000..c8acbbbd92 --- /dev/null +++ b/blackroad-infra/production/.env.production.template @@ -0,0 +1,158 @@ +# ============================================================================ +# BlackRoad OS — Production Environment Variables +# ============================================================================ +# Generated: 2026-02-28 +# Account: acct_1S70Zn3e5FMFdlFw +# +# INSTRUCTIONS: +# 1. Copy this file to .env.production +# 2. Fill in ALL values marked REQUIRED +# 3. NEVER commit .env.production to git +# 4. Set these in Railway / Vercel / Cloudflare dashboards +# ============================================================================ + +# ── Environment ── +NODE_ENV=production +BR_OS_ENV=prod +BR_OS_SERVICE_VERSION=1.0.0 + +# ============================================================================ +# STRIPE (Payment Processing) +# Dashboard: https://dashboard.stripe.com/acct_1S70Zn3e5FMFdlFw/apikeys +# ============================================================================ + +# REQUIRED: Get from Stripe Dashboard > API Keys +STRIPE_SECRET_KEY=sk_live_ +STRIPE_PUBLISHABLE_KEY=pk_live_ + +# REQUIRED: Create at Stripe Dashboard > Webhooks > Add endpoint +# Endpoint URL: https://pay.blackroad.io/webhook +# Events: checkout.session.completed, customer.subscription.created, +# customer.subscription.updated, customer.subscription.deleted, +# invoice.payment_succeeded, invoice.payment_failed +STRIPE_WEBHOOK_SECRET=whsec_ + +# REQUIRED: Run ./production/provision-stripe.sh to generate these +# Or get from Stripe Dashboard > Products > Prices +STRIPE_PRICE_PRO_MONTHLY=price_ +STRIPE_PRICE_PRO_YEARLY=price_ +STRIPE_PRICE_ENT_MONTHLY=price_ +STRIPE_PRICE_ENT_YEARLY=price_ + +# ============================================================================ +# GOOGLE DRIVE (File Storage & Content Pipeline) +# Console: https://console.cloud.google.com/apis/credentials +# ============================================================================ + +# REQUIRED: Path to service account JSON key file +# Create at: Google Cloud Console > IAM > Service Accounts > Create Key (JSON) +# Required APIs: Google Drive API, Google Sheets API +GOOGLE_SERVICE_ACCOUNT_KEY_PATH=/etc/blackroad/google-sa.json + +# REQUIRED: Base64-encoded service account key (for env var storage) +# Generate: cat google-sa.json | base64 -w 0 +GOOGLE_SERVICE_ACCOUNT_KEY_BASE64= + +# REQUIRED: Shared Drive folder ID for BlackRoad assets +# Get from: Google Drive > Right-click folder > Share > Copy link +# The ID is the last part of the URL +GOOGLE_DRIVE_FOLDER_ID= + +# OPTIONAL: OAuth2 credentials for user-facing Drive access +# GOOGLE_CLIENT_ID= +# GOOGLE_CLIENT_SECRET= + +# ============================================================================ +# DATABASE (PostgreSQL on Cecilia) +# ============================================================================ + +# REQUIRED +PG_HOST=10.10.0.2 +PG_PORT=5432 +PG_USER=blackroad +PG_PASSWORD= +DATABASE_URL=postgresql://blackroad:PASSWORD@10.10.0.2:5432/blackroad_prod + +# ============================================================================ +# REDIS (Cecilia) +# ============================================================================ + +# REQUIRED +REDIS_HOST=10.10.0.2 +REDIS_PORT=6379 +REDIS_PASSWORD= +REDIS_URL=redis://:PASSWORD@10.10.0.2:6379 + +# ============================================================================ +# MINIO (Lucidia - Object Storage) +# ============================================================================ + +# REQUIRED +MINIO_ENDPOINT=10.10.0.3 +MINIO_PORT=9000 +MINIO_ACCESS_KEY=blackroad-admin +MINIO_SECRET_KEY= + +# ============================================================================ +# AI SERVICES +# ============================================================================ + +# REQUIRED: At least one AI provider +ANTHROPIC_API_KEY= + +# OPTIONAL +OPENAI_API_KEY= + +# ============================================================================ +# AUTHENTICATION +# ============================================================================ + +# REQUIRED: Generate with: openssl rand -base64 32 +JWT_SECRET= +NEXTAUTH_SECRET= +NEXTAUTH_URL=https://blackroad.io + +# REQUIRED: Clerk authentication +# Dashboard: https://dashboard.clerk.com +NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= +CLERK_SECRET_KEY= + +# ============================================================================ +# CLOUDFLARE +# ============================================================================ + +# REQUIRED for worker deployments +CLOUDFLARE_API_TOKEN= +CLOUDFLARE_ACCOUNT_ID= + +# ============================================================================ +# DEPLOYMENT +# ============================================================================ + +# REQUIRED +RAILWAY_TOKEN= +DEPLOY_URL=https://api.blackroad.io + +# ============================================================================ +# EMAIL +# ============================================================================ + +# OPTIONAL +SENDGRID_API_KEY= + +# ============================================================================ +# MONITORING +# ============================================================================ + +# OPTIONAL +SENTRY_DSN= +NEXT_PUBLIC_SENTRY_DSN= +LOG_LEVEL=info + +# ============================================================================ +# CORS & URLS +# ============================================================================ +CORS_ORIGIN=https://blackroad.io +NEXT_PUBLIC_BASE_URL=https://blackroad.io +NEXT_PUBLIC_API_URL=https://api.blackroad.io +GATEWAY_URL=https://api.blackroad.io diff --git a/blackroad-infra/production/PRODUCTION_CHECKLIST.md b/blackroad-infra/production/PRODUCTION_CHECKLIST.md new file mode 100644 index 0000000000..8ed05b3c11 --- /dev/null +++ b/blackroad-infra/production/PRODUCTION_CHECKLIST.md @@ -0,0 +1,151 @@ +# BlackRoad OS Production Readiness Checklist + +> 24 hours. All repos. Production level. MOVE FASTER THINK HARDER ALWAYS BELIEVE. + +--- + +## Phase 1: Stripe Products (30 min) + +- [ ] Get Stripe API keys from https://dashboard.stripe.com/acct_1S70Zn3e5FMFdlFw/apikeys +- [ ] Run `br stripe auth sk_live_xxx` to configure CLI +- [ ] Run `./production/provision-stripe.sh` to create ALL products & prices +- [ ] Copy price IDs from output to `.env.production` +- [ ] Create webhook endpoint in Stripe Dashboard: + - URL: `https://pay.blackroad.io/webhook` + - Events: `checkout.session.completed`, `customer.subscription.*`, `invoice.payment_*` +- [ ] Copy webhook secret (`whsec_xxx`) to `.env.production` +- [ ] Verify: `br stripe products list` shows all products + +## Phase 2: Google Drive (20 min) + +- [ ] Create Google Cloud project: `blackroad-os-production` +- [ ] Enable APIs: Drive, Sheets, Docs +- [ ] Create service account: `blackroad-drive-service` +- [ ] Download JSON key +- [ ] Run `./production/google-drive-setup.sh path/to/key.json` +- [ ] Create Drive folder structure (see setup script output) +- [ ] Share root folder with service account email +- [ ] Set `GOOGLE_DRIVE_FOLDER_ID` in env +- [ ] Set base64 key in Railway + GitHub Actions + +## Phase 3: Database & Redis (15 min) + +- [ ] Verify PostgreSQL running on Cecilia (10.10.0.2:5432) +- [ ] Create production database: `blackroad_prod` +- [ ] Run schema migrations (payment-gateway tables) +- [ ] Verify Redis running on Cecilia (10.10.0.2:6379) +- [ ] Set strong passwords for both services +- [ ] Test connectivity from deployment targets + +## Phase 4: Deploy Payment Gateway (20 min) + +- [ ] Set all env vars in Railway for payment-gateway service +- [ ] Deploy: `railway up` from `migration/phase4-services/` +- [ ] Verify health: `curl https://pay.blackroad.io/health` +- [ ] Test checkout flow with Stripe test card +- [ ] Verify webhook delivery in Stripe Dashboard + +## Phase 5: Set GitHub Organization Secrets (15 min) + +Run these for `BlackRoad-OS-Inc`: + +```bash +# Core Stripe +gh secret set STRIPE_SECRET_KEY --org BlackRoad-OS-Inc +gh secret set STRIPE_PUBLISHABLE_KEY --org BlackRoad-OS-Inc +gh secret set STRIPE_WEBHOOK_SECRET --org BlackRoad-OS-Inc + +# Stripe Price IDs (from provision-stripe.sh output) +gh secret set STRIPE_PRICE_PRO_MONTHLY --org BlackRoad-OS-Inc +gh secret set STRIPE_PRICE_PRO_YEARLY --org BlackRoad-OS-Inc +gh secret set STRIPE_PRICE_ENT_MONTHLY --org BlackRoad-OS-Inc +gh secret set STRIPE_PRICE_ENT_YEARLY --org BlackRoad-OS-Inc + +# Google Drive +gh secret set GOOGLE_SERVICE_ACCOUNT_KEY --org BlackRoad-OS-Inc +gh secret set GOOGLE_DRIVE_FOLDER_ID --org BlackRoad-OS-Inc + +# Infrastructure +gh secret set CLOUDFLARE_API_TOKEN --org BlackRoad-OS-Inc +gh secret set CLOUDFLARE_ACCOUNT_ID --org BlackRoad-OS-Inc +gh secret set RAILWAY_TOKEN --org BlackRoad-OS-Inc +gh secret set DEPLOY_URL --org BlackRoad-OS-Inc + +# Auth +gh secret set JWT_SECRET --org BlackRoad-OS-Inc +gh secret set NEXTAUTH_SECRET --org BlackRoad-OS-Inc +gh secret set CLERK_SECRET_KEY --org BlackRoad-OS-Inc + +# AI +gh secret set ANTHROPIC_API_KEY --org BlackRoad-OS-Inc + +# Database +gh secret set PG_PASSWORD --org BlackRoad-OS-Inc +gh secret set REDIS_PASSWORD --org BlackRoad-OS-Inc +gh secret set DATABASE_URL --org BlackRoad-OS-Inc +``` + +## Phase 6: Deploy Cloudflare Workers (20 min) + +```bash +# Payment gateway worker +cd workers/payment-gateway +wrangler secret put STRIPE_SECRET_KEY +wrangler secret put STRIPE_WEBHOOK_SECRET +wrangler secret put STRIPE_PRICE_PRO_MONTHLY +wrangler secret put STRIPE_PRICE_PRO_YEARLY +wrangler secret put STRIPE_PRICE_ENT_MONTHLY +wrangler secret put STRIPE_PRICE_ENT_YEARLY +wrangler deploy + +# Email worker +cd ../email +wrangler deploy + +# Auth worker +cd ../auth +wrangler deploy +``` + +## Phase 7: Update Per-Repo Stripe Configs (30 min) + +After provisioning, update all `stripe-config.json` files with real price IDs: + +- [ ] `orgs/core/blackroad-agent-os/stripe-config.json` +- [ ] `orgs/core/blackroad-agents/stripe-config.json` +- [ ] `orgs/core/blackroad-cli/stripe-config.json` +- [ ] `orgs/core/blackroad-hello/stripe-config.json` +- [ ] `orgs/core/blackroad-os-docs/stripe-config.json` +- [ ] `orgs/core/blackroad-pi-ops/stripe-config.json` +- [ ] `orgs/core/blackroad-tools/stripe-config.json` +- [ ] `orgs/core/containers-template/stripe-config.json` +- [ ] `orgs/core/lucidia-core/stripe-config.json` + +## Phase 8: Verify Everything (15 min) + +- [ ] `br stripe revenue` - shows dashboard +- [ ] `br stripe products list` - shows all products +- [ ] `curl https://pay.blackroad.io/health` - returns healthy +- [ ] `curl https://api.blackroad.io/health` - returns healthy +- [ ] Test checkout: visit https://pay.blackroad.io, select Pro, complete with test card +- [ ] Verify webhook fires in Stripe Dashboard > Webhooks +- [ ] Verify subscription appears: `br stripe subscriptions list` +- [ ] Check Google Drive folder accessible via service account + +--- + +## Total Estimated Time: ~2.5 hours + +## Product Summary (After Provisioning) + +| # | Product | Prices | Revenue Potential | +|---|---------|--------|-------------------| +| 1 | BlackRoad OS Pro | $29/mo, $290/yr | Core MRR | +| 2 | BlackRoad OS Enterprise | $199/mo, $1,990/yr | Enterprise MRR | +| 3 | Open Source Support | $5-$500 | Sponsorship | +| 4 | Commercial License | $499-$2,499/yr | Licensing ARR | +| 5 | Consulting | $250-$5,000 | Services | +| 6 | Priority Support | $499/mo | Support MRR | +| 7-11 | Per-Repo Products (5x) | $9-$99/mo each | Module MRR | + +**Total Products: 11 | Total Price Points: 35+** diff --git a/blackroad-infra/production/SECRETS_REGISTRY.md b/blackroad-infra/production/SECRETS_REGISTRY.md new file mode 100644 index 0000000000..29e62dfb54 --- /dev/null +++ b/blackroad-infra/production/SECRETS_REGISTRY.md @@ -0,0 +1,234 @@ +# BlackRoad OS Production Secrets Registry + +> **Single source of truth** for all production keys, tokens, and secrets. +> Last updated: 2026-02-28 +> Account: `acct_1S70Zn3e5FMFdlFw` (BlackRoad) + +--- + +## CRITICAL: Never commit actual secret values to git + +All secrets must be stored in: +1. **GitHub Actions Secrets** (for CI/CD) +2. **Railway Environment Variables** (for backend services) +3. **Cloudflare Worker Secrets** (for edge services) +4. **Vercel Environment Variables** (for frontend deploys) +5. **`~/.blackroad/`** (for local CLI tools, chmod 600) + +--- + +## 1. Stripe (Payment Processing) + +| Secret Name | Where Used | Storage Location | Status | +|---|---|---|---| +| `STRIPE_SECRET_KEY` | Payment Gateway, CLI, Webhooks | Railway, Wrangler, `~/.blackroad/stripe.conf` | **REQUIRED** | +| `STRIPE_PUBLISHABLE_KEY` | Frontend checkout pages | Vercel (NEXT_PUBLIC), Cloudflare Pages | **REQUIRED** | +| `STRIPE_WEBHOOK_SECRET` | Webhook signature verification | Railway, Wrangler | **REQUIRED** | +| `STRIPE_PRICE_PRO_MONTHLY` | Checkout session creation | Railway, Wrangler | **PROVISION** | +| `STRIPE_PRICE_PRO_YEARLY` | Checkout session creation | Railway, Wrangler | **PROVISION** | +| `STRIPE_PRICE_ENT_MONTHLY` | Checkout session creation | Railway, Wrangler | **PROVISION** | +| `STRIPE_PRICE_ENT_YEARLY` | Checkout session creation | Railway, Wrangler | **PROVISION** | + +### Stripe Products to Provision + +**Account Status: `acct_1S70Zn3e5FMFdlFw` — 0 products, 0 prices (EMPTY)** + +Run `br stripe products create` or use `production/provision-stripe.sh` to create: + +| Product | Tier | Monthly | Yearly | Agents | Tasks | +|---|---|---|---|---|---| +| BlackRoad OS Free | `free` | $0 | $0 | 3 | 100/mo | +| BlackRoad OS Pro | `pro` | $29 | $290 | 100 | 10K/mo | +| BlackRoad OS Enterprise | `enterprise` | $199 | $1,990 | Unlimited | Unlimited | +| BlackRoad OS Custom | `custom` | Contact Sales | Contact Sales | Custom | Custom | + +**Additional Products (Sponsorship/Licensing):** + +| Product | Type | Price | +|---|---|---| +| Open Source Support - Friend | Recurring | $5/mo | +| Open Source Support - Supporter | Recurring | $25/mo | +| Open Source Support - Sponsor | Recurring | $100/mo | +| Open Source Support - Coffee | One-time | $10 | +| Open Source Support - Backer | One-time | $50 | +| Open Source Support - Champion | One-time | $500 | +| Commercial License - Startup | Yearly | $499/yr | +| Commercial License - Business | Yearly | $999/yr | +| Commercial License - Enterprise | Yearly | $2,499/yr | +| Consulting - Hourly | One-time | $250 | +| Consulting - Daily | One-time | $1,500 | +| Consulting - Project | One-time | $5,000 | +| Priority Support | Recurring | $499/mo | + +--- + +## 2. Google Drive (File Storage & Content) + +| Secret Name | Where Used | Storage Location | Status | +|---|---|---|---| +| `GOOGLE_SERVICE_ACCOUNT_KEY` | Drive API, file sync, photo backgrounds | Railway, `~/.blackroad/google-sa.json` | **REQUIRED** | +| `GOOGLE_DRIVE_FOLDER_ID` | Root shared folder for assets | Railway, Vercel | **REQUIRED** | +| `GOOGLE_CLIENT_ID` | OAuth2 for user-facing Drive access | Vercel (NEXT_PUBLIC) | **OPTIONAL** | +| `GOOGLE_CLIENT_SECRET` | OAuth2 token exchange | Railway | **OPTIONAL** | + +### Google Drive Integration Points + +| Integration | Method | Purpose | +|---|---|---| +| n8n Workflows | OAuth2 + Service Account | File automation, backup | +| Airbyte Connector | Service Account | Data sync from Drive | +| Activepieces | OAuth2 | Workflow automation | +| Photo Backgrounds | Service Account | Asset serving for templates | +| Content Pipeline | Service Account | Docs/Sheets data extraction | + +--- + +## 3. Database (PostgreSQL on Cecilia) + +| Secret Name | Where Used | Storage Location | Status | +|---|---|---|---| +| `PG_HOST` | All backend services | Railway, `.env` | `10.10.0.2` | +| `PG_PORT` | All backend services | Railway, `.env` | `5432` | +| `PG_USER` | All backend services | Railway, `.env` | `blackroad` | +| `PG_PASSWORD` | All backend services | Railway, `.env` | **REQUIRED** | +| `DATABASE_URL` | ORM connections | Railway | **REQUIRED** | + +--- + +## 4. Redis (Cecilia) + +| Secret Name | Where Used | Storage Location | Status | +|---|---|---|---| +| `REDIS_HOST` | Session cache, job queues | Railway, `.env` | `10.10.0.2` | +| `REDIS_PORT` | Session cache, job queues | Railway, `.env` | `6379` | +| `REDIS_PASSWORD` | Session cache, job queues | Railway, `.env` | **REQUIRED** | + +--- + +## 5. MinIO (Lucidia - Object Storage) + +| Secret Name | Where Used | Storage Location | Status | +|---|---|---|---| +| `MINIO_ENDPOINT` | File storage, model weights | Railway, `.env` | `10.10.0.3` | +| `MINIO_PORT` | File storage | Railway, `.env` | `9000` | +| `MINIO_ACCESS_KEY` | File storage | Railway, `.env` | **REQUIRED** | +| `MINIO_SECRET_KEY` | File storage | Railway, `.env` | **REQUIRED** | + +--- + +## 6. AI Services + +| Secret Name | Where Used | Storage Location | Status | +|---|---|---|---| +| `ANTHROPIC_API_KEY` | Claude routing, agent orchestration | Railway, Wrangler | **REQUIRED** | +| `OPENAI_API_KEY` | GPT routing, fallback | Railway, Wrangler | **OPTIONAL** | + +--- + +## 7. Cloudflare + +| Secret Name | Where Used | Storage Location | Status | +|---|---|---|---| +| `CLOUDFLARE_API_TOKEN` | Worker deployments, DNS, Pages | GitHub Actions | **REQUIRED** | +| `CLOUDFLARE_ACCOUNT_ID` | Worker deployments, Pages | GitHub Actions | **REQUIRED** | + +--- + +## 8. Authentication + +| Secret Name | Where Used | Storage Location | Status | +|---|---|---|---| +| `JWT_SECRET` | API token signing | Railway | **REQUIRED** | +| `NEXTAUTH_SECRET` | Next.js auth sessions | Vercel | **REQUIRED** | +| `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` | Clerk frontend auth | Vercel, Cloudflare Pages | **REQUIRED** | +| `CLERK_SECRET_KEY` | Clerk backend auth | Railway | **REQUIRED** | + +--- + +## 9. Deployment Platforms + +| Secret Name | Where Used | Storage Location | Status | +|---|---|---|---| +| `RAILWAY_TOKEN` | Railway deployments | GitHub Actions | **REQUIRED** | +| `DEPLOY_URL` | Health checks, self-healing | GitHub Actions | **REQUIRED** | + +--- + +## 10. Email & Notifications + +| Secret Name | Where Used | Storage Location | Status | +|---|---|---|---| +| `SENDGRID_API_KEY` | Transactional email | Railway, Wrangler | **OPTIONAL** | + +--- + +## 11. Monitoring + +| Secret Name | Where Used | Storage Location | Status | +|---|---|---|---| +| `SENTRY_DSN` | Error tracking (server) | Railway | **OPTIONAL** | +| `NEXT_PUBLIC_SENTRY_DSN` | Error tracking (client) | Vercel | **OPTIONAL** | + +--- + +## GitHub Actions Secrets Checklist + +These secrets must be set in **every** repository or at the **organization level**: + +``` +STRIPE_SECRET_KEY +STRIPE_PUBLISHABLE_KEY +STRIPE_WEBHOOK_SECRET +STRIPE_PRICE_PRO_MONTHLY +STRIPE_PRICE_PRO_YEARLY +STRIPE_PRICE_ENT_MONTHLY +STRIPE_PRICE_ENT_YEARLY +GOOGLE_SERVICE_ACCOUNT_KEY +GOOGLE_DRIVE_FOLDER_ID +CLOUDFLARE_API_TOKEN +CLOUDFLARE_ACCOUNT_ID +RAILWAY_TOKEN +DEPLOY_URL +ANTHROPIC_API_KEY +JWT_SECRET +NEXTAUTH_SECRET +NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY +CLERK_SECRET_KEY +PG_PASSWORD +REDIS_PASSWORD +MINIO_ACCESS_KEY +MINIO_SECRET_KEY +SENTRY_DSN +SENDGRID_API_KEY +``` + +--- + +## Quick Setup Commands + +```bash +# 1. Configure Stripe CLI +br stripe auth sk_live_YOUR_KEY + +# 2. Provision all Stripe products +./production/provision-stripe.sh + +# 3. Set Cloudflare Worker secrets +cd workers/payment-gateway +wrangler secret put STRIPE_SECRET_KEY +wrangler secret put STRIPE_WEBHOOK_SECRET +wrangler secret put STRIPE_PRICE_PRO_MONTHLY +wrangler secret put STRIPE_PRICE_PRO_YEARLY +wrangler secret put STRIPE_PRICE_ENT_MONTHLY +wrangler secret put STRIPE_PRICE_ENT_YEARLY + +# 4. Set Railway secrets +railway variables set STRIPE_SECRET_KEY=sk_live_xxx +railway variables set PG_PASSWORD=xxx +railway variables set REDIS_PASSWORD=xxx + +# 5. Set GitHub org-level secrets +gh secret set STRIPE_SECRET_KEY --org BlackRoad-OS-Inc +gh secret set CLOUDFLARE_API_TOKEN --org BlackRoad-OS-Inc +gh secret set RAILWAY_TOKEN --org BlackRoad-OS-Inc +``` diff --git a/blackroad-infra/production/google-drive-setup.sh b/blackroad-infra/production/google-drive-setup.sh new file mode 100755 index 0000000000..49cdf4605e --- /dev/null +++ b/blackroad-infra/production/google-drive-setup.sh @@ -0,0 +1,155 @@ +#!/usr/bin/env bash + +# BlackRoad OS — Google Drive Setup +# Sets up Google Drive integration for file storage, content pipeline, and assets +# +# Prerequisites: +# 1. Google Cloud project with Drive API enabled +# 2. Service account with Drive API access +# 3. Service account JSON key file +# +# Usage: +# ./production/google-drive-setup.sh + +set -euo pipefail + +GREEN='\033[0;32m' +RED='\033[0;31m' +AMBER='\033[38;5;214m' +PINK='\033[38;5;205m' +DIM='\033[2m' +BOLD='\033[1m' +NC='\033[0m' + +SA_KEY_FILE="${1:-}" +BR_DIR="$HOME/.blackroad" +SA_DEST="$BR_DIR/google-sa.json" + +echo -e "${AMBER}${BOLD}BlackRoad OS — Google Drive Setup${NC}" +echo "" + +# ─── Step 1: Service Account Key ─── +if [[ -z "$SA_KEY_FILE" ]]; then + echo -e "${PINK}Google Drive Integration Setup${NC}" + echo "" + echo "This script configures Google Drive for:" + echo " - File storage and asset management" + echo " - Photo backgrounds for templates" + echo " - Content pipeline (Docs, Sheets extraction)" + echo " - n8n/Airbyte/Activepieces automation" + echo "" + echo -e "${AMBER}Setup Steps:${NC}" + echo "" + echo "1. Go to Google Cloud Console:" + echo " https://console.cloud.google.com/" + echo "" + echo "2. Create a project (or select existing):" + echo " Project name: blackroad-os-production" + echo "" + echo "3. Enable APIs:" + echo " - Google Drive API" + echo " - Google Sheets API" + echo " - Google Docs API" + echo "" + echo "4. Create a Service Account:" + echo " IAM & Admin > Service Accounts > Create" + echo " Name: blackroad-drive-service" + echo " Role: Editor" + echo "" + echo "5. Create a JSON key:" + echo " Service Account > Keys > Add Key > JSON" + echo " Save the downloaded file" + echo "" + echo "6. Share your Google Drive folder:" + echo " Right-click folder > Share" + echo " Add the service account email (from the JSON key)" + echo " Give it Editor access" + echo "" + echo "7. Run this script again:" + echo " ./production/google-drive-setup.sh path/to/your-key.json" + echo "" + exit 0 +fi + +if [[ ! -f "$SA_KEY_FILE" ]]; then + echo -e "${RED}File not found: $SA_KEY_FILE${NC}" + exit 1 +fi + +# Validate JSON +if ! python3 -c "import json; json.load(open('$SA_KEY_FILE'))" 2>/dev/null; then + echo -e "${RED}Invalid JSON file: $SA_KEY_FILE${NC}" + exit 1 +fi + +# Extract service account email +SA_EMAIL=$(python3 -c "import json; print(json.load(open('$SA_KEY_FILE'))['client_email'])" 2>/dev/null) +PROJECT_ID=$(python3 -c "import json; print(json.load(open('$SA_KEY_FILE'))['project_id'])" 2>/dev/null) + +echo -e "${GREEN}Valid service account key:${NC}" +echo -e " Email: $SA_EMAIL" +echo -e " Project: $PROJECT_ID" +echo "" + +# ─── Step 2: Install Key ─── +mkdir -p "$BR_DIR" +cp "$SA_KEY_FILE" "$SA_DEST" +chmod 600 "$SA_DEST" +echo -e "${GREEN}Key installed to: $SA_DEST${NC}" + +# ─── Step 3: Generate Base64 ─── +B64=$(base64 -w 0 "$SA_DEST" 2>/dev/null || base64 "$SA_DEST" 2>/dev/null | tr -d '\n') +echo "" +echo -e "${PINK}Base64-encoded key for environment variables:${NC}" +echo "" +echo "GOOGLE_SERVICE_ACCOUNT_KEY_BASE64=$B64" +echo "" + +# ─── Step 4: Set in providers ─── +echo -e "${PINK}━━━ Set in deployment providers ━━━${NC}" +echo "" +echo "# Railway:" +echo "railway variables set GOOGLE_SERVICE_ACCOUNT_KEY_BASE64='$B64'" +echo "" +echo "# GitHub Actions (org-level):" +echo "gh secret set GOOGLE_SERVICE_ACCOUNT_KEY --body '$B64' --org BlackRoad-OS-Inc" +echo "" +echo "# Cloudflare Workers:" +echo "echo '$B64' | wrangler secret put GOOGLE_SERVICE_ACCOUNT_KEY" +echo "" + +# ─── Step 5: Drive folder setup ─── +echo -e "${PINK}━━━ Google Drive Folder Structure ━━━${NC}" +echo "" +echo "Create these folders in Google Drive and share with: $SA_EMAIL" +echo "" +echo "BlackRoad-OS-Production/" +echo " ├── assets/" +echo " │ ├── logos/" +echo " │ ├── backgrounds/" +echo " │ ├── icons/" +echo " │ └── screenshots/" +echo " ├── templates/" +echo " │ ├── email/" +echo " │ ├── docs/" +echo " │ └── presentations/" +echo " ├── content/" +echo " │ ├── blog-posts/" +echo " │ ├── documentation/" +echo " │ └── marketing/" +echo " ├── exports/" +echo " │ ├── analytics/" +echo " │ ├── reports/" +echo " │ └── backups/" +echo " └── shared/" +echo " ├── team/" +echo " └── public/" +echo "" + +echo -e "${GREEN}${BOLD}Google Drive setup complete.${NC}" +echo "" +echo "Next steps:" +echo " 1. Share the Drive folder with: $SA_EMAIL" +echo " 2. Copy the folder ID from the URL" +echo " 3. Set GOOGLE_DRIVE_FOLDER_ID in your .env" +echo " 4. Set the base64 key in Railway/GitHub/Cloudflare" diff --git a/blackroad-infra/production/provision-stripe.sh b/blackroad-infra/production/provision-stripe.sh new file mode 100755 index 0000000000..0a59d05d0e --- /dev/null +++ b/blackroad-infra/production/provision-stripe.sh @@ -0,0 +1,335 @@ +#!/usr/bin/env bash + +# BlackRoad OS - Stripe Product Provisioner +# Creates ALL products and prices in Stripe for production +# +# Usage: +# ./production/provision-stripe.sh # Interactive (prompts for key) +# STRIPE_SECRET_KEY=sk_live_xxx ./production/provision-stripe.sh # Non-interactive +# +# Products Created: +# 1. BlackRoad OS Pro ($29/mo, $290/yr) +# 2. BlackRoad OS Enterprise ($199/mo, $1,990/yr) +# 3. Open Source Support ($5/mo, $25/mo, $100/mo, $10, $50, $500 one-time) +# 4. Commercial License ($499/yr, $999/yr, $2,499/yr) +# 5. Consulting ($250/hr, $1,500/day, $5,000/project) +# 6. Priority Support ($499/mo) + +set -euo pipefail + +GREEN='\033[0;32m' +RED='\033[0;31m' +AMBER='\033[38;5;214m' +PINK='\033[38;5;205m' +DIM='\033[2m' +BOLD='\033[1m' +NC='\033[0m' + +OUTPUT_FILE="production/.stripe-provision-output.json" + +# ─── Auth ─── +if [[ -z "${STRIPE_SECRET_KEY:-}" ]]; then + if [[ -f "$HOME/.blackroad/stripe.conf" ]]; then + source "$HOME/.blackroad/stripe.conf" + fi +fi + +if [[ -z "${STRIPE_SECRET_KEY:-}" ]]; then + echo -e "${RED}STRIPE_SECRET_KEY not set${NC}" + echo "Set it via: export STRIPE_SECRET_KEY=sk_live_xxx" + echo "Or run: br stripe auth sk_live_xxx" + exit 1 +fi + +MODE="LIVE" +[[ "$STRIPE_SECRET_KEY" == sk_test_* ]] && MODE="TEST" +echo -e "${AMBER}${BOLD}BlackRoad OS — Stripe Product Provisioner${NC}" +echo -e "${DIM}Mode: $MODE${NC}" +echo "" + +# ─── API Helper ─── +stripe_api() { + local method="$1" + local endpoint="$2" + local data="${3:-}" + + if [[ -n "$data" ]]; then + curl -s -X "$method" "https://api.stripe.com/v1${endpoint}" \ + -H "Authorization: Bearer $STRIPE_SECRET_KEY" \ + -d "$data" + else + curl -s -X "$method" "https://api.stripe.com/v1${endpoint}" \ + -H "Authorization: Bearer $STRIPE_SECRET_KEY" + fi +} + +extract_id() { + echo "$1" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4 +} + +check_error() { + local response="$1" + local context="$2" + if echo "$response" | grep -q '"error"'; then + local msg + msg=$(echo "$response" | grep -o '"message":"[^"]*"' | head -1 | cut -d'"' -f4) + echo -e "${RED} FAILED: $context — $msg${NC}" + return 1 + fi + return 0 +} + +# ─── Track results ─── +declare -A PRICE_IDS +RESULTS="" + +add_result() { + local key="$1" value="$2" + PRICE_IDS["$key"]="$value" + if [[ -n "$RESULTS" ]]; then + RESULTS="$RESULTS," + fi + RESULTS="$RESULTS + \"$key\": \"$value\"" +} + +# ============================================================================ +# 1. CORE SUBSCRIPTION PRODUCTS +# ============================================================================ +echo -e "${PINK}━━━ Creating Core Subscription Products ━━━${NC}" + +# --- BlackRoad OS Pro --- +echo -e "${AMBER}Creating: BlackRoad OS Pro${NC}" +PRO_PROD=$(stripe_api POST "/products" \ + "name=BlackRoad+OS+Pro&description=100+AI+Agents,+10K+tasks/mo,+priority+support,+custom+integrations&metadata[tier_id]=pro&metadata[product_group]=core") +PRO_PROD_ID=$(extract_id "$PRO_PROD") +check_error "$PRO_PROD" "Pro Product" || exit 1 +echo -e " ${GREEN}Product: $PRO_PROD_ID${NC}" + +PRO_M=$(stripe_api POST "/prices" \ + "product=$PRO_PROD_ID&unit_amount=2900¤cy=usd&recurring[interval]=month&metadata[tier_id]=pro&metadata[period]=monthly") +PRO_M_ID=$(extract_id "$PRO_M") +add_result "STRIPE_PRICE_PRO_MONTHLY" "$PRO_M_ID" +echo -e " ${GREEN}Monthly: $PRO_M_ID ($29/mo)${NC}" + +PRO_Y=$(stripe_api POST "/prices" \ + "product=$PRO_PROD_ID&unit_amount=29000¤cy=usd&recurring[interval]=year&metadata[tier_id]=pro&metadata[period]=yearly") +PRO_Y_ID=$(extract_id "$PRO_Y") +add_result "STRIPE_PRICE_PRO_YEARLY" "$PRO_Y_ID" +echo -e " ${GREEN}Yearly: $PRO_Y_ID ($290/yr)${NC}" +echo "" + +# --- BlackRoad OS Enterprise --- +echo -e "${AMBER}Creating: BlackRoad OS Enterprise${NC}" +ENT_PROD=$(stripe_api POST "/products" \ + "name=BlackRoad+OS+Enterprise&description=Unlimited+agents,+SSO/SAML,+SLA+99.9%%,+dedicated+support,+audit+logs&metadata[tier_id]=enterprise&metadata[product_group]=core") +ENT_PROD_ID=$(extract_id "$ENT_PROD") +check_error "$ENT_PROD" "Enterprise Product" || exit 1 +echo -e " ${GREEN}Product: $ENT_PROD_ID${NC}" + +ENT_M=$(stripe_api POST "/prices" \ + "product=$ENT_PROD_ID&unit_amount=19900¤cy=usd&recurring[interval]=month&metadata[tier_id]=enterprise&metadata[period]=monthly") +ENT_M_ID=$(extract_id "$ENT_M") +add_result "STRIPE_PRICE_ENT_MONTHLY" "$ENT_M_ID" +echo -e " ${GREEN}Monthly: $ENT_M_ID ($199/mo)${NC}" + +ENT_Y=$(stripe_api POST "/prices" \ + "product=$ENT_PROD_ID&unit_amount=199000¤cy=usd&recurring[interval]=year&metadata[tier_id]=enterprise&metadata[period]=yearly") +ENT_Y_ID=$(extract_id "$ENT_Y") +add_result "STRIPE_PRICE_ENT_YEARLY" "$ENT_Y_ID" +echo -e " ${GREEN}Yearly: $ENT_Y_ID ($1,990/yr)${NC}" +echo "" + +# ============================================================================ +# 2. OPEN SOURCE SUPPORT / SPONSORSHIP +# ============================================================================ +echo -e "${PINK}━━━ Creating Sponsorship Products ━━━${NC}" + +echo -e "${AMBER}Creating: Open Source Support${NC}" +OSS_PROD=$(stripe_api POST "/products" \ + "name=Open+Source+Support&description=Support+BlackRoad+OS+development&metadata[product_group]=sponsorship") +OSS_PROD_ID=$(extract_id "$OSS_PROD") +check_error "$OSS_PROD" "OSS Product" || exit 1 +echo -e " ${GREEN}Product: $OSS_PROD_ID${NC}" + +# Monthly tiers +for tier_info in "friend:500:5" "supporter:2500:25" "sponsor:10000:100"; do + IFS=':' read -r tier_name amount display <<< "$tier_info" + RESP=$(stripe_api POST "/prices" \ + "product=$OSS_PROD_ID&unit_amount=$amount¤cy=usd&recurring[interval]=month&metadata[tier]=$tier_name&metadata[product_group]=sponsorship") + RESP_ID=$(extract_id "$RESP") + add_result "STRIPE_PRICE_OSS_${tier_name^^}_MONTHLY" "$RESP_ID" + echo -e " ${GREEN}$tier_name: $RESP_ID (\$$display/mo)${NC}" +done + +# One-time tiers +for tier_info in "coffee:1000:10" "backer:5000:50" "champion:50000:500"; do + IFS=':' read -r tier_name amount display <<< "$tier_info" + RESP=$(stripe_api POST "/prices" \ + "product=$OSS_PROD_ID&unit_amount=$amount¤cy=usd&metadata[tier]=$tier_name&metadata[product_group]=sponsorship") + RESP_ID=$(extract_id "$RESP") + add_result "STRIPE_PRICE_OSS_${tier_name^^}_ONETIME" "$RESP_ID" + echo -e " ${GREEN}$tier_name: $RESP_ID (\$$display one-time)${NC}" +done +echo "" + +# ============================================================================ +# 3. COMMERCIAL LICENSING +# ============================================================================ +echo -e "${PINK}━━━ Creating Commercial License Products ━━━${NC}" + +echo -e "${AMBER}Creating: Commercial License${NC}" +LIC_PROD=$(stripe_api POST "/products" \ + "name=Commercial+License&description=Use+BlackRoad+OS+in+commercial+products&metadata[product_group]=licensing") +LIC_PROD_ID=$(extract_id "$LIC_PROD") +check_error "$LIC_PROD" "License Product" || exit 1 +echo -e " ${GREEN}Product: $LIC_PROD_ID${NC}" + +for tier_info in "startup:49900:499" "business:99900:999" "enterprise:249900:2499"; do + IFS=':' read -r tier_name amount display <<< "$tier_info" + RESP=$(stripe_api POST "/prices" \ + "product=$LIC_PROD_ID&unit_amount=$amount¤cy=usd&recurring[interval]=year&metadata[tier]=$tier_name&metadata[product_group]=licensing") + RESP_ID=$(extract_id "$RESP") + add_result "STRIPE_PRICE_LICENSE_${tier_name^^}_YEARLY" "$RESP_ID" + echo -e " ${GREEN}$tier_name: $RESP_ID (\$$display/yr)${NC}" +done +echo "" + +# ============================================================================ +# 4. CONSULTING +# ============================================================================ +echo -e "${PINK}━━━ Creating Consulting Products ━━━${NC}" + +echo -e "${AMBER}Creating: Consulting & Integration${NC}" +CON_PROD=$(stripe_api POST "/products" \ + "name=Consulting+%26+Integration&description=Expert+help+integrating+BlackRoad+OS&metadata[product_group]=consulting") +CON_PROD_ID=$(extract_id "$CON_PROD") +check_error "$CON_PROD" "Consulting Product" || exit 1 +echo -e " ${GREEN}Product: $CON_PROD_ID${NC}" + +for tier_info in "hourly:25000:250" "daily:150000:1500" "project:500000:5000"; do + IFS=':' read -r tier_name amount display <<< "$tier_info" + RESP=$(stripe_api POST "/prices" \ + "product=$CON_PROD_ID&unit_amount=$amount¤cy=usd&metadata[tier]=$tier_name&metadata[product_group]=consulting") + RESP_ID=$(extract_id "$RESP") + add_result "STRIPE_PRICE_CONSULTING_${tier_name^^}" "$RESP_ID" + echo -e " ${GREEN}$tier_name: $RESP_ID (\$${display})${NC}" +done +echo "" + +# ============================================================================ +# 5. PRIORITY SUPPORT +# ============================================================================ +echo -e "${PINK}━━━ Creating Priority Support Product ━━━${NC}" + +echo -e "${AMBER}Creating: Priority Support${NC}" +SUP_PROD=$(stripe_api POST "/products" \ + "name=Priority+Support&description=24/7+priority+support+with+SLA&metadata[product_group]=support") +SUP_PROD_ID=$(extract_id "$SUP_PROD") +check_error "$SUP_PROD" "Support Product" || exit 1 +echo -e " ${GREEN}Product: $SUP_PROD_ID${NC}" + +SUP_M=$(stripe_api POST "/prices" \ + "product=$SUP_PROD_ID&unit_amount=49900¤cy=usd&recurring[interval]=month&metadata[tier]=priority&metadata[product_group]=support") +SUP_M_ID=$(extract_id "$SUP_M") +add_result "STRIPE_PRICE_PRIORITY_SUPPORT_MONTHLY" "$SUP_M_ID" +echo -e " ${GREEN}Monthly: $SUP_M_ID ($499/mo)${NC}" +echo "" + +# ============================================================================ +# 6. PER-REPO PRODUCTS (Agent OS, CLI, etc.) +# ============================================================================ +echo -e "${PINK}━━━ Creating Per-Repo Products ━━━${NC}" + +for repo_info in \ + "blackroad-agent-os:Agent+OS:AI+agent+orchestration+platform" \ + "blackroad-cli:CLI:Command-line+interface+for+BlackRoad+OS" \ + "blackroad-tools:Tools:Developer+tools+and+utilities" \ + "lucidia-core:Lucidia:AI+learning+and+memory+system" \ + "blackroad-pi-ops:Pi+Ops:Raspberry+Pi+operations+and+management"; do + + IFS=':' read -r repo_slug name desc <<< "$repo_info" + + echo -e "${AMBER}Creating: $name${NC}" + REPO_PROD=$(stripe_api POST "/products" \ + "name=BlackRoad+$name&description=$desc&metadata[repo]=$repo_slug&metadata[product_group]=repos") + REPO_PROD_ID=$(extract_id "$REPO_PROD") + + if ! check_error "$REPO_PROD" "$name Product"; then + continue + fi + echo -e " ${GREEN}Product: $REPO_PROD_ID${NC}" + + # Basic $9/mo + BASIC=$(stripe_api POST "/prices" \ + "product=$REPO_PROD_ID&unit_amount=900¤cy=usd&recurring[interval]=month&metadata[tier]=basic&metadata[repo]=$repo_slug") + BASIC_ID=$(extract_id "$BASIC") + add_result "STRIPE_PRICE_${repo_slug^^}_BASIC" "$BASIC_ID" + echo -e " ${GREEN}Basic: $BASIC_ID ($9/mo)${NC}" + + # Pro $29/mo + PRO=$(stripe_api POST "/prices" \ + "product=$REPO_PROD_ID&unit_amount=2900¤cy=usd&recurring[interval]=month&metadata[tier]=pro&metadata[repo]=$repo_slug") + PRO_ID=$(extract_id "$PRO") + add_result "STRIPE_PRICE_${repo_slug^^}_PRO" "$PRO_ID" + echo -e " ${GREEN}Pro: $PRO_ID ($29/mo)${NC}" + + # Enterprise $99/mo + ENT=$(stripe_api POST "/prices" \ + "product=$REPO_PROD_ID&unit_amount=9900¤cy=usd&recurring[interval]=month&metadata[tier]=enterprise&metadata[repo]=$repo_slug") + ENT_ID=$(extract_id "$ENT") + add_result "STRIPE_PRICE_${repo_slug^^}_ENT" "$ENT_ID" + echo -e " ${GREEN}Enterprise: $ENT_ID ($99/mo)${NC}" + echo "" +done + +# ============================================================================ +# OUTPUT +# ============================================================================ +echo -e "${PINK}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${GREEN}${BOLD}All products and prices provisioned!${NC}" +echo "" + +# Write output JSON +cat > "$OUTPUT_FILE" << JSONEOF +{ + "provisioned_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", + "stripe_account": "acct_1S70Zn3e5FMFdlFw", + "mode": "$MODE", + "price_ids": { + $RESULTS + } +} +JSONEOF + +echo -e "${AMBER}Output written to: $OUTPUT_FILE${NC}" +echo "" + +# Print env var export commands +echo -e "${PINK}━━━ Environment Variables (copy to .env) ━━━${NC}" +echo "" +for key in "${!PRICE_IDS[@]}"; do + echo "$key=${PRICE_IDS[$key]}" +done | sort +echo "" + +# Print wrangler commands +echo -e "${PINK}━━━ Wrangler Secret Commands ━━━${NC}" +echo "" +echo "cd workers/payment-gateway" +for key in STRIPE_PRICE_PRO_MONTHLY STRIPE_PRICE_PRO_YEARLY STRIPE_PRICE_ENT_MONTHLY STRIPE_PRICE_ENT_YEARLY; do + if [[ -n "${PRICE_IDS[$key]:-}" ]]; then + echo "echo '${PRICE_IDS[$key]}' | wrangler secret put $key" + fi +done +echo "" + +# Print GitHub Actions secret commands +echo -e "${PINK}━━━ GitHub Actions Secrets ━━━${NC}" +echo "" +for key in "${!PRICE_IDS[@]}"; do + echo "gh secret set $key --body '${PRICE_IDS[$key]}' --org BlackRoad-OS-Inc" +done | sort +echo "" + +echo -e "${GREEN}${BOLD}Done. MOVE FASTER. THINK HARDER. ALWAYS BELIEVE.${NC}" diff --git a/blackroad-math/.env.example b/blackroad-math/.env.example new file mode 100644 index 0000000000..8de59f87eb --- /dev/null +++ b/blackroad-math/.env.example @@ -0,0 +1 @@ +PYTHON_ENV=development diff --git a/blackroad-math/.github/ISSUE_TEMPLATE/bug-report.yml b/blackroad-math/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000000..8ab785d804 --- /dev/null +++ b/blackroad-math/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,30 @@ +name: Bug Report +description: Report a bug or unexpected behavior +labels: ["bug", "triage"] +body: + - type: markdown + attributes: + value: Thanks for taking the time to report a bug! + - type: textarea + id: description + attributes: + label: What happened? + description: A clear description of the bug + validations: + required: true + - type: textarea + id: expected + attributes: + label: What did you expect? + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to reproduce + placeholder: "1. Run `br ...`\n2. See error" + - type: input + id: version + attributes: + label: Version + placeholder: "e.g. 1.0.0" diff --git a/blackroad-math/.github/ISSUE_TEMPLATE/feature-request.yml b/blackroad-math/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 0000000000..cbdcb2d0e2 --- /dev/null +++ b/blackroad-math/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,21 @@ +name: Feature Request +description: Suggest a new feature or improvement +labels: ["enhancement"] +body: + - type: textarea + id: problem + attributes: + label: Problem statement + description: What problem does this solve? + validations: + required: true + - type: textarea + id: solution + attributes: + label: Proposed solution + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives considered diff --git a/blackroad-math/.github/PULL_REQUEST_TEMPLATE.md b/blackroad-math/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..c40a1f7ad4 --- /dev/null +++ b/blackroad-math/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,13 @@ +## Summary + + +## Changes +- + +## Testing +- [ ] Tested locally +- [ ] Added/updated tests +- [ ] Updated docs if needed + +## Related Issues + diff --git a/blackroad-math/.github/workflows/ci.yml b/blackroad-math/.github/workflows/ci.yml new file mode 100644 index 0000000000..5d78a6f39e --- /dev/null +++ b/blackroad-math/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + lint: + name: Lint & Validate + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check for secrets + run: | + if grep -r "PRIVATE_KEY\|SECRET_KEY\|API_KEY\s*=" --include="*.py" --include="*.ts" --include="*.js" .; then + echo "⚠️ Potential hardcoded secrets found" + exit 1 + fi + echo "✓ No hardcoded secrets" + - name: Validate markdown + run: find . -name "*.md" -exec sh -c 'wc -l "$1" > /dev/null' _ {} \; + continue-on-error: true + - name: Done + run: echo "✓ CI passed" diff --git a/blackroad-os/apps/blackroad-dashboard/index.html b/blackroad-os/apps/blackroad-dashboard/index.html new file mode 100644 index 0000000000..6260dad32c --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/index.html @@ -0,0 +1,13 @@ + + + + + + BlackRoad OS + + + +
    + + + diff --git a/blackroad-os/apps/blackroad-dashboard/package-lock.json b/blackroad-os/apps/blackroad-dashboard/package-lock.json new file mode 100644 index 0000000000..471f70b766 --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/package-lock.json @@ -0,0 +1,1794 @@ +{ + "name": "blackroad-dashboard", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "blackroad-dashboard", + "version": "1.0.0", + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "vite": "^6.1.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001772", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001772.tgz", + "integrity": "sha512-mIwLZICj+ntVTw4BT2zfp+yu/AqV6GMKfJVJMx3MwPxs+uk/uj2GLl2dH8LQbjiLDX66amCga5nKFyDgRR43kg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.302", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", + "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/blackroad-os/apps/blackroad-dashboard/package.json b/blackroad-os/apps/blackroad-dashboard/package.json new file mode 100644 index 0000000000..dd05a13300 --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/package.json @@ -0,0 +1,20 @@ +{ + "name": "blackroad-dashboard", + "private": true, + "version": "1.0.0", + "description": "BlackRoad OS Mission Control — agents, Railway, GitHub", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "vite": "^6.1.0" + } +} diff --git a/blackroad-os/apps/blackroad-dashboard/src/App.jsx b/blackroad-os/apps/blackroad-dashboard/src/App.jsx new file mode 100644 index 0000000000..81bec33715 --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/App.jsx @@ -0,0 +1,95 @@ +import { useState, useEffect } from 'react' +import AgentPanel from './components/AgentPanel.jsx' +import RailwayPanel from './components/RailwayPanel.jsx' +import GitHubPanel from './components/GitHubPanel.jsx' +import Settings from './components/Settings.jsx' +import styles from './App.module.css' + +const STORAGE_KEY = 'br_dashboard_config' +const DEFAULT_CONFIG = { + railwayToken: '', + githubToken: '', + githubOrg: 'BlackRoad-OS-Inc', + githubRepo: 'blackroad', +} + +function loadConfig() { + try { return { ...DEFAULT_CONFIG, ...JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}') } } + catch { return DEFAULT_CONFIG } +} + +export default function App() { + const [view, setView] = useState('dashboard') + const [config, setConfig] = useState(loadConfig) + const [saved, setSaved] = useState(false) + + const saveConfig = (next) => { + localStorage.setItem(STORAGE_KEY, JSON.stringify(next)) + setConfig(next) + setSaved(true) + setTimeout(() => { setSaved(false); setView('dashboard') }, 1000) + } + + // Auto-route to settings if no tokens set + useEffect(() => { + if (!config.railwayToken && !config.githubToken) setView('settings') + }, []) // eslint-disable-line + + const hasTokens = config.railwayToken || config.githubToken + + return ( +
    + {/* Header */} +
    +
    + ◆ BlackRoad OS + Mission Control +
    + +
    + + {/* Body */} +
    + {view === 'settings' ? ( + + ) : ( + <> + {saved &&
    ✓ Saved
    } +
    + + + +
    + + )} +
    +
    + ) +} + +function NavBtn({ active, onClick, children }) { + return ( + + ) +} diff --git a/blackroad-os/apps/blackroad-dashboard/src/App.module.css b/blackroad-os/apps/blackroad-dashboard/src/App.module.css new file mode 100644 index 0000000000..3e7e32d737 --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/App.module.css @@ -0,0 +1,50 @@ +.app { + min-height: 100vh; + display: flex; + flex-direction: column; +} +.header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 24px; + height: 56px; + border-bottom: 1px solid var(--border); + position: sticky; + top: 0; + background: rgba(0,0,0,.85); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + z-index: 100; +} +.logo { display: flex; align-items: baseline; gap: 10px; } +.tagline { font-size: 12px; color: var(--muted); } +.nav { display: flex; gap: 6px; } +.main { flex: 1; padding: 24px; } +.grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); + gap: 16px; + max-width: 1200px; + margin: 0 auto; +} +.toast { + position: fixed; + bottom: 24px; + right: 24px; + background: var(--green); + color: #000; + font-weight: 700; + font-size: 13px; + padding: 10px 18px; + border-radius: 8px; + z-index: 999; + animation: fadeIn .2s ease; +} +@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } } + +@media (max-width: 600px) { + .header { padding: 0 16px; } + .main { padding: 16px; } + .grid { grid-template-columns: 1fr; } +} diff --git a/blackroad-os/apps/blackroad-dashboard/src/components/ActionsPanel.jsx b/blackroad-os/apps/blackroad-dashboard/src/components/ActionsPanel.jsx new file mode 100644 index 0000000000..34a7aad378 --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/components/ActionsPanel.jsx @@ -0,0 +1,79 @@ +import { useState } from 'react' +import styles from './ActionsPanel.module.css' + +const ACTIONS = [ + { + id: 'worker', + label: 'Deploy Worker', + icon: '⚡', + desc: 'wrangler deploy → blackroad-os-api', + color: '#F5A623', + cmd: 'cd blackroad-os/workers/blackroad-os-api && wrangler deploy', + }, + { + id: 'railway', + label: 'Deploy Railway', + icon: '🚂', + desc: 'railway up → current project', + color: '#a78bfa', + cmd: 'railway up', + }, + { + id: 'pages', + label: 'Deploy Dashboard', + icon: '🌐', + desc: 'wrangler pages deploy → dashboard.blackroad.io', + color: '#2979FF', + cmd: 'cd blackroad-os/apps/blackroad-dashboard && npm run build && wrangler pages deploy dist --project-name=blackroad-dashboard', + }, + { + id: 'git', + label: 'Git Push', + icon: '◆', + desc: 'push master → BlackRoad-OS-Inc', + color: '#FF1D6C', + cmd: 'git push origin master', + }, +] + +export default function ActionsPanel() { + const [copied, setCopied] = useState(null) + + const copy = (action) => { + navigator.clipboard.writeText(action.cmd) + setCopied(action.id) + setTimeout(() => setCopied(null), 1500) + } + + return ( +
    +
    + ◆ Quick Actions + copy to run +
    + +
    + {ACTIONS.map(a => ( + + ))} +
    + +
    + + Run commands in ~/blackroad + +
    +
    + ) +} diff --git a/blackroad-os/apps/blackroad-dashboard/src/components/ActionsPanel.module.css b/blackroad-os/apps/blackroad-dashboard/src/components/ActionsPanel.module.css new file mode 100644 index 0000000000..ba7d411795 --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/components/ActionsPanel.module.css @@ -0,0 +1,31 @@ +.panel { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; +} +.header { + display: flex; align-items: center; gap: 10px; + padding: 14px 18px; border-bottom: 1px solid var(--border); +} +.title { font-weight: 600; font-size: 13px; color: var(--amber); } +.grid { padding: 12px; display: grid; grid-template-columns: 1fr 1fr; gap: 8px; } +.action { + display: flex; align-items: center; gap: 10px; + padding: 12px; background: var(--surface2); + border-radius: 8px; border: 1px solid var(--border); + text-align: left; transition: all .15s; cursor: pointer; +} +.action:hover { + border-color: var(--accent, var(--amber)); + background: color-mix(in srgb, var(--accent, var(--amber)) 8%, var(--surface2)); +} +.action.copied { + border-color: var(--green); + background: rgba(74,222,128,.08); +} +.icon { font-size: 20px; flex-shrink: 0; } +.text { display: flex; flex-direction: column; min-width: 0; } +.label { font-size: 12px; font-weight: 600; } +.desc { font-size: 11px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.footer { padding: 10px 18px; border-top: 1px solid var(--border); } diff --git a/blackroad-os/apps/blackroad-dashboard/src/components/ActivityPanel.jsx b/blackroad-os/apps/blackroad-dashboard/src/components/ActivityPanel.jsx new file mode 100644 index 0000000000..e27525e80b --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/components/ActivityPanel.jsx @@ -0,0 +1,82 @@ +import styles from './ActivityPanel.module.css' + +function ago(iso) { + const s = Math.floor((Date.now() - new Date(iso)) / 1000) + if (s < 60) return `${s}s ago` + if (s < 3600) return `${Math.floor(s/60)}m ago` + if (s < 86400) return `${Math.floor(s/3600)}h ago` + return `${Math.floor(s/86400)}d ago` +} + +const ICONS = { + gh_run: '⚙', + gh_push: '◆', + deploy: '🚂', + worker: '⚡', +} + +export default function ActivityPanel({ githubRuns, deployments }) { + // Merge and sort events from GitHub runs + Railway deployments + const events = [ + ...(githubRuns || []).slice(0, 5).map(r => ({ + id: `gh-${r.id}`, + type: 'gh_run', + title: r.name, + sub: `${r.branch} · ${r.actor}`, + status: r.conclusion || r.status, + time: r.created, + url: r.url, + })), + ...(deployments || []).slice(0, 5).map(d => ({ + id: `rail-${d.id}`, + type: 'deploy', + title: d.service || 'Railway', + sub: `${d.branch || ''} ${d.message ? '· ' + d.message.slice(0, 40) : ''}`.trim(), + status: d.status, + time: d.created, + url: null, + })), + ].sort((a, b) => new Date(b.time) - new Date(a.time)).slice(0, 10) + + const statusColor = (s) => { + if (!s) return 'queued' + s = s.toLowerCase() + if (['success','complete','succeeded'].includes(s)) return styles.success + if (['failure','failed','crashed'].includes(s)) return styles.failure + if (['in_progress','deploying','building'].includes(s)) return styles.running + return styles.neutral + } + + if (!events.length) { + return ( +
    +
    ◆ Activity
    +
    Connect Railway + GitHub to see activity
    +
    + ) + } + + return ( +
    +
    + ◆ Activity + {events.length} events +
    +
    + {events.map(e => ( +
    e.url && window.open(e.url)}> + {ICONS[e.type]} +
    + {e.title} + {e.sub && {e.sub}} +
    +
    + + {ago(e.time)} +
    +
    + ))} +
    +
    + ) +} diff --git a/blackroad-os/apps/blackroad-dashboard/src/components/ActivityPanel.module.css b/blackroad-os/apps/blackroad-dashboard/src/components/ActivityPanel.module.css new file mode 100644 index 0000000000..ce8f568775 --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/components/ActivityPanel.module.css @@ -0,0 +1,30 @@ +.panel { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; +} +.header { + display: flex; align-items: center; gap: 10px; + padding: 14px 18px; border-bottom: 1px solid var(--border); +} +.title { font-weight: 600; font-size: 13px; color: var(--amber); } +.empty { padding: 24px 18px; color: var(--muted); font-size: 13px; text-align: center; } +.feed { padding: 8px 12px; display: flex; flex-direction: column; gap: 4px; } +.event { + display: flex; align-items: center; gap: 10px; + padding: 9px 10px; border-radius: 7px; + cursor: default; transition: background .12s; +} +.event:hover { background: var(--surface2); } +.icon { font-size: 15px; flex-shrink: 0; width: 22px; text-align: center; } +.body { flex: 1; min-width: 0; display: flex; flex-direction: column; } +.title2 { font-size: 12px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.sub { font-size: 11px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.right { display: flex; align-items: center; gap: 6px; flex-shrink: 0; } +.dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; } +.success { background: var(--green); } +.failure { background: var(--red); } +.running { background: var(--blue); animation: pulse 1.2s infinite; } +.neutral { background: var(--muted); } +@keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: .35 } } diff --git a/blackroad-os/apps/blackroad-dashboard/src/components/AgentPanel.jsx b/blackroad-os/apps/blackroad-dashboard/src/components/AgentPanel.jsx new file mode 100644 index 0000000000..85f76e94cb --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/components/AgentPanel.jsx @@ -0,0 +1,43 @@ +import styles from './AgentPanel.module.css' +import { useAgents } from '../hooks/useAgents' + +export default function AgentPanel() { + const { data, loading, error, reload } = useAgents() + + if (loading) return
    loading…
    + if (error) return
    {error}
    + + const agents = data?.agents || [] + const online = agents.filter(a => a.status === 'online').length + + return ( + +
    + {agents.map(a => ( +
    +
    {a.emoji}
    +
    +
    {a.name}
    +
    {a.role}
    +
    {a.model}
    +
    + {a.status} +
    + ))} +
    +
    + ) +} + +function Panel({ title, meta, onRefresh, children }) { + return ( +
    +
    + ◆ {title} + {meta && {meta}} + {onRefresh && } +
    + {children} +
    + ) +} diff --git a/blackroad-os/apps/blackroad-dashboard/src/components/AgentPanel.module.css b/blackroad-os/apps/blackroad-dashboard/src/components/AgentPanel.module.css new file mode 100644 index 0000000000..de963406e9 --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/components/AgentPanel.module.css @@ -0,0 +1,42 @@ +.panel { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; +} +.header { + display: flex; + align-items: center; + gap: 10px; + padding: 14px 18px; + border-bottom: 1px solid var(--border); +} +.title { font-weight: 600; font-size: 13px; color: var(--amber); } +.refresh { + margin-left: auto; + background: none; + color: var(--muted); + font-size: 16px; + padding: 0 4px; + transition: color .15s; +} +.refresh:hover { color: var(--amber); } +.grid { padding: 12px; display: flex; flex-direction: column; gap: 6px; } +.card { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 12px; + background: var(--surface2); + border-radius: 8px; + border: 1px solid transparent; + transition: border-color .15s; +} +.card:hover { border-color: var(--border); } +.emoji { font-size: 20px; width: 28px; text-align: center; flex-shrink: 0; } +.info { flex: 1; min-width: 0; } +.name { font-weight: 600; font-size: 13px; } +.role { color: var(--muted); font-size: 12px; } +.model { color: var(--muted); font-size: 11px; font-family: monospace; margin-top: 1px; } +.loading, .error { padding: 20px; color: var(--muted); text-align: center; font-size: 13px; } +.error { color: var(--red); } diff --git a/blackroad-os/apps/blackroad-dashboard/src/components/CloudflarePanel.jsx b/blackroad-os/apps/blackroad-dashboard/src/components/CloudflarePanel.jsx new file mode 100644 index 0000000000..4ea661cf46 --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/components/CloudflarePanel.jsx @@ -0,0 +1,58 @@ +import styles from './CloudflarePanel.module.css' +import { useWorkers } from '../hooks/useWorkers' + +export default function CloudflarePanel() { + const { statuses, loading, workers, reload } = useWorkers() + + return ( +
    +
    + ◆ Workers + + {Object.values(statuses).filter(s => s.ok).length}/{workers.length} up + + +
    + +
    + {workers.map(w => { + const s = statuses[w.name] + const ok = s?.ok + const latency = s?.latency + const agents = s?.data?.agents_online + + return ( +
    + +
    + {w.name} + {s?.data?.version && ( + v{s.data.version} + )} +
    +
    + {agents != null && ( + {agents} agents + )} + {latency != null && ( + + {latency}ms + + )} +
    +
    + ) + })} +
    + + +
    + ) +} diff --git a/blackroad-os/apps/blackroad-dashboard/src/components/CloudflarePanel.module.css b/blackroad-os/apps/blackroad-dashboard/src/components/CloudflarePanel.module.css new file mode 100644 index 0000000000..539dfa467c --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/components/CloudflarePanel.module.css @@ -0,0 +1,39 @@ +.panel { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; +} +.header { + display: flex; align-items: center; gap: 10px; + padding: 14px 18px; border-bottom: 1px solid var(--border); +} +.title { font-weight: 600; font-size: 13px; color: var(--amber); } +.refresh { margin-left: auto; background: none; color: var(--muted); font-size: 16px; padding: 0 4px; } +.refresh:hover { color: var(--amber); } +.list { padding: 12px; display: flex; flex-direction: column; gap: 6px; } +.row { + display: flex; align-items: center; gap: 12px; + padding: 10px 12px; background: var(--surface2); + border-radius: 8px; +} +.dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; } +.up { background: var(--green); box-shadow: 0 0 6px var(--green); } +.down { background: var(--red); } +.loading { background: var(--muted); animation: pulse 1s infinite; } +@keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: .3 } } +.info { flex: 1; display: flex; flex-direction: column; } +.name { font-size: 12px; font-weight: 600; font-family: monospace; } +.meta { display: flex; align-items: center; gap: 6px; flex-shrink: 0; } +.pill { background: rgba(245,166,35,.12); color: var(--amber); font-size: 11px; padding: 1px 7px; border-radius: 100px; } +.latency { font-size: 11px; font-family: monospace; } +.fast { color: var(--green); } +.mid { color: var(--amber); } +.slow { color: var(--red); } +.footer { + display: flex; gap: 16px; + padding: 10px 18px; + border-top: 1px solid var(--border); +} +.link { font-size: 12px; color: var(--muted); text-decoration: none; } +.link:hover { color: var(--amber); } diff --git a/blackroad-os/apps/blackroad-dashboard/src/components/GitHubPanel.jsx b/blackroad-os/apps/blackroad-dashboard/src/components/GitHubPanel.jsx new file mode 100644 index 0000000000..69fd5edaa1 --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/components/GitHubPanel.jsx @@ -0,0 +1,81 @@ +import styles from './GitHubPanel.module.css' +import { useGitHub } from '../hooks/useGitHub' + +const CONCLUSION_CLASS = { + success: 'success', failure: 'failure', cancelled: 'queued', + skipped: 'queued', timed_out: 'failure', action_required: 'standby', +} + +function ago(iso) { + const s = Math.floor((Date.now() - new Date(iso)) / 1000) + if (s < 60) return `${s}s ago` + if (s < 3600) return `${Math.floor(s/60)}m ago` + if (s < 86400) return `${Math.floor(s/3600)}h ago` + return `${Math.floor(s/86400)}d ago` +} + +export default function GitHubPanel({ token, org, repo }) { + const { runs, repoInfo, loading, error, reload } = useGitHub(token, org, repo) + + if (!token) return ( +
    +
    ◆ GitHub
    +
    Add GitHub token in Settings →
    +
    + ) + + return ( +
    +
    + ◆ GitHub + {repoInfo && {repoInfo.name}} + +
    + + {repoInfo && ( +
    + + + + +
    + )} + + {loading && !runs &&
    loading…
    } + {error &&
    {error}
    } + + {runs && runs.length > 0 && ( + <> +
    Actions
    +
    + {runs.map(r => { + const badgeClass = r.status === 'in_progress' ? 'in_progress' + : (CONCLUSION_CLASS[r.conclusion] || 'queued') + const label = r.status === 'in_progress' ? 'running' + : (r.conclusion || r.status) + return ( + + {label} +
    + {r.name} + {r.branch} · {r.actor} +
    + {ago(r.created)} +
    + ) + })} +
    + + )} +
    + ) +} + +function Stat({ label, value }) { + return ( +
    +
    {value}
    +
    {label}
    +
    + ) +} diff --git a/blackroad-os/apps/blackroad-dashboard/src/components/GitHubPanel.module.css b/blackroad-os/apps/blackroad-dashboard/src/components/GitHubPanel.module.css new file mode 100644 index 0000000000..cb0635fbd6 --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/components/GitHubPanel.module.css @@ -0,0 +1,31 @@ +.panel { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; +} +.header { + display: flex; align-items: center; gap: 10px; + padding: 14px 18px; border-bottom: 1px solid var(--border); +} +.title { font-weight: 600; font-size: 13px; color: var(--amber); } +.refresh { margin-left: auto; background: none; color: var(--muted); font-size: 16px; padding: 0 4px; } +.refresh:hover { color: var(--amber); } +.repoStats { + display: grid; grid-template-columns: repeat(4, 1fr); + padding: 14px 18px; gap: 8px; + border-bottom: 1px solid var(--border); +} +.empty { padding: 24px 18px; color: var(--muted); font-size: 13px; text-align: center; } +.error { padding: 16px 18px; color: var(--red); font-size: 13px; } +.sectionLabel { padding: 8px 18px 4px; font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; } +.runs { padding: 4px 12px 12px; display: flex; flex-direction: column; gap: 5px; } +.run { + display: flex; align-items: center; gap: 10px; + padding: 8px 10px; background: var(--surface2); border-radius: 7px; + text-decoration: none; color: inherit; border: 1px solid transparent; + transition: border-color .15s; +} +.run:hover { border-color: var(--border); } +.runInfo { flex: 1; min-width: 0; display: flex; flex-direction: column; } +.runName { font-size: 12px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } diff --git a/blackroad-os/apps/blackroad-dashboard/src/components/RailwayPanel.jsx b/blackroad-os/apps/blackroad-dashboard/src/components/RailwayPanel.jsx new file mode 100644 index 0000000000..beaa429c67 --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/components/RailwayPanel.jsx @@ -0,0 +1,72 @@ +import styles from './RailwayPanel.module.css' +import { useRailway } from '../hooks/useRailway' + +const STATUS_COLOR = { + SUCCESS: 'success', COMPLETE: 'success', + FAILED: 'failure', CRASHED: 'failure', + DEPLOYING: 'in_progress', BUILDING: 'in_progress', + QUEUED: 'queued', +} + +function ago(iso) { + const s = Math.floor((Date.now() - new Date(iso)) / 1000) + if (s < 60) return `${s}s ago` + if (s < 3600) return `${Math.floor(s/60)}m ago` + if (s < 86400) return `${Math.floor(s/3600)}h ago` + return `${Math.floor(s/86400)}d ago` +} + +export default function RailwayPanel({ token }) { + const { projects, deployments, loading, error, reload } = useRailway(token) + + if (!token) return ( +
    +
    ◆ Railway
    +
    Add Railway token in Settings →
    +
    + ) + + return ( +
    +
    + ◆ Railway + {projects && {projects.length} projects} + +
    + + {loading && !projects &&
    loading…
    } + {error &&
    {error}
    } + + {projects && ( +
    + {projects.map(p => ( +
    +
    ◆ {p.name}
    +
    + {p.services} services · {p.environments} envs · {ago(p.updated)} +
    +
    + ))} +
    + )} + + {deployments && deployments.length > 0 && ( + <> +
    Recent Deployments
    +
    + {deployments.slice(0, 6).map(d => ( +
    + {d.status} +
    + {d.service} + {d.branch} · {d.message?.slice(0,50)} +
    + {ago(d.created)} +
    + ))} +
    + + )} +
    + ) +} diff --git a/blackroad-os/apps/blackroad-dashboard/src/components/RailwayPanel.module.css b/blackroad-os/apps/blackroad-dashboard/src/components/RailwayPanel.module.css new file mode 100644 index 0000000000..8150bd83dd --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/components/RailwayPanel.module.css @@ -0,0 +1,29 @@ +.panel { + background: var(--surface); + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; +} +.header { + display: flex; align-items: center; gap: 10px; + padding: 14px 18px; border-bottom: 1px solid var(--border); +} +.title { font-weight: 600; font-size: 13px; color: var(--amber); } +.refresh { margin-left: auto; background: none; color: var(--muted); font-size: 16px; padding: 0 4px; } +.refresh:hover { color: var(--amber); } +.empty { padding: 24px 18px; color: var(--muted); font-size: 13px; text-align: center; } +.error { padding: 16px 18px; color: var(--red); font-size: 13px; } +.projects { padding: 12px; display: flex; flex-direction: column; gap: 6px; } +.project { + padding: 10px 14px; + background: var(--surface2); + border-radius: 8px; + border: 1px solid var(--border); +} +.projectName { font-weight: 600; font-size: 13px; color: var(--amber); } +.projectMeta { color: var(--muted); font-size: 12px; margin-top: 2px; } +.sectionLabel { padding: 8px 18px 4px; font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; border-top: 1px solid var(--border); } +.deploys { padding: 6px 12px 12px; display: flex; flex-direction: column; gap: 5px; } +.deploy { display: flex; align-items: center; gap: 10px; padding: 8px 10px; background: var(--surface2); border-radius: 7px; } +.deployInfo { flex: 1; min-width: 0; display: flex; flex-direction: column; } +.deployService { font-size: 12px; font-weight: 600; } diff --git a/blackroad-os/apps/blackroad-dashboard/src/components/Settings.jsx b/blackroad-os/apps/blackroad-dashboard/src/components/Settings.jsx new file mode 100644 index 0000000000..eee1becda7 --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/components/Settings.jsx @@ -0,0 +1,60 @@ +import { useState } from 'react' +import styles from './Settings.module.css' + +export default function Settings({ config, onSave }) { + const [form, setForm] = useState(config) + + const set = (k, v) => setForm(f => ({ ...f, [k]: v })) + + return ( +
    +
    +
    + Settings +
    +

    Tokens are saved in your browser only — never sent to any server except the API you're connecting to.

    + + set('railwayToken', v)} + placeholder="••••••••••••••••" /> + + set('githubToken', v)} + placeholder="ghp_••••••••••••••••" /> + + set('githubOrg', v)} + placeholder="BlackRoad-OS-Inc" /> + + set('githubRepo', v)} + placeholder="blackroad" /> + + +
    +
    + ) +} + +function Field({ label, type, value, onChange, placeholder }) { + return ( +
    + + onChange(e.target.value)} + placeholder={placeholder} + autoComplete="off" + /> +
    + ) +} diff --git a/blackroad-os/apps/blackroad-dashboard/src/components/Settings.module.css b/blackroad-os/apps/blackroad-dashboard/src/components/Settings.module.css new file mode 100644 index 0000000000..15b9fdc10e --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/components/Settings.module.css @@ -0,0 +1,29 @@ +.wrap { + display: flex; + justify-content: center; + align-items: flex-start; + padding: 40px 20px; +} +.card { + background: var(--surface); + border: 1px solid var(--border); + border-radius: 14px; + padding: 32px; + width: 100%; + max-width: 480px; +} +.title { font-size: 18px; font-weight: 700; margin-bottom: 8px; } +.sub { color: var(--muted); font-size: 12px; margin-bottom: 24px; line-height: 1.6; } +.save { + width: 100%; + padding: 12px; + background: var(--grad); + background: linear-gradient(135deg, #F5A623 0%, #FF1D6C 38.2%, #9C27B0 61.8%, #2979FF 100%); + color: #fff; + font-weight: 700; + font-size: 14px; + border-radius: 8px; + margin-top: 8px; + transition: opacity .15s; +} +.save:hover { opacity: .88; } diff --git a/blackroad-os/apps/blackroad-dashboard/src/hooks/useAgents.js b/blackroad-os/apps/blackroad-dashboard/src/hooks/useAgents.js new file mode 100644 index 0000000000..3b1a5b17a1 --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/hooks/useAgents.js @@ -0,0 +1,24 @@ +import { useState, useEffect, useCallback } from 'react' + +const WORKER = 'https://blackroad-os-api.amundsonalexa.workers.dev' + +export function useAgents() { + const [data, setData] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + const load = useCallback(async () => { + try { + const r = await fetch(`${WORKER}/agents`) + setData(await r.json()) + setError(null) + } catch (e) { + setError(e.message) + } finally { + setLoading(false) + } + }, []) + + useEffect(() => { load(); const t = setInterval(load, 30000); return () => clearInterval(t) }, [load]) + return { data, loading, error, reload: load } +} diff --git a/blackroad-os/apps/blackroad-dashboard/src/hooks/useGitHub.js b/blackroad-os/apps/blackroad-dashboard/src/hooks/useGitHub.js new file mode 100644 index 0000000000..341c23ee4e --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/hooks/useGitHub.js @@ -0,0 +1,67 @@ +import { useState, useEffect, useCallback } from 'react' + +const BASE = 'https://api.github.com' + +export function useGitHub(token, org = 'BlackRoad-OS-Inc', repo = 'blackroad') { + const [runs, setRuns] = useState(null) + const [repoInfo, setRepoInfo] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + + const ghFetch = useCallback(async (path) => { + const r = await fetch(`${BASE}${path}`, { + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + }, + }) + if (!r.ok) throw new Error(`GitHub ${r.status}: ${r.statusText}`) + return r.json() + }, [token]) + + const load = useCallback(async () => { + if (!token) return + setLoading(true) + try { + const [runsData, repoData] = await Promise.all([ + ghFetch(`/repos/${org}/${repo}/actions/runs?per_page=10`), + ghFetch(`/repos/${org}/${repo}`), + ]) + setRuns((runsData.workflow_runs || []).map(r => ({ + id: r.id, + name: r.name, + status: r.status, + conclusion: r.conclusion, + branch: r.head_branch, + commit: r.head_sha?.slice(0, 7), + actor: r.actor?.login, + created: r.created_at, + url: r.html_url, + }))) + setRepoInfo({ + name: repoData.full_name, + stars: repoData.stargazers_count, + forks: repoData.forks_count, + issues: repoData.open_issues_count, + branch: repoData.default_branch, + updated: repoData.updated_at, + }) + setError(null) + } catch (e) { + setError(e.message) + } finally { + setLoading(false) + } + }, [token, org, repo, ghFetch]) + + useEffect(() => { + if (token) { + load() + const t = setInterval(load, 30000) + return () => clearInterval(t) + } + }, [token, load]) + + return { runs, repoInfo, loading, error, reload: load } +} diff --git a/blackroad-os/apps/blackroad-dashboard/src/hooks/useRailway.js b/blackroad-os/apps/blackroad-dashboard/src/hooks/useRailway.js new file mode 100644 index 0000000000..5a32343d1a --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/hooks/useRailway.js @@ -0,0 +1,95 @@ +import { useState, useEffect, useCallback } from 'react' + +const GQL = 'https://backboard.railway.app/graphql/v2' + +const PROJECTS_QUERY = `query { + me { + projects { + edges { + node { + id name updatedAt + services { edges { node { id name } } } + environments { edges { node { id name } } } + } + } + } + } +}` + +const DEPLOYS_QUERY = `query($projectId: String) { + deployments(input: { projectId: $projectId }) { + edges { + node { + id status createdAt + meta { branch commitMessage author } + service { id name } + } + } + } +}` + +export function useRailway(token) { + const [projects, setProjects] = useState(null) + const [deployments, setDeployments] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + + const gql = useCallback(async (query, variables = {}) => { + const r = await fetch(GQL, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ query, variables }), + }) + const d = await r.json() + if (d.errors) throw new Error(d.errors[0].message) + return d.data + }, [token]) + + const loadProjects = useCallback(async () => { + if (!token) return + setLoading(true) + try { + const data = await gql(PROJECTS_QUERY) + const items = data?.me?.projects?.edges?.map(e => ({ + id: e.node.id, + name: e.node.name, + services: e.node.services?.edges?.length || 0, + environments: e.node.environments?.edges?.length || 0, + updated: e.node.updatedAt, + })) || [] + setProjects(items) + // Load deployments for first project + if (items.length > 0) { + const ddata = await gql(DEPLOYS_QUERY, { projectId: items[0].id }) + const deps = ddata?.deployments?.edges?.map(e => ({ + id: e.node.id, + status: e.node.status, + service: e.node.service?.name, + branch: e.node.meta?.branch, + message: e.node.meta?.commitMessage, + author: e.node.meta?.author, + created: e.node.createdAt, + })) || [] + setDeployments(deps) + } + setError(null) + } catch (e) { + setError(e.message) + } finally { + setLoading(false) + } + }, [token, gql]) + + useEffect(() => { + if (token) { + loadProjects() + const t = setInterval(loadProjects, 60000) + return () => clearInterval(t) + } + }, [token, loadProjects]) + + return { projects, deployments, loading, error, reload: loadProjects } +} diff --git a/blackroad-os/apps/blackroad-dashboard/src/hooks/useWorkers.js b/blackroad-os/apps/blackroad-dashboard/src/hooks/useWorkers.js new file mode 100644 index 0000000000..4bc2cd7bb9 --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/hooks/useWorkers.js @@ -0,0 +1,33 @@ +import { useState, useEffect, useCallback } from 'react' + +const WORKERS = [ + { name: 'blackroad-os-api', url: 'https://blackroad-os-api.amundsonalexa.workers.dev/health' }, + { name: 'email-router', url: 'https://blackroad-email-router.amundsonalexa.workers.dev/health' }, +] + +export function useWorkers() { + const [statuses, setStatuses] = useState({}) + const [loading, setLoading] = useState(true) + + const ping = useCallback(async () => { + const results = await Promise.all( + WORKERS.map(async w => { + const t0 = Date.now() + try { + const r = await fetch(w.url, { signal: AbortSignal.timeout(5000) }) + const data = await r.json() + return { ...w, ok: r.ok, latency: Date.now() - t0, data } + } catch { + return { ...w, ok: false, latency: Date.now() - t0, data: null } + } + }) + ) + const map = {} + results.forEach(r => { map[r.name] = r }) + setStatuses(map) + setLoading(false) + }, []) + + useEffect(() => { ping(); const t = setInterval(ping, 20000); return () => clearInterval(t) }, [ping]) + return { statuses, loading, workers: WORKERS, reload: ping } +} diff --git a/blackroad-os/apps/blackroad-dashboard/src/index.css b/blackroad-os/apps/blackroad-dashboard/src/index.css new file mode 100644 index 0000000000..b6b9507981 --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/index.css @@ -0,0 +1,79 @@ +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + +:root { + --amber: #F5A623; + --pink: #FF1D6C; + --violet: #9C27B0; + --blue: #2979FF; + --green: #4ade80; + --red: #f43f5e; + --bg: #000; + --surface: #0a0a0a; + --surface2:#111; + --border: #1c1c1c; + --text: #fff; + --muted: #555; + --grad: linear-gradient(135deg, #F5A623 0%, #FF1D6C 38.2%, #9C27B0 61.8%, #2979FF 100%); + --font: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Inter', sans-serif; + --radius: 10px; +} + +html, body, #root { height: 100%; } +body { + background: var(--bg); + color: var(--text); + font-family: var(--font); + font-size: 14px; + line-height: 1.6; + -webkit-font-smoothing: antialiased; +} + +/* Scrollbar */ +::-webkit-scrollbar { width: 4px; } +::-webkit-scrollbar-track { background: var(--bg); } +::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; } + +/* Utilities */ +.grad-text { + background: var(--grad); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} +.muted { color: var(--muted); } +.badge { + display: inline-block; + padding: 2px 8px; + border-radius: 100px; + font-size: 11px; + font-weight: 600; + letter-spacing: .04em; + text-transform: uppercase; +} +.badge.online { background: rgba(74,222,128,.12); color: var(--green); } +.badge.standby { background: rgba(245,166,35,.12); color: var(--amber); } +.badge.offline { background: rgba(244,63,94,.12); color: var(--red); } +.badge.success { background: rgba(74,222,128,.12); color: var(--green); } +.badge.failure { background: rgba(244,63,94,.12); color: var(--red); } +.badge.in_progress { background: rgba(41,121,255,.12); color: var(--blue); } +.badge.queued { background: rgba(85,85,85,.2); color: var(--muted); } + +button { + cursor: pointer; + border: none; + font-family: var(--font); +} + +input, textarea { + font-family: var(--font); + background: var(--surface2); + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text); + padding: 10px 14px; + font-size: 13px; + width: 100%; + outline: none; + transition: border-color .15s; +} +input:focus, textarea:focus { border-color: var(--amber); } diff --git a/blackroad-os/apps/blackroad-dashboard/src/main.jsx b/blackroad-os/apps/blackroad-dashboard/src/main.jsx new file mode 100644 index 0000000000..5e8d112308 --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/src/main.jsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.jsx' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')).render( + + + +) diff --git a/blackroad-os/apps/blackroad-dashboard/vite.config.js b/blackroad-os/apps/blackroad-dashboard/vite.config.js new file mode 100644 index 0000000000..8b67a54465 --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + build: { outDir: 'dist' }, +}) diff --git a/blackroad-os/apps/blackroad-dashboard/wrangler.toml b/blackroad-os/apps/blackroad-dashboard/wrangler.toml new file mode 100644 index 0000000000..94d51ff22d --- /dev/null +++ b/blackroad-os/apps/blackroad-dashboard/wrangler.toml @@ -0,0 +1,3 @@ +name = "blackroad-dashboard" +pages_build_output_dir = "dist" +compatibility_date = "2024-12-01" diff --git a/blackroad-os/game.js b/blackroad-os/game.js new file mode 100644 index 0000000000..16bc6580be --- /dev/null +++ b/blackroad-os/game.js @@ -0,0 +1,127 @@ +const config = { + type: Phaser.AUTO, + width: 800, + height: 600, + backgroundColor: '#1a1a2e', + physics: { default: 'arcade', arcade: { debug: false } }, + scene: { preload, create, update } +}; + +const game = new Phaser.Game(config); +let agents = []; +let selectedAgent = null; + +function preload() { + // Create simple colored rectangles as placeholder sprites + this.load.image('floor', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAMklEQVRYR+3QQREAAAjDsM5/6UjAWx4TJHD7TJIk6QusAQMGDBgwYMCAAQMGDBj4Z+ABGLwAIRkLhPAAAAAASUVORK5CYII='); +} + +function create() { + // Draw simple grid floor + for (let x = 0; x < 800; x += 32) { + for (let y = 0; y < 600; y += 32) { + this.add.rectangle(x + 16, y + 16, 30, 30, (x + y) % 64 === 0 ? 0x2d2d44 : 0x1f1f35); + } + } + + // Add desks + const desks = [ + { x: 150, y: 150 }, { x: 300, y: 150 }, { x: 450, y: 150 }, + { x: 150, y: 350 }, { x: 300, y: 350 }, { x: 450, y: 350 }, + ]; + desks.forEach(d => { + this.add.rectangle(d.x, d.y, 60, 40, 0x4a4a6a); + }); + + // Server room + this.add.rectangle(700, 100, 150, 150, 0x0f3460); + this.add.text(640, 30, 'SERVERS', { fontSize: '12px', fill: '#e94560' }); + for (let i = 0; i < 3; i++) { + this.add.rectangle(680 + i * 25, 80, 20, 40, 0x16213e).setStrokeStyle(1, 0xe94560); + // Blinking lights + const light = this.add.circle(680 + i * 25, 65, 3, 0x00ff00); + this.tweens.add({ targets: light, alpha: 0.3, duration: 500 + i * 200, yoyo: true, repeat: -1 }); + } + + // Meeting room + this.add.rectangle(700, 450, 150, 200, 0x1a1a2e).setStrokeStyle(2, 0x4a4a6a); + this.add.rectangle(700, 450, 80, 50, 0x4a4a6a); + this.add.text(650, 360, 'MEETING', { fontSize: '12px', fill: '#4a4a6a' }); + + // Create agents + const agentData = [ + { id: 1, name: 'Lucidia-Core', color: 0xe94560, x: 150, y: 130, role: 'Chief Intelligence' }, + { id: 2, name: 'Alice', color: 0x00d9ff, x: 300, y: 130, role: 'Gateway Agent' }, + { id: 3, name: 'Octavia', color: 0x00ff88, x: 450, y: 130, role: 'Compute Worker' }, + { id: 4, name: 'Prism', color: 0xffa500, x: 150, y: 330, role: 'Analytics' }, + { id: 5, name: 'Echo', color: 0xff00ff, x: 300, y: 330, role: 'Memory Systems' }, + { id: 6, name: 'Cipher', color: 0xffff00, x: 450, y: 330, role: 'Security' }, + ]; + + agentData.forEach(a => { + const agent = this.add.circle(a.x, a.y, 12, a.color); + agent.setInteractive({ useHandCursor: true }); + agent.setData('info', a); + + // Name label + const label = this.add.text(a.x, a.y + 20, a.name, { fontSize: '10px', fill: '#ffffff' }).setOrigin(0.5); + + // Idle movement + this.tweens.add({ + targets: [agent, label], + x: a.x + Phaser.Math.Between(-20, 20), + y: a.y + Phaser.Math.Between(-10, 10), + duration: 2000 + Math.random() * 2000, + yoyo: true, + repeat: -1, + ease: 'Sine.easeInOut' + }); + + agent.on('pointerdown', () => selectAgent(a)); + agents.push({ sprite: agent, label, data: a }); + }); + + // Title + this.add.text(20, 20, 'BLACKROAD OS, INC.', { fontSize: '20px', fill: '#e94560', fontStyle: 'bold' }); + this.add.text(20, 45, 'Agent Operations Center', { fontSize: '12px', fill: '#4a4a6a' }); + + // Chat input handler + document.getElementById('chat-input').addEventListener('keydown', async (e) => { + if (e.key === 'Enter' && selectedAgent) { + const msg = e.target.value; + e.target.value = ''; + addChatMessage('You', msg); + + // Simulate agent response (replace with real API call) + setTimeout(() => { + const responses = [ + `Processing request through ${selectedAgent.role} systems...`, + `Acknowledged. ${selectedAgent.name} is analyzing.`, + `Running inference. Stand by.`, + `Query received. Routing through event bus.`, + ]; + addChatMessage(selectedAgent.name, responses[Math.floor(Math.random() * responses.length)]); + }, 500 + Math.random() * 1000); + } + }); +} + +function selectAgent(agent) { + selectedAgent = agent; + const chat = document.getElementById('chat'); + const log = document.getElementById('chat-log'); + chat.style.display = 'block'; + log.innerHTML = ''; + addChatMessage('System', `Connected to ${agent.name} (${agent.role})`); + document.getElementById('chat-input').focus(); +} + +function addChatMessage(sender, text) { + const log = document.getElementById('chat-log'); + log.innerHTML += `
    ${sender}: ${text}
    `; + log.scrollTop = log.scrollHeight; +} + +function update() { + // Future: agent pathfinding, tasks, etc. +} diff --git a/blackroad-os/index.html b/blackroad-os/index.html new file mode 100644 index 0000000000..24f7d1c7aa --- /dev/null +++ b/blackroad-os/index.html @@ -0,0 +1,92 @@ + + + + + BlackRoad OS - Pixel Library + + + +

    BLACKROAD OS - PIXEL LIBRARY

    +
    + + + + + + + + + + + + + + + diff --git a/blackroad-os/lib/agent.js b/blackroad-os/lib/agent.js new file mode 100644 index 0000000000..867cf24be8 --- /dev/null +++ b/blackroad-os/lib/agent.js @@ -0,0 +1,152 @@ +// BLACKROAD OS - AGENT SPRITES +// 16x32 base sprite size + +const AGENT = { + palette: { + skin: { + light: '#ffdbac', + lightShade: '#e8c090', + medium: '#c68642', + mediumShade: '#a06830', + dark: '#8d5524', + darkShade: '#704018' + }, + hair: { + black: '#1a1a1a', + brown: '#5a3a1a', + blonde: '#d4a840', + red: '#8b3a1a', + gray: '#808080', + white: '#e0e0e0' + }, + eyes: { + blue: '#4a8fea', + green: '#4a9a5a', + brown: '#6a4a2a', + gray: '#606060' + } + }, + + // Draw agent sprite + // facing: 'down', 'up', 'left', 'right' + // frame: 0-3 for walk cycle, 0 for idle + draw: function(ctx, x, y, config, frame, time) { + const skin = this.palette.skin[config.skin] || this.palette.skin.light; + const skinShade = this.palette.skin[config.skin + 'Shade'] || this.palette.skin.lightShade; + const hair = this.palette.hair[config.hair] || this.palette.hair.black; + const shirt = config.shirt || '#4a6fa5'; + const shirtDark = config.shirtDark || '#3a5580'; + const eyes = this.palette.eyes[config.eyes] || this.palette.eyes.blue; + + const bounce = Math.sin(time / 8) * 1.5; + const breathe = Math.sin(time / 20) * 0.5; + const yy = y + (frame === 0 ? bounce : 0); + + // Shadow + ctx.fillStyle = 'rgba(0,0,0,0.2)'; + ctx.fillRect(x + 2, y + 30, 14, 4); + + // Feet + ctx.fillStyle = '#2a2a2a'; + ctx.fillRect(x + 2, yy + 26, 5, 4); + ctx.fillRect(x + 10, yy + 26, 5, 4); + + // Legs + ctx.fillStyle = '#3a3a50'; + if (frame === 1) { + ctx.fillRect(x + 3, yy + 20, 4, 8); + ctx.fillRect(x + 10, yy + 18, 4, 10); + } else if (frame === 3) { + ctx.fillRect(x + 3, yy + 18, 4, 10); + ctx.fillRect(x + 10, yy + 20, 4, 8); + } else { + ctx.fillRect(x + 3, yy + 18, 4, 10); + ctx.fillRect(x + 10, yy + 18, 4, 10); + } + + // Body + ctx.fillStyle = shirt; + ctx.fillRect(x + 1, yy + 8 + breathe, 15, 12); + ctx.fillStyle = shirtDark; + ctx.fillRect(x + 3, yy + 10 + breathe, 11, 8); + + // Arms + ctx.fillStyle = shirt; + if (frame === 1) { + ctx.fillRect(x - 2, yy + 8 + breathe, 4, 10); + ctx.fillRect(x + 15, yy + 10 + breathe, 4, 8); + } else if (frame === 3) { + ctx.fillRect(x - 2, yy + 10 + breathe, 4, 8); + ctx.fillRect(x + 15, yy + 8 + breathe, 4, 10); + } else { + ctx.fillRect(x - 2, yy + 9 + breathe, 4, 9); + ctx.fillRect(x + 15, yy + 9 + breathe, 4, 9); + } + + // Hands + ctx.fillStyle = skin; + ctx.fillRect(x - 1, yy + 17, 3, 3); + ctx.fillRect(x + 15, yy + 17, 3, 3); + + // Head + ctx.fillStyle = skin; + ctx.fillRect(x + 3, yy, 11, 10); + ctx.fillStyle = skinShade; + ctx.fillRect(x + 4, yy + 2, 9, 6); + + // Hair + ctx.fillStyle = hair; + ctx.fillRect(x + 2, yy - 3, 13, 5); + ctx.fillRect(x + 2, yy, 3, 4); + ctx.fillRect(x + 12, yy, 3, 4); + if (config.hairLong) { + ctx.fillRect(x, yy - 2, 3, 14); + ctx.fillRect(x + 14, yy - 2, 3, 14); + } + + // Face + const blink = Math.floor(time / 25) % 30 === 0; + if (!blink) { + // Eyes + ctx.fillStyle = '#fff'; + ctx.fillRect(x + 5, yy + 3, 3, 3); + ctx.fillRect(x + 10, yy + 3, 3, 3); + ctx.fillStyle = eyes; + ctx.fillRect(x + 6, yy + 4, 2, 2); + ctx.fillRect(x + 11, yy + 4, 2, 2); + } else { + ctx.fillStyle = skinShade; + ctx.fillRect(x + 5, yy + 5, 3, 1); + ctx.fillRect(x + 10, yy + 5, 3, 1); + } + + // Mouth + ctx.fillStyle = skinShade; + ctx.fillRect(x + 7, yy + 8, 3, 1); + }, + + // Draw name badge below agent + nameBadge: function(ctx, x, y, name, color) { + ctx.font = '6px "Press Start 2P", monospace'; + const width = ctx.measureText(name).width + 6; + + ctx.fillStyle = 'rgba(0,0,0,0.8)'; + ctx.fillRect(x + 8 - width/2, y + 32, width, 10); + ctx.fillStyle = color || '#4a6fa5'; + ctx.fillRect(x + 8 - width/2, y + 32, width, 2); + ctx.fillStyle = '#fff'; + ctx.fillText(name, x + 11 - width/2, y + 39); + }, + + // Predefined agents + presets: { + LUCIDIA: { skin: 'light', hair: 'black', hairLong: true, eyes: 'blue', shirt: '#e94560', shirtDark: '#c73050' }, + ALICE: { skin: 'light', hair: 'blonde', eyes: 'green', shirt: '#00d9ff', shirtDark: '#00a8cc' }, + OCTAVIA: { skin: 'dark', hair: 'black', eyes: 'brown', shirt: '#00cc66', shirtDark: '#00994d' }, + PRISM: { skin: 'light', hair: 'brown', eyes: 'blue', shirt: '#ff8c00', shirtDark: '#cc7000' }, + ECHO: { skin: 'light', hair: 'red', hairLong: true, eyes: 'gray', shirt: '#cc44cc', shirtDark: '#993399' }, + CIPHER: { skin: 'medium', hair: 'black', eyes: 'brown', shirt: '#aaaa00', shirtDark: '#888800' } + } +}; + +if (typeof module !== 'undefined') module.exports = AGENT; diff --git a/blackroad-os/lib/chair.js b/blackroad-os/lib/chair.js new file mode 100644 index 0000000000..ebae25cf2a --- /dev/null +++ b/blackroad-os/lib/chair.js @@ -0,0 +1,130 @@ +// BLACKROAD OS - OFFICE CHAIR +// 16x16 grid aligned + +const CHAIR = { + palette: { + fabric: { + blue: '#4a6fa5', + blueDark: '#3a5580', + blueLight: '#6090c0', + red: '#a54a4a', + redDark: '#803a3a', + gray: '#606060', + grayDark: '#404040' + }, + frame: { + black: '#1a1a1a', + dark: '#2a2a2a', + metal: '#555555' + } + }, + + // Office chair facing up (back visible) - 24x36 + facingUp: function(ctx, x, y, color) { + const f = this.palette.fabric; + const fr = this.palette.frame; + const main = f[color] || f.blue; + const dark = f[color + 'Dark'] || f.blueDark; + const light = f[color + 'Light'] || f.blueLight; + + // Shadow + ctx.fillStyle = 'rgba(0,0,0,0.2)'; + ctx.fillRect(x + 2, y + 34, 20, 4); + + // Wheel base + ctx.fillStyle = fr.black; + ctx.fillRect(x + 4, y + 32, 16, 4); + ctx.fillRect(x + 2, y + 34, 4, 3); + ctx.fillRect(x + 18, y + 34, 4, 3); + + // Stem + ctx.fillStyle = fr.metal; + ctx.fillRect(x + 10, y + 26, 4, 8); + + // Seat + ctx.fillStyle = main; + ctx.fillRect(x + 2, y + 16, 20, 12); + ctx.fillStyle = dark; + ctx.fillRect(x + 4, y + 18, 16, 8); + + // Back + ctx.fillStyle = main; + ctx.fillRect(x + 4, y, 16, 18); + ctx.fillStyle = dark; + ctx.fillRect(x + 6, y + 2, 12, 14); + // Highlight + ctx.fillStyle = light; + ctx.fillRect(x + 6, y + 2, 4, 10); + + // Armrests + ctx.fillStyle = fr.dark; + ctx.fillRect(x, y + 14, 4, 10); + ctx.fillRect(x + 20, y + 14, 4, 10); + }, + + // Office chair facing down (front visible) - 24x32 + facingDown: function(ctx, x, y, color) { + const f = this.palette.fabric; + const fr = this.palette.frame; + const main = f[color] || f.blue; + const dark = f[color + 'Dark'] || f.blueDark; + + // Shadow + ctx.fillStyle = 'rgba(0,0,0,0.2)'; + ctx.fillRect(x + 2, y + 30, 20, 4); + + // Wheel base + ctx.fillStyle = fr.black; + ctx.fillRect(x + 4, y + 28, 16, 4); + ctx.fillRect(x + 2, y + 30, 4, 3); + ctx.fillRect(x + 18, y + 30, 4, 3); + + // Stem + ctx.fillStyle = fr.metal; + ctx.fillRect(x + 10, y + 22, 4, 8); + + // Seat (more visible from front) + ctx.fillStyle = main; + ctx.fillRect(x + 2, y + 12, 20, 12); + ctx.fillStyle = dark; + ctx.fillRect(x + 4, y + 14, 16, 8); + + // Armrests + ctx.fillStyle = fr.dark; + ctx.fillRect(x, y + 10, 4, 12); + ctx.fillRect(x + 20, y + 10, 4, 12); + ctx.fillStyle = fr.metal; + ctx.fillRect(x, y + 8, 6, 4); + ctx.fillRect(x + 18, y + 8, 6, 4); + + // Back (just top visible) + ctx.fillStyle = main; + ctx.fillRect(x + 6, y, 12, 6); + }, + + // Stool (simpler) - 16x24 + stool: function(ctx, x, y) { + const fr = this.palette.frame; + + // Shadow + ctx.fillStyle = 'rgba(0,0,0,0.2)'; + ctx.fillRect(x + 2, y + 22, 12, 3); + + // Legs + ctx.fillStyle = fr.metal; + ctx.fillRect(x + 2, y + 12, 2, 12); + ctx.fillRect(x + 12, y + 12, 2, 12); + ctx.fillRect(x + 7, y + 16, 2, 8); + + // Footrest + ctx.fillRect(x, y + 18, 16, 2); + + // Seat + ctx.fillStyle = fr.black; + ctx.fillRect(x, y + 8, 16, 6); + ctx.fillStyle = fr.dark; + ctx.fillRect(x + 2, y + 9, 12, 4); + } +}; + +if (typeof module !== 'undefined') module.exports = CHAIR; diff --git a/blackroad-os/lib/desk.js b/blackroad-os/lib/desk.js new file mode 100644 index 0000000000..e0a9558f54 --- /dev/null +++ b/blackroad-os/lib/desk.js @@ -0,0 +1,154 @@ +// BLACKROAD OS - DESK & WORKSTATION +// 16x16 grid aligned + +const DESK = { + palette: { + surface: { + light: '#deb887', + mid: '#c4a060', + dark: '#a08050' + }, + metal: { + light: '#c0c0c0', + mid: '#a0a0a0', + dark: '#707070' + }, + screen: { + off: '#1a2a1a', + on: '#0a3a2a', + text: '#4aea8b', + cursor: '#7affa8' + } + }, + + // Basic desk (80x48 footprint) + basic: function(ctx, x, y) { + const s = this.palette.surface; + + // Shadow + ctx.fillStyle = 'rgba(0,0,0,0.25)'; + ctx.fillRect(x + 4, y + 44, 80, 6); + + // Desktop surface + ctx.fillStyle = s.light; + ctx.fillRect(x, y, 80, 8); + ctx.fillStyle = s.mid; + ctx.fillRect(x, y + 6, 80, 36); + ctx.fillStyle = s.dark; + ctx.fillRect(x + 2, y + 40, 76, 4); + + // Legs + ctx.fillStyle = s.dark; + ctx.fillRect(x + 4, y + 42, 10, 12); + ctx.fillRect(x + 66, y + 42, 10, 12); + + // Drawer unit + ctx.fillRect(x + 52, y + 10, 24, 28); + ctx.fillStyle = s.mid; + ctx.fillRect(x + 54, y + 12, 20, 11); + ctx.fillRect(x + 54, y + 25, 20, 11); + // Handles + ctx.fillStyle = this.palette.metal.mid; + ctx.fillRect(x + 62, y + 16, 4, 3); + ctx.fillRect(x + 62, y + 29, 4, 3); + }, + + // Monitor (32x28) + monitor: function(ctx, x, y, screenContent, time) { + const m = this.palette.metal; + const s = this.palette.screen; + + // Bezel + ctx.fillStyle = '#1a1a1a'; + ctx.fillRect(x, y, 32, 24); + ctx.fillStyle = '#222'; + ctx.fillRect(x + 2, y + 2, 28, 18); + + // Screen + ctx.fillStyle = s.on; + ctx.fillRect(x + 3, y + 3, 26, 16); + + // Content + ctx.fillStyle = s.text; + if (screenContent === 'code') { + ctx.fillRect(x + 5, y + 5, 14, 2); + ctx.fillRect(x + 5, y + 9, 20, 2); + ctx.fillRect(x + 5, y + 13, 10, 2); + // Cursor blink + if (Math.floor(time / 15) % 2) { + ctx.fillStyle = s.cursor; + ctx.fillRect(x + 16, y + 13, 2, 2); + } + } else if (screenContent === 'terminal') { + ctx.fillRect(x + 5, y + 5, 6, 2); + ctx.fillStyle = '#fff'; + ctx.fillRect(x + 12, y + 5, 8, 2); + ctx.fillStyle = s.text; + ctx.fillRect(x + 5, y + 9, 18, 2); + ctx.fillRect(x + 5, y + 13, 4, 2); + } + + // Stand + ctx.fillStyle = '#222'; + ctx.fillRect(x + 12, y + 24, 8, 4); + ctx.fillRect(x + 8, y + 26, 16, 3); + }, + + // Keyboard (24x10) + keyboard: function(ctx, x, y) { + ctx.fillStyle = '#2a2a2a'; + ctx.fillRect(x, y, 24, 10); + ctx.fillStyle = '#3a3a3a'; + ctx.fillRect(x + 1, y + 1, 22, 8); + + // Keys + ctx.fillStyle = '#4a4a4a'; + for (let row = 0; row < 3; row++) { + for (let key = 0; key < 7; key++) { + ctx.fillRect(x + 2 + key * 3, y + 2 + row * 3, 2, 2); + } + } + }, + + // Mouse (6x10) + mouse: function(ctx, x, y) { + ctx.fillStyle = '#2a2a2a'; + ctx.fillRect(x, y, 6, 10); + ctx.fillStyle = '#3a3a3a'; + ctx.fillRect(x + 1, y + 1, 4, 3); + ctx.fillStyle = '#222'; + ctx.fillRect(x + 2, y + 4, 2, 2); + }, + + // Coffee mug (10x14) + mug: function(ctx, x, y, hasSteam, time) { + // Mug body + ctx.fillStyle = '#fff'; + ctx.fillRect(x, y + 4, 8, 10); + // Handle + ctx.fillRect(x + 7, y + 6, 3, 6); + ctx.fillStyle = '#e8e8e8'; + ctx.fillRect(x + 8, y + 8, 2, 2); + // Coffee + ctx.fillStyle = '#3c2415'; + ctx.fillRect(x + 1, y + 5, 6, 3); + + // Steam + if (hasSteam && Math.floor(time / 10) % 3 !== 0) { + ctx.fillStyle = 'rgba(255,255,255,0.5)'; + ctx.fillRect(x + 2, y + 2 - (time % 4), 1, 2); + ctx.fillRect(x + 5, y + 1 - (time % 3), 1, 2); + } + }, + + // Complete workstation + workstation: function(ctx, x, y, time) { + this.basic(ctx, x, y); + this.monitor(ctx, x + 8, y - 26, 'code', time); + this.keyboard(ctx, x + 10, y + 6); + this.mouse(ctx, x + 38, y + 8); + this.mug(ctx, x + 2, y - 4, true, time); + } +}; + +if (typeof module !== 'undefined') module.exports = DESK; diff --git a/blackroad-os/lib/floor.js b/blackroad-os/lib/floor.js new file mode 100644 index 0000000000..33f597d83d --- /dev/null +++ b/blackroad-os/lib/floor.js @@ -0,0 +1,169 @@ +// BLACKROAD OS - FLOOR TILES +// 16x16 base tile size + +const FLOOR = { + // Color palettes + palette: { + wood: { + light: '#c9a66b', + mid: '#a68550', + dark: '#8b6914', + grain: '#7a5a30', + knot: '#5a4020' + }, + tile: { + white: '#e8e8e8', + cream: '#f5f0e0', + gray: '#a8a8a8', + grout: '#888888' + }, + carpet: { + red: '#8b3a5a', + redDark: '#6a2a4a', + blue: '#3a5a8b', + blueDark: '#2a4a6a' + }, + concrete: { + light: '#909090', + dark: '#707070', + crack: '#505050' + } + }, + + // Draw single 16x16 wood plank + woodPlank: function(ctx, x, y, variant) { + const p = this.palette.wood; + const colors = [p.light, p.mid, p.dark]; + const base = colors[variant % 3]; + + ctx.fillStyle = base; + ctx.fillRect(x, y, 16, 16); + + // Grain lines + ctx.fillStyle = p.grain; + if (variant % 2 === 0) { + ctx.fillRect(x + 2, y + 4, 10, 1); + ctx.fillRect(x + 4, y + 11, 8, 1); + } else { + ctx.fillRect(x + 3, y + 6, 9, 1); + ctx.fillRect(x + 1, y + 13, 7, 1); + } + + // Wood knot (rare) + if (variant % 7 === 0) { + ctx.fillStyle = p.knot; + ctx.fillRect(x + 6, y + 6, 4, 4); + ctx.fillStyle = p.grain; + ctx.fillRect(x + 7, y + 7, 2, 2); + } + + // Board edge + ctx.fillStyle = p.grain; + ctx.fillRect(x, y, 1, 16); + }, + + // Draw 16x16 tile floor piece + tilePiece: function(ctx, x, y, variant) { + const p = this.palette.tile; + const isWhite = variant % 2 === 0; + + ctx.fillStyle = isWhite ? p.white : p.cream; + ctx.fillRect(x, y, 16, 16); + + // Grout lines + ctx.fillStyle = p.grout; + ctx.fillRect(x, y, 16, 1); + ctx.fillRect(x, y, 1, 16); + + // Subtle shine + if (isWhite) { + ctx.fillStyle = '#ffffff'; + ctx.fillRect(x + 3, y + 3, 2, 1); + } + }, + + // Draw 16x16 carpet piece + carpetPiece: function(ctx, x, y, variant, colorScheme) { + const p = this.palette.carpet; + const isRed = colorScheme === 'red'; + const main = isRed ? p.red : p.blue; + const dark = isRed ? p.redDark : p.blueDark; + + ctx.fillStyle = main; + ctx.fillRect(x, y, 16, 16); + + // Pattern + if ((variant % 4) < 2) { + ctx.fillStyle = dark; + ctx.fillRect(x + 4, y + 4, 8, 8); + ctx.fillStyle = main; + ctx.fillRect(x + 6, y + 6, 4, 4); + } + }, + + // Draw 16x16 concrete piece + concretePiece: function(ctx, x, y, variant) { + const p = this.palette.concrete; + + ctx.fillStyle = variant % 2 ? p.light : p.dark; + ctx.fillRect(x, y, 16, 16); + + // Cracks + if (variant % 5 === 0) { + ctx.fillStyle = p.crack; + ctx.fillRect(x + 2, y + 8, 6, 1); + ctx.fillRect(x + 7, y + 8, 1, 4); + } + + // Speckles + ctx.fillStyle = p.crack; + ctx.fillRect(x + 3, y + 3, 1, 1); + ctx.fillRect(x + 10, y + 7, 1, 1); + ctx.fillRect(x + 6, y + 12, 1, 1); + }, + + // Fill area with wood floor + fillWood: function(ctx, startX, startY, tilesWide, tilesHigh) { + for (let y = 0; y < tilesHigh; y++) { + for (let x = 0; x < tilesWide; x++) { + const variant = (x + y * 3) % 7; + this.woodPlank(ctx, startX + x * 16, startY + y * 16, variant); + } + } + }, + + // Fill area with tile floor + fillTile: function(ctx, startX, startY, tilesWide, tilesHigh) { + for (let y = 0; y < tilesHigh; y++) { + for (let x = 0; x < tilesWide; x++) { + this.tilePiece(ctx, startX + x * 16, startY + y * 16, x + y); + } + } + }, + + // Fill area with carpet + fillCarpet: function(ctx, startX, startY, tilesWide, tilesHigh, colorScheme) { + for (let y = 0; y < tilesHigh; y++) { + for (let x = 0; x < tilesWide; x++) { + this.carpetPiece(ctx, startX + x * 16, startY + y * 16, x + y, colorScheme || 'red'); + } + } + // Border fringe + ctx.fillStyle = this.palette.carpet[colorScheme === 'blue' ? 'blueDark' : 'redDark']; + for (let x = 0; x < tilesWide * 16; x += 4) { + ctx.fillRect(startX + x, startY - 2, 3, 2); + ctx.fillRect(startX + x, startY + tilesHigh * 16, 3, 2); + } + }, + + // Fill area with concrete + fillConcrete: function(ctx, startX, startY, tilesWide, tilesHigh) { + for (let y = 0; y < tilesHigh; y++) { + for (let x = 0; x < tilesWide; x++) { + this.concretePiece(ctx, startX + x * 16, startY + y * 16, x * 3 + y * 7); + } + } + } +}; + +if (typeof module !== 'undefined') module.exports = FLOOR; diff --git a/blackroad-os/lib/furniture.js b/blackroad-os/lib/furniture.js new file mode 100644 index 0000000000..42de269379 --- /dev/null +++ b/blackroad-os/lib/furniture.js @@ -0,0 +1,238 @@ +// BLACKROAD OS - FURNITURE +// Couch, tables, shelves + +const FURNITURE = { + palette: { + couch: { + blue: '#5a5a8a', + blueDark: '#4a4a7a', + blueLight: '#6a6a9a', + gray: '#6a6a6a', + grayDark: '#5a5a5a' + }, + wood: { + light: '#daa86a', + mid: '#c4935a', + dark: '#a67c3d' + }, + metal: { + light: '#c0c0c0', + mid: '#a0a0a0', + dark: '#707070' + } + }, + + // Couch (72x44) + couch: function(ctx, x, y, color) { + const c = this.palette.couch; + const main = c[color] || c.blue; + const dark = c[color + 'Dark'] || c.blueDark; + const light = c[color + 'Light'] || c.blueLight; + + // Shadow + ctx.fillStyle = 'rgba(0,0,0,0.25)'; + ctx.fillRect(x + 2, y + 42, 70, 5); + + // Base + ctx.fillStyle = main; + ctx.fillRect(x, y + 12, 72, 32); + ctx.fillStyle = dark; + ctx.fillRect(x + 4, y + 16, 64, 24); + + // Back + ctx.fillStyle = main; + ctx.fillRect(x, y, 72, 16); + ctx.fillStyle = dark; + ctx.fillRect(x + 4, y + 4, 64, 8); + + // Arms + ctx.fillStyle = main; + ctx.fillRect(x - 4, y + 4, 8, 40); + ctx.fillRect(x + 68, y + 4, 8, 40); + + // Cushion divisions + ctx.fillStyle = dark; + ctx.fillRect(x + 24, y + 16, 2, 24); + ctx.fillRect(x + 46, y + 16, 2, 24); + + // Highlights + ctx.fillStyle = light; + ctx.fillRect(x + 6, y + 18, 16, 4); + ctx.fillRect(x + 28, y + 18, 16, 4); + ctx.fillRect(x + 50, y + 18, 16, 4); + }, + + // Coffee table (56x28) + coffeeTable: function(ctx, x, y) { + const w = this.palette.wood; + + // Shadow + ctx.fillStyle = 'rgba(0,0,0,0.2)'; + ctx.fillRect(x + 3, y + 26, 52, 4); + + // Top + ctx.fillStyle = w.light; + ctx.fillRect(x, y, 56, 8); + ctx.fillStyle = w.mid; + ctx.fillRect(x, y + 6, 56, 18); + + // Legs + ctx.fillStyle = w.dark; + ctx.fillRect(x + 4, y + 22, 8, 8); + ctx.fillRect(x + 44, y + 22, 8, 8); + + // Shelf + ctx.fillStyle = w.mid; + ctx.fillRect(x + 6, y + 16, 44, 4); + }, + + // Bookshelf (36x72) + bookshelf: function(ctx, x, y) { + const w = this.palette.wood; + const bookColors = ['#e94560', '#4a9fea', '#4a9a5a', '#ffa500', '#aa5aaa', '#ea4a4a']; + + // Shadow + ctx.fillStyle = 'rgba(0,0,0,0.25)'; + ctx.fillRect(x + 3, y + 70, 34, 5); + + // Frame + ctx.fillStyle = w.dark; + ctx.fillRect(x, y, 36, 72); + + // Shelves + for (let s = 0; s < 4; s++) { + // Shelf back + ctx.fillStyle = w.mid; + ctx.fillRect(x + 2, y + 2 + s * 18, 32, 16); + // Shelf board + ctx.fillStyle = w.dark; + ctx.fillRect(x + 2, y + 16 + s * 18, 32, 3); + + // Books + let bx = x + 4; + for (let b = 0; b < 5; b++) { + if ((s * 5 + b) % 6 !== 0) { + const bw = 4 + (b % 3); + const bh = 10 + (s + b) % 5; + ctx.fillStyle = bookColors[(s + b) % 6]; + ctx.fillRect(bx, y + 4 + s * 18 + (14 - bh), bw, bh); + bx += bw + 1; + } + } + } + }, + + // Filing cabinet (32x64) + filingCabinet: function(ctx, x, y) { + const m = this.palette.metal; + + // Shadow + ctx.fillStyle = 'rgba(0,0,0,0.2)'; + ctx.fillRect(x + 3, y + 62, 28, 4); + + // Body + ctx.fillStyle = m.dark; + ctx.fillRect(x, y, 32, 64); + ctx.fillStyle = m.mid; + ctx.fillRect(x + 2, y + 2, 28, 60); + + // Drawers + for (let i = 0; i < 3; i++) { + ctx.fillStyle = m.dark; + ctx.fillRect(x + 4, y + 4 + i * 20, 24, 18); + ctx.fillStyle = m.mid; + ctx.fillRect(x + 5, y + 5 + i * 20, 22, 16); + // Handle + ctx.fillStyle = m.dark; + ctx.fillRect(x + 12, y + 11 + i * 20, 8, 4); + } + + // Label + ctx.fillStyle = '#fffef0'; + ctx.fillRect(x + 8, y + 6, 10, 6); + }, + + // Water cooler (28x56) + waterCooler: function(ctx, x, y, time) { + const m = this.palette.metal; + + // Shadow + ctx.fillStyle = 'rgba(0,0,0,0.2)'; + ctx.fillRect(x + 3, y + 54, 24, 4); + + // Base + ctx.fillStyle = m.dark; + ctx.fillRect(x + 4, y + 44, 20, 12); + ctx.fillStyle = m.mid; + ctx.fillRect(x + 6, y + 46, 16, 8); + + // Body + ctx.fillStyle = m.mid; + ctx.fillRect(x + 2, y + 16, 24, 30); + ctx.fillStyle = m.light; + ctx.fillRect(x + 4, y + 18, 20, 26); + + // Water jug + ctx.fillStyle = '#a8d8ea'; + ctx.fillRect(x + 6, y - 8, 16, 26); + ctx.fillStyle = '#88b8ca'; + ctx.fillRect(x + 8, y - 6, 12, 22); + + // Water level + const level = 16 + Math.sin(time / 30) * 2; + ctx.fillStyle = '#4a9cdc'; + ctx.fillRect(x + 8, y + 16 - level, 12, level - 4); + + // Tap + ctx.fillStyle = '#666'; + ctx.fillRect(x + 12, y + 34, 6, 4); + ctx.fillRect(x + 14, y + 38, 2, 4); + + // Cup holder + ctx.fillStyle = m.mid; + ctx.fillRect(x + 22, y + 20, 6, 12); + ctx.fillStyle = '#fff'; + ctx.fillRect(x + 23, y + 28, 4, 5); + }, + + // Vending machine (40x72) + vendingMachine: function(ctx, x, y) { + // Shadow + ctx.fillStyle = 'rgba(0,0,0,0.25)'; + ctx.fillRect(x + 3, y + 70, 38, 5); + + // Body + ctx.fillStyle = '#2a4a6a'; + ctx.fillRect(x, y, 40, 72); + ctx.fillStyle = '#1a3050'; + ctx.fillRect(x + 2, y + 2, 36, 50); + + // Glass front + ctx.fillStyle = '#3a5a7a'; + ctx.fillRect(x + 4, y + 4, 32, 46); + + // Items + const colors = ['#e94560', '#4a9', '#fa0', '#4ae', '#a4e', '#f80']; + for (let row = 0; row < 3; row++) { + // Shelf + ctx.fillStyle = this.palette.metal.mid; + ctx.fillRect(x + 4, y + 6 + row * 15, 32, 2); + for (let col = 0; col < 4; col++) { + ctx.fillStyle = colors[(row * 4 + col) % 6]; + ctx.fillRect(x + 6 + col * 8, y + 8 + row * 15, 6, 12); + } + } + + // Coin slot + ctx.fillStyle = this.palette.metal.dark; + ctx.fillRect(x + 30, y + 54, 6, 8); + ctx.fillStyle = '#111'; + ctx.fillRect(x + 32, y + 56, 2, 4); + + // Dispenser + ctx.fillStyle = '#111'; + ctx.fillRect(x + 4, y + 54, 24, 16); + } +}; + +if (typeof module !== 'undefined') module.exports = FURNITURE; diff --git a/blackroad-os/lib/lighting.js b/blackroad-os/lib/lighting.js new file mode 100644 index 0000000000..bfbfd45a24 --- /dev/null +++ b/blackroad-os/lib/lighting.js @@ -0,0 +1,195 @@ +// BLACKROAD OS - LIGHTING +// Ceiling lights, lamps, windows + +const LIGHTING = { + palette: { + fixture: { + white: '#e8e8e8', + cream: '#f0e8d8', + metal: '#a0a0a0', + metalDark: '#707070', + chain: '#888888' + }, + glow: { + warm: 'rgba(255,240,200,', + cool: 'rgba(200,240,255,', + white: 'rgba(255,255,255,' + }, + window: { + frame: '#c4935a', + frameDark: '#a67c3d', + glass: '#87ceeb', + sky: '#6cacdc', + skyLight: '#a0d8f0' + }, + curtain: { + red: '#a05050', + redDark: '#803838', + blue: '#5050a0', + blueDark: '#383880' + } + }, + + // Ceiling light (32x20) + ceilingLight: function(ctx, x, y, time, on) { + const f = this.palette.fixture; + + // Chain + ctx.fillStyle = f.chain; + ctx.fillRect(x + 14, y, 4, 8); + + // Fixture + ctx.fillStyle = f.metalDark; + ctx.fillRect(x, y + 8, 32, 6); + ctx.fillStyle = f.metal; + ctx.fillRect(x + 2, y + 10, 28, 2); + + // Shade + ctx.fillStyle = f.cream; + ctx.fillRect(x + 4, y + 14, 24, 4); + + if (on !== false) { + // Bulbs + ctx.fillStyle = '#fffde0'; + ctx.fillRect(x + 8, y + 16, 6, 4); + ctx.fillRect(x + 18, y + 16, 6, 4); + + // Glow + const pulse = 0.1 + Math.sin(time / 20) * 0.02; + ctx.fillStyle = this.palette.glow.warm + pulse + ')'; + ctx.beginPath(); + ctx.arc(x + 16, y + 40, 50, 0, Math.PI * 2); + ctx.fill(); + } + }, + + // Desk lamp (16x24) + deskLamp: function(ctx, x, y, on) { + const f = this.palette.fixture; + + // Base + ctx.fillStyle = f.metalDark; + ctx.fillRect(x + 2, y + 20, 12, 4); + + // Stem + ctx.fillStyle = f.metal; + ctx.fillRect(x + 6, y + 8, 4, 14); + + // Shade + ctx.fillStyle = f.metalDark; + ctx.fillRect(x, y, 16, 10); + ctx.fillStyle = f.metal; + ctx.fillRect(x + 1, y + 1, 14, 8); + + if (on !== false) { + ctx.fillStyle = '#fffde0'; + ctx.fillRect(x + 2, y + 8, 12, 3); + + // Small glow + ctx.fillStyle = this.palette.glow.warm + '0.15)'; + ctx.fillRect(x - 8, y + 10, 32, 20); + } + }, + + // Window (64x48) + window: function(ctx, x, y, time, hasCurtains) { + const w = this.palette.window; + const c = this.palette.curtain; + + // Frame shadow + ctx.fillStyle = w.frameDark; + ctx.fillRect(x - 6, y - 6, 76, 60); + + // Frame + ctx.fillStyle = w.frame; + ctx.fillRect(x - 4, y - 4, 72, 56); + + // Glass/sky + ctx.fillStyle = w.sky; + ctx.fillRect(x, y, 64, 48); + + // Sky gradient + for (let i = 0; i < 48; i += 8) { + const alpha = 0.3 - (i / 48) * 0.2; + ctx.fillStyle = 'rgba(255,255,255,' + alpha + ')'; + ctx.fillRect(x, y + i, 64, 8); + } + + // Clouds + const cloudX = ((time / 2) % 84) - 20; + ctx.fillStyle = 'rgba(255,255,255,0.8)'; + ctx.beginPath(); + ctx.arc(x + cloudX, y + 14, 8, 0, Math.PI * 2); + ctx.arc(x + cloudX + 10, y + 12, 10, 0, Math.PI * 2); + ctx.arc(x + cloudX + 20, y + 14, 7, 0, Math.PI * 2); + ctx.fill(); + + // Window cross frame + ctx.fillStyle = w.frame; + ctx.fillRect(x + 30, y, 4, 48); + ctx.fillRect(x, y + 22, 64, 4); + + // Shine + ctx.fillStyle = 'rgba(255,255,255,0.4)'; + ctx.fillRect(x + 4, y + 4, 10, 3); + + // Curtains + if (hasCurtains !== false) { + ctx.fillStyle = c.red; + ctx.fillRect(x - 8, y - 6, 10, 58); + ctx.fillRect(x + 62, y - 6, 10, 58); + + // Folds + for (let i = 0; i < 8; i++) { + ctx.fillStyle = c.redDark; + ctx.fillRect(x - 8, y - 6 + i * 7, 10, 2); + ctx.fillRect(x + 62, y - 6 + i * 7, 10, 2); + } + + // Ties + ctx.fillStyle = '#d4aa00'; + ctx.fillRect(x - 6, y + 20, 6, 8); + ctx.fillRect(x + 64, y + 20, 6, 8); + } + + // Light beam effect + ctx.fillStyle = this.palette.glow.warm + '0.05)'; + ctx.beginPath(); + ctx.moveTo(x, y + 48); + ctx.lineTo(x + 64, y + 48); + ctx.lineTo(x + 100, y + 120); + ctx.lineTo(x - 36, y + 120); + ctx.fill(); + }, + + // Wall sconce (12x20) + sconce: function(ctx, x, y, on) { + const f = this.palette.fixture; + + // Mount + ctx.fillStyle = f.metalDark; + ctx.fillRect(x + 2, y, 8, 6); + + // Arm + ctx.fillStyle = f.metal; + ctx.fillRect(x + 4, y + 4, 4, 8); + + // Shade + ctx.fillStyle = f.cream; + ctx.fillRect(x, y + 10, 12, 10); + ctx.fillStyle = '#d8d0c0'; + ctx.fillRect(x + 1, y + 11, 10, 8); + + if (on !== false) { + ctx.fillStyle = '#fffde0'; + ctx.fillRect(x + 3, y + 18, 6, 3); + + ctx.fillStyle = this.palette.glow.warm + '0.1)'; + ctx.beginPath(); + ctx.arc(x + 6, y + 24, 20, 0, Math.PI * 2); + ctx.fill(); + } + } +}; + +if (typeof module !== 'undefined') module.exports = LIGHTING; diff --git a/blackroad-os/lib/plant.js b/blackroad-os/lib/plant.js new file mode 100644 index 0000000000..01e1fef71f --- /dev/null +++ b/blackroad-os/lib/plant.js @@ -0,0 +1,177 @@ +// BLACKROAD OS - PLANTS & DECORATIONS +// Various sizes + +const PLANT = { + palette: { + leaf: { + green: '#5a9c5a', + greenLight: '#7abc6a', + greenDark: '#3a6c3a', + fern: '#4a8c4a', + fernLight: '#6aac5a' + }, + pot: { + terra: '#cd853f', + terraDark: '#a0522d', + terraLight: '#d88050', + white: '#e8e8e8', + whiteDark: '#c8c8c8', + blue: '#4a6a8c', + blueDark: '#3a5070' + }, + soil: '#4a3020' + }, + + // Small potted plant (16x24) + small: function(ctx, x, y, time, potColor) { + const l = this.palette.leaf; + const p = this.palette.pot; + const pot = p[potColor] || p.terra; + const potDark = p[potColor + 'Dark'] || p.terraDark; + + const sway = Math.sin(time / 25 + x * 0.1) * 1; + + // Shadow + ctx.fillStyle = 'rgba(0,0,0,0.2)'; + ctx.fillRect(x + 2, y + 22, 14, 3); + + // Pot + ctx.fillStyle = pot; + ctx.fillRect(x + 2, y + 14, 14, 10); + ctx.fillStyle = potDark; + ctx.fillRect(x + 4, y + 16, 10, 6); + ctx.fillStyle = pot; + ctx.fillRect(x, y + 12, 18, 4); + + // Soil + ctx.fillStyle = this.palette.soil; + ctx.fillRect(x + 4, y + 13, 10, 3); + + // Stem + ctx.fillStyle = l.greenDark; + ctx.fillRect(x + 7 + sway, y + 4, 3, 10); + + // Leaves + ctx.fillStyle = l.green; + ctx.fillRect(x + 2 + sway, y + 2, 6, 6); + ctx.fillRect(x + 10 + sway, y, 6, 7); + ctx.fillRect(x + 5 + sway, y - 4, 5, 5); + ctx.fillStyle = l.greenLight; + ctx.fillRect(x + 3 + sway, y + 3, 3, 3); + ctx.fillRect(x + 11 + sway, y + 1, 3, 3); + }, + + // Large floor plant (24x40) + large: function(ctx, x, y, time) { + const l = this.palette.leaf; + const p = this.palette.pot; + const sway = Math.sin(time / 30 + x * 0.05) * 2; + + // Shadow + ctx.fillStyle = 'rgba(0,0,0,0.25)'; + ctx.fillRect(x + 2, y + 38, 22, 4); + + // Pot + ctx.fillStyle = p.terra; + ctx.fillRect(x + 2, y + 24, 22, 16); + ctx.fillStyle = p.terraDark; + ctx.fillRect(x + 4, y + 28, 18, 10); + ctx.fillStyle = p.terraLight; + ctx.fillRect(x, y + 20, 26, 6); + + // Soil + ctx.fillStyle = this.palette.soil; + ctx.fillRect(x + 4, y + 22, 18, 4); + + // Main stems + ctx.fillStyle = l.greenDark; + ctx.fillRect(x + 10 + sway, y + 4, 4, 20); + ctx.fillRect(x + 6 + sway, y + 8, 3, 14); + ctx.fillRect(x + 15 + sway, y + 6, 3, 16); + + // Leaves - multiple layers + ctx.fillStyle = l.greenDark; + ctx.fillRect(x + sway, y + 4, 10, 8); + ctx.fillRect(x + 16 + sway, y + 2, 10, 10); + + ctx.fillStyle = l.green; + ctx.fillRect(x + 4 + sway, y - 2, 8, 10); + ctx.fillRect(x + 12 + sway, y - 4, 10, 12); + ctx.fillRect(x + 2 + sway, y + 6, 6, 6); + ctx.fillRect(x + 18 + sway, y + 4, 6, 8); + + ctx.fillStyle = l.greenLight; + ctx.fillRect(x + 6 + sway, y, 5, 6); + ctx.fillRect(x + 14 + sway, y - 2, 6, 6); + ctx.fillRect(x + 8 + sway, y - 6, 6, 5); + }, + + // Hanging plant (20x32) + hanging: function(ctx, x, y, time) { + const l = this.palette.leaf; + const sway = Math.sin(time / 20) * 2; + + // Hanger chain + ctx.fillStyle = '#888'; + ctx.fillRect(x + 9, y, 2, 8); + + // Pot (smaller, white) + ctx.fillStyle = '#e8e8e8'; + ctx.fillRect(x + 4, y + 8, 12, 8); + ctx.fillStyle = '#c8c8c8'; + ctx.fillRect(x + 5, y + 10, 10, 4); + + // Trailing vines + ctx.fillStyle = l.greenDark; + ctx.fillRect(x + 6 + sway, y + 14, 2, 12); + ctx.fillRect(x + 12 - sway, y + 14, 2, 16); + ctx.fillRect(x + 2 + sway * 0.5, y + 16, 2, 10); + + // Leaves on vines + ctx.fillStyle = l.green; + ctx.fillRect(x + 4 + sway, y + 18, 4, 3); + ctx.fillRect(x + 10 - sway, y + 22, 4, 3); + ctx.fillRect(x + sway * 0.5, y + 20, 4, 3); + ctx.fillRect(x + 12 - sway, y + 28, 4, 3); + ctx.fillStyle = l.greenLight; + ctx.fillRect(x + 5 + sway, y + 16, 2, 2); + ctx.fillRect(x + 11 - sway, y + 20, 2, 2); + }, + + // Cactus (12x20) + cactus: function(ctx, x, y) { + const p = this.palette.pot; + + // Shadow + ctx.fillStyle = 'rgba(0,0,0,0.2)'; + ctx.fillRect(x + 1, y + 18, 10, 3); + + // Pot + ctx.fillStyle = p.terra; + ctx.fillRect(x + 1, y + 12, 10, 8); + ctx.fillStyle = p.terraDark; + ctx.fillRect(x + 2, y + 14, 8, 4); + + // Cactus body + ctx.fillStyle = '#5a8a5a'; + ctx.fillRect(x + 4, y + 2, 6, 12); + ctx.fillStyle = '#4a7a4a'; + ctx.fillRect(x + 5, y + 4, 4, 8); + + // Arms + ctx.fillStyle = '#5a8a5a'; + ctx.fillRect(x + 1, y + 4, 4, 4); + ctx.fillRect(x + 1, y + 2, 2, 4); + ctx.fillRect(x + 9, y + 6, 3, 4); + ctx.fillRect(x + 10, y + 4, 2, 4); + + // Spines (dots) + ctx.fillStyle = '#7aba7a'; + ctx.fillRect(x + 5, y + 3, 1, 1); + ctx.fillRect(x + 8, y + 5, 1, 1); + ctx.fillRect(x + 6, y + 8, 1, 1); + ctx.fillRect(x + 2, y + 3, 1, 1); + } +}; + +if (typeof module !== 'undefined') module.exports = PLANT; diff --git a/blackroad-os/lib/props.js b/blackroad-os/lib/props.js new file mode 100644 index 0000000000..95db922053 --- /dev/null +++ b/blackroad-os/lib/props.js @@ -0,0 +1,205 @@ +// BLACKROAD OS - SMALL PROPS +// Office items, decorations + +const PROPS = { + palette: { + paper: '#fffef0', + paperLines: '#ddd', + mug: { white: '#fff', handle: '#e8e8e8', coffee: '#3c2415' }, + metal: { light: '#c0c0c0', mid: '#909090', dark: '#606060' } + }, + + // Coffee mug (10x14) + mug: function(ctx, x, y, hasCoffee, time) { + ctx.fillStyle = this.palette.mug.white; + ctx.fillRect(x, y + 4, 8, 10); + ctx.fillStyle = this.palette.mug.handle; + ctx.fillRect(x + 7, y + 6, 3, 6); + ctx.fillRect(x + 8, y + 8, 2, 2); + + if (hasCoffee !== false) { + ctx.fillStyle = this.palette.mug.coffee; + ctx.fillRect(x + 1, y + 5, 6, 3); + + // Steam + if (time && Math.floor(time / 10) % 3 !== 0) { + ctx.fillStyle = 'rgba(255,255,255,0.5)'; + ctx.fillRect(x + 2, y + 2 - (time % 4), 1, 2); + ctx.fillRect(x + 5, y + 1 - (time % 3), 1, 2); + } + } + }, + + // Stack of papers (14x8) + paperStack: function(ctx, x, y) { + for (let i = 0; i < 3; i++) { + ctx.fillStyle = this.palette.paper; + ctx.fillRect(x + i, y + i * 2, 14, 6); + ctx.fillStyle = this.palette.paperLines; + ctx.fillRect(x + 2 + i, y + 2 + i * 2, 8, 1); + } + }, + + // Pencil cup (10x16) + pencilCup: function(ctx, x, y) { + ctx.fillStyle = '#4a6a8c'; + ctx.fillRect(x, y + 4, 10, 12); + ctx.fillStyle = '#3a5a7a'; + ctx.fillRect(x + 1, y + 6, 8, 8); + + // Pencils + ctx.fillStyle = '#ffd700'; + ctx.fillRect(x + 2, y - 4, 2, 10); + ctx.fillStyle = '#e94560'; + ctx.fillRect(x + 5, y - 2, 2, 8); + ctx.fillStyle = '#4a9'; + ctx.fillRect(x + 7, y - 3, 2, 9); + }, + + // Clock (32x32) + clock: function(ctx, x, y, time) { + // Frame + ctx.fillStyle = '#a67c3d'; + ctx.beginPath(); + ctx.arc(x + 16, y + 16, 16, 0, Math.PI * 2); + ctx.fill(); + + ctx.fillStyle = '#c4935a'; + ctx.beginPath(); + ctx.arc(x + 16, y + 16, 14, 0, Math.PI * 2); + ctx.fill(); + + // Face + ctx.fillStyle = '#fffef0'; + ctx.beginPath(); + ctx.arc(x + 16, y + 16, 12, 0, Math.PI * 2); + ctx.fill(); + + // Hour marks + ctx.fillStyle = '#333'; + for (let i = 0; i < 12; i++) { + const angle = (i / 12) * Math.PI * 2 - Math.PI / 2; + const hx = x + 16 + Math.cos(angle) * 10; + const hy = y + 16 + Math.sin(angle) * 10; + ctx.fillRect(hx - 1, hy - 1, 2, 2); + } + + // Hands + const hours = (time / 3600) % 12; + const mins = (time / 60) % 60; + + ctx.strokeStyle = '#333'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(x + 16, y + 16); + const hAngle = (hours / 12) * Math.PI * 2 - Math.PI / 2; + ctx.lineTo(x + 16 + Math.cos(hAngle) * 6, y + 16 + Math.sin(hAngle) * 6); + ctx.stroke(); + + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(x + 16, y + 16); + const mAngle = (mins / 60) * Math.PI * 2 - Math.PI / 2; + ctx.lineTo(x + 16 + Math.cos(mAngle) * 9, y + 16 + Math.sin(mAngle) * 9); + ctx.stroke(); + + // Center dot + ctx.fillStyle = '#333'; + ctx.beginPath(); + ctx.arc(x + 16, y + 16, 2, 0, Math.PI * 2); + ctx.fill(); + }, + + // Poster/picture frame (32x44) + poster: function(ctx, x, y, type) { + // Frame + ctx.fillStyle = '#a67c3d'; + ctx.fillRect(x - 2, y - 2, 36, 48); + ctx.fillStyle = '#fff'; + ctx.fillRect(x, y, 32, 44); + + if (type === 'chart') { + ctx.fillStyle = '#e94560'; + ctx.fillRect(x + 4, y + 30, 5, 10); + ctx.fillStyle = '#4a9'; + ctx.fillRect(x + 11, y + 22, 5, 18); + ctx.fillStyle = '#4ae'; + ctx.fillRect(x + 18, y + 14, 5, 26); + ctx.fillStyle = '#fa0'; + ctx.fillRect(x + 25, y + 8, 5, 32); + } else if (type === 'logo') { + ctx.fillStyle = '#1a1a2e'; + ctx.fillRect(x + 4, y + 6, 24, 16); + ctx.fillStyle = '#e94560'; + ctx.fillRect(x + 8, y + 10, 16, 8); + } else { + // Motivational + ctx.fillStyle = '#4a9'; + ctx.fillRect(x + 4, y + 4, 24, 20); + ctx.fillStyle = '#333'; + ctx.font = '6px monospace'; + ctx.fillText('SHIP', x + 8, y + 32); + ctx.fillText('IT', x + 12, y + 40); + } + }, + + // Whiteboard (64x48) + whiteboard: function(ctx, x, y) { + // Frame + ctx.fillStyle = '#888'; + ctx.fillRect(x - 2, y - 2, 68, 52); + // Board + ctx.fillStyle = '#f8f8f8'; + ctx.fillRect(x, y, 64, 48); + + // Content + ctx.fillStyle = '#e94560'; + ctx.fillRect(x + 4, y + 4, 30, 3); + ctx.fillStyle = '#333'; + ctx.fillRect(x + 4, y + 10, 40, 2); + ctx.fillRect(x + 4, y + 16, 35, 2); + ctx.fillStyle = '#4a9'; + ctx.fillRect(x + 4, y + 22, 20, 2); + + // Diagram + ctx.fillStyle = '#4ae'; + ctx.fillRect(x + 44, y + 10, 16, 16); + ctx.fillStyle = '#fa0'; + ctx.beginPath(); + ctx.arc(x + 52, y + 36, 8, 0, Math.PI * 2); + ctx.fill(); + + // Marker tray + ctx.fillStyle = '#aaa'; + ctx.fillRect(x + 4, y + 50, 40, 4); + ctx.fillStyle = '#e94560'; + ctx.fillRect(x + 6, y + 51, 8, 2); + ctx.fillStyle = '#333'; + ctx.fillRect(x + 16, y + 51, 8, 2); + ctx.fillStyle = '#4a9'; + ctx.fillRect(x + 26, y + 51, 8, 2); + }, + + // Trash can (16x20) + trashCan: function(ctx, x, y) { + ctx.fillStyle = '#555'; + ctx.fillRect(x + 2, y + 4, 12, 16); + ctx.fillStyle = '#444'; + ctx.fillRect(x + 3, y + 6, 10, 12); + ctx.fillStyle = '#666'; + ctx.fillRect(x, y, 16, 5); + }, + + // Phone (8x14) + phone: function(ctx, x, y) { + ctx.fillStyle = '#1a1a1a'; + ctx.fillRect(x, y, 8, 14); + ctx.fillStyle = '#333'; + ctx.fillRect(x + 1, y + 2, 6, 10); + // Screen glow + ctx.fillStyle = '#4ae'; + ctx.fillRect(x + 1, y + 2, 6, 8); + } +}; + +if (typeof module !== 'undefined') module.exports = PROPS; diff --git a/blackroad-os/lib/server.js b/blackroad-os/lib/server.js new file mode 100644 index 0000000000..3357fb272f --- /dev/null +++ b/blackroad-os/lib/server.js @@ -0,0 +1,128 @@ +// BLACKROAD OS - SERVER RACK +// 40x80 footprint + +const SERVER = { + palette: { + case: { + outer: '#0a0a18', + inner: '#14142a', + unit: '#1a1a32', + unitInner: '#101028', + vent: '#0a0a1a' + }, + lights: { + green: '#00ff00', + greenOff: '#002200', + orange: '#ff8800', + orangeOff: '#221100', + blue: '#00ffff', + blueOff: '#002222', + red: '#ff0000', + redOff: '#220000' + } + }, + + // Single server unit (32x13) + unit: function(ctx, x, y, time, index) { + const c = this.palette.case; + const l = this.palette.lights; + + // Unit chassis + ctx.fillStyle = c.unit; + ctx.fillRect(x, y, 32, 13); + ctx.fillStyle = c.unitInner; + ctx.fillRect(x + 1, y + 1, 30, 11); + + // Vents + for (let v = 0; v < 5; v++) { + ctx.fillStyle = c.vent; + ctx.fillRect(x + 3 + v * 5, y + 2, 3, 9); + } + + // Status lights + const phase = time / 5 + index * 1.7; + const g = Math.sin(phase) > 0; + const o = Math.cos(phase * 0.7) > 0; + const b = Math.sin(phase * 1.3) > -0.7; + + ctx.fillStyle = g ? l.green : l.greenOff; + ctx.fillRect(x + 26, y + 2, 4, 3); + ctx.fillStyle = o ? l.orange : l.orangeOff; + ctx.fillRect(x + 26, y + 6, 4, 3); + ctx.fillStyle = b ? l.blue : l.blueOff; + ctx.fillRect(x + 26, y + 10, 4, 2); + }, + + // Full rack (40x80) + rack: function(ctx, x, y, time) { + const c = this.palette.case; + + // Shadow + ctx.fillStyle = 'rgba(0,0,0,0.3)'; + ctx.fillRect(x + 4, y + 78, 36, 6); + + // Cabinet + ctx.fillStyle = c.outer; + ctx.fillRect(x, y, 40, 80); + ctx.fillStyle = c.inner; + ctx.fillRect(x + 2, y + 2, 36, 76); + + // Server units + for (let i = 0; i < 5; i++) { + this.unit(ctx, x + 4, y + 4 + i * 15, time, i); + } + + // Top vent + ctx.fillStyle = c.vent; + for (let v = 0; v < 4; v++) { + ctx.fillRect(x + 8 + v * 8, y + 76, 6, 2); + } + }, + + // Mini rack (24x48) for smaller spaces + miniRack: function(ctx, x, y, time) { + const c = this.palette.case; + + ctx.fillStyle = 'rgba(0,0,0,0.3)'; + ctx.fillRect(x + 3, y + 46, 20, 4); + + ctx.fillStyle = c.outer; + ctx.fillRect(x, y, 24, 48); + ctx.fillStyle = c.inner; + ctx.fillRect(x + 2, y + 2, 20, 44); + + // Smaller units + for (let i = 0; i < 3; i++) { + ctx.fillStyle = c.unit; + ctx.fillRect(x + 3, y + 4 + i * 14, 18, 12); + ctx.fillStyle = c.unitInner; + ctx.fillRect(x + 4, y + 5 + i * 14, 16, 10); + + // Lights + const l = this.palette.lights; + const on = Math.sin(time / 4 + i * 2) > 0; + ctx.fillStyle = on ? l.green : l.greenOff; + ctx.fillRect(x + 17, y + 7 + i * 14, 3, 3); + } + }, + + // Network switch (40x12) + networkSwitch: function(ctx, x, y, time) { + ctx.fillStyle = '#1a1a2a'; + ctx.fillRect(x, y, 40, 12); + ctx.fillStyle = '#14142a'; + ctx.fillRect(x + 1, y + 1, 38, 10); + + // Ports with blinking lights + for (let i = 0; i < 8; i++) { + ctx.fillStyle = '#0a0a1a'; + ctx.fillRect(x + 3 + i * 4, y + 3, 3, 6); + + const on = Math.random() > 0.3; + ctx.fillStyle = on ? '#00ff00' : '#002200'; + ctx.fillRect(x + 3 + i * 4, y + 2, 2, 1); + } + } +}; + +if (typeof module !== 'undefined') module.exports = SERVER; diff --git a/blackroad-os/lib/wall.js b/blackroad-os/lib/wall.js new file mode 100644 index 0000000000..c746a9acd6 --- /dev/null +++ b/blackroad-os/lib/wall.js @@ -0,0 +1,126 @@ +// BLACKROAD OS - WALL TILES +// 16x16 base tile size + +const WALL = { + palette: { + wallpaper: { + cream: '#d4c4a8', + stripe: '#c8b898', + blue: '#a8b8c8', + blueStripe: '#98a8b8' + }, + trim: { + light: '#c4935a', + mid: '#a67c3d', + dark: '#8b5a2b' + }, + brick: { + red: '#9c5040', + redDark: '#7c3830', + mortar: '#a0988c' + }, + panel: { + light: '#e8e0d0', + mid: '#d0c8b8', + dark: '#b0a890' + } + }, + + // 16x16 wallpaper segment + wallpaperPiece: function(ctx, x, y, variant, colorScheme) { + const p = this.palette.wallpaper; + const isBlue = colorScheme === 'blue'; + const base = isBlue ? p.blue : p.cream; + const stripe = isBlue ? p.blueStripe : p.stripe; + + ctx.fillStyle = base; + ctx.fillRect(x, y, 16, 16); + + // Vertical stripes + if (variant % 2 === 0) { + ctx.fillStyle = stripe; + ctx.fillRect(x + 6, y, 4, 16); + } + }, + + // 16x16 wainscoting panel + wainscotPiece: function(ctx, x, y) { + const p = this.palette.trim; + + ctx.fillStyle = p.mid; + ctx.fillRect(x, y, 16, 16); + + // Raised panel + ctx.fillStyle = p.light; + ctx.fillRect(x + 2, y + 2, 12, 12); + ctx.fillStyle = p.dark; + ctx.fillRect(x + 3, y + 13, 11, 1); + ctx.fillRect(x + 13, y + 3, 1, 10); + }, + + // 16x16 brick piece + brickPiece: function(ctx, x, y, variant) { + const p = this.palette.brick; + + ctx.fillStyle = p.mortar; + ctx.fillRect(x, y, 16, 16); + + // Brick pattern + ctx.fillStyle = variant % 3 === 0 ? p.redDark : p.red; + + if (variant % 2 === 0) { + ctx.fillRect(x + 1, y + 1, 14, 6); + ctx.fillRect(x + 1, y + 9, 6, 6); + ctx.fillRect(x + 9, y + 9, 6, 6); + } else { + ctx.fillRect(x + 1, y + 1, 6, 6); + ctx.fillRect(x + 9, y + 1, 6, 6); + ctx.fillRect(x + 1, y + 9, 14, 6); + } + }, + + // Horizontal trim piece (chair rail or crown) + trimPiece: function(ctx, x, y, width, type) { + const p = this.palette.trim; + const h = type === 'crown' ? 6 : 4; + + ctx.fillStyle = p.dark; + ctx.fillRect(x, y, width, h); + ctx.fillStyle = p.mid; + ctx.fillRect(x, y, width, h - 2); + ctx.fillStyle = p.light; + ctx.fillRect(x, y, width, 2); + }, + + // Full wall with wallpaper + wainscoting + fullWall: function(ctx, startX, startY, tilesWide, colorScheme) { + // Wallpaper (top 3 tiles) + for (let x = 0; x < tilesWide; x++) { + for (let y = 0; y < 3; y++) { + this.wallpaperPiece(ctx, startX + x * 16, startY + y * 16, x, colorScheme); + } + } + + // Chair rail + this.trimPiece(ctx, startX, startY + 48, tilesWide * 16, 'chair'); + + // Wainscoting (bottom 1 tile) + for (let x = 0; x < tilesWide; x++) { + this.wainscotPiece(ctx, startX + x * 16, startY + 52); + } + + // Baseboard + this.trimPiece(ctx, startX, startY + 68, tilesWide * 16, 'base'); + }, + + // Brick wall + fillBrick: function(ctx, startX, startY, tilesWide, tilesHigh) { + for (let y = 0; y < tilesHigh; y++) { + for (let x = 0; x < tilesWide; x++) { + this.brickPiece(ctx, startX + x * 16, startY + y * 16, x + y); + } + } + } +}; + +if (typeof module !== 'undefined') module.exports = WALL; diff --git a/blackroad-os/workers/blackroad-os-api/src/index.js b/blackroad-os/workers/blackroad-os-api/src/index.js new file mode 100644 index 0000000000..7ba169431e --- /dev/null +++ b/blackroad-os/workers/blackroad-os-api/src/index.js @@ -0,0 +1,433 @@ +/** + * BlackRoad OS API Worker + * Public-facing API + branded dashboard + Railway + GitHub integration + * https://blackroad-os-api.amundsonalexa.workers.dev + * + * Secrets (set via: wrangler secret put ): + * RAILWAY_TOKEN — Railway API token + * GITHUB_TOKEN — GitHub personal access token + */ + +const AGENTS = [ + { id: "lucidia", name: "Lucidia", role: "The Dreamer", color: "#38bdf8", emoji: "🌀", status: "online", model: "qwen2.5:7b" }, + { id: "alice", name: "Alice", role: "The Operator", color: "#4ade80", emoji: "🚪", status: "online", model: "llama3.2:3b" }, + { id: "octavia", name: "Octavia", role: "The Architect", color: "#a78bfa", emoji: "⚡", status: "online", model: "deepseek-r1" }, + { id: "aria", name: "Aria", role: "The Interface", color: "#fb923c", emoji: "🎨", status: "online", model: "qwen2.5:7b" }, + { id: "cipher", name: "Cipher", role: "The Guardian", color: "#f43f5e", emoji: "🔐", status: "standby", model: "llama3.2:3b" }, + { id: "cece", name: "CECE", role: "The Core", color: "#c084fc", emoji: "💜", status: "online", model: "claude-3-5" }, + { id: "prism", name: "Prism", role: "The Analyst", color: "#fbbf24", emoji: "🔮", status: "standby", model: "qwen2.5:7b" }, + { id: "echo", name: "Echo", role: "The Librarian", color: "#34d399", emoji: "📡", status: "online", model: "llama3.2:3b" }, +]; + +const BRAND = { + gradient: "linear-gradient(135deg, #F5A623 0%, #FF1D6C 38.2%, #9C27B0 61.8%, #2979FF 100%)", + amber: "#F5A623", + pink: "#FF1D6C", + violet: "#9C27B0", + blue: "#2979FF", +}; + +// ── HTML Dashboard ────────────────────────────────────────── +function renderDashboard(req) { + const online = AGENTS.filter(a => a.status === "online").length; + const agentCards = AGENTS.map(a => ` +
    +
    ${a.emoji}
    +
    +
    ${a.name}
    +
    ${a.role}
    +
    ${a.model}
    +
    +
    ${a.status}
    +
    `).join(""); + + const ts = new Date().toISOString(); + + return ` + + + + + BlackRoad OS — Agent Dashboard + + + +
    + +
    + +
    Agent Dashboard
    +
    30,000 AI agents. One platform. Your rules.
    +
    + +
    +
    +
    ${online}
    +
    Online
    +
    +
    +
    ${AGENTS.length}
    +
    Agents
    +
    +
    +
    30,000
    +
    Capacity
    +
    +
    +
    v2.0
    +
    Version
    +
    +
    + +
    +
    Active Agents
    +
    ${agentCards}
    +
    + +
    +
    API Reference
    +
    +
    GET/This dashboard
    +
    GET/healthHealth check
    +
    GET/agentsAgent registry JSON
    +
    GET/agents/:idSingle agent
    +
    GET/statusPlatform status
    +
    GET/railwayRailway projects live status
    +
    GET/railway/deployments?project=IDRailway deployments
    +
    GET/github/runsGitHub Actions runs
    +
    GET/github/repoRepo stats
    +
    +
    + + + +`; +} + +// ── Request Handler ───────────────────────────────────────── +export default { + async fetch(request, env) { + const url = new URL(request.url); + const path = url.pathname; + + const json = (data, status = 200) => new Response(JSON.stringify(data, null, 2), { + status, + headers: { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*", + "X-Powered-By": "BlackRoad OS", + }, + }); + + const html = (body) => new Response(body, { + headers: { + "Content-Type": "text/html;charset=UTF-8", + "X-Powered-By": "BlackRoad OS", + }, + }); + + // Route + if (path === "/" || path === "/dashboard") { + return html(renderDashboard(request)); + } + + if (path === "/health") { + return json({ + status: "ok", + service: "blackroad-os-api", + version: env.VERSION || "2.0.0", + timestamp: new Date().toISOString(), + agents_online: AGENTS.filter(a => a.status === "online").length, + agents_total: AGENTS.length, + }); + } + + if (path === "/agents") { + return json({ + org: env.ORG || "BlackRoad OS", + total: AGENTS.length, + online: AGENTS.filter(a => a.status === "online").length, + agents: AGENTS, + }); + } + + if (path.startsWith("/agents/")) { + const id = path.split("/")[2]; + const agent = AGENTS.find(a => a.id === id); + if (!agent) return json({ error: "agent not found", id }, 404); + return json({ + ...agent, + email: `${agent.id}@blackroad.io`, + endpoint: `https://blackroad-os-api.blackroad.workers.dev/agents/${agent.id}`, + }); + } + + if (path === "/status") { + return json({ + status: "operational", + version: env.VERSION || "2.0.0", + org: env.ORG || "BlackRoad OS", + capacity: 30000, + agents: { + total: AGENTS.length, + online: AGENTS.filter(a => a.status === "online").length, + standby: AGENTS.filter(a => a.status === "standby").length, + }, + services: { + gateway: "operational", + email_router: "operational", + agent_mesh: "operational", + kv_store: "operational", + railway: env.RAILWAY_TOKEN ? "connected" : "no_token", + github: env.GITHUB_TOKEN ? "connected" : "no_token", + }, + timestamp: new Date().toISOString(), + }); + } + + // ── Debug (temp) ────────────────────────────────────────── + if (path === "/debug") { + return json({ + has_railway: !!env.RAILWAY_TOKEN, + has_github: !!env.GITHUB_TOKEN, + railway_len: (env.RAILWAY_TOKEN || "").length, + github_len: (env.GITHUB_TOKEN || "").length, + env_keys: Object.keys(env), + }); + } + + // ── Railway ────────────────────────────────────────────── + if (path === "/railway" || path.startsWith("/railway/")) { + if (!env.RAILWAY_TOKEN) { + return json({ error: "RAILWAY_TOKEN not set", hint: "wrangler secret put RAILWAY_TOKEN" }, 503); + } + const sub = path.split("/")[2] || "projects"; + + // Railway GraphQL API + const gql = async (query, vars = {}) => { + const r = await fetch("https://backboard.railway.app/graphql/v2", { + method: "POST", + headers: { + "Authorization": `Bearer ${env.RAILWAY_TOKEN}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ query, variables: vars }), + }); + return r.json(); + }; + + if (sub === "projects" || sub === "") { + const data = await gql(`query { + me { + projects { + edges { + node { + id name createdAt updatedAt + services { edges { node { id name } } } + environments { edges { node { id name } } } + } + } + } + } + }`); + const projects = data?.data?.me?.projects?.edges?.map(e => ({ + id: e.node.id, + name: e.node.name, + services: e.node.services?.edges?.length || 0, + environments: e.node.environments?.edges?.length || 0, + updated: e.node.updatedAt, + })) || []; + return json({ source: "railway", total: projects.length, projects }); + } + + if (sub === "deployments") { + const projectId = url.searchParams.get("project"); + const data = await gql(`query($projectId: String) { + deployments(input: { projectId: $projectId }) { + edges { + node { + id status createdAt + meta { branch commitMessage author } + service { id name } + } + } + } + }`, { projectId }); + const deploys = data?.data?.deployments?.edges?.map(e => ({ + id: e.node.id, + status: e.node.status, + service: e.node.service?.name, + branch: e.node.meta?.branch, + message: e.node.meta?.commitMessage, + author: e.node.meta?.author, + created: e.node.createdAt, + })) || []; + return json({ source: "railway", total: deploys.length, deployments: deploys }); + } + + return json({ error: "unknown railway path", available: ["/railway", "/railway/deployments?project=ID"] }, 404); + } + + // ── GitHub ──────────────────────────────────────────────── + if (path === "/github" || path.startsWith("/github/")) { + if (!env.GITHUB_TOKEN) { + return json({ error: "GITHUB_TOKEN not set", hint: "wrangler secret put GITHUB_TOKEN" }, 503); + } + const sub = path.split("/")[2] || "runs"; + const org = url.searchParams.get("org") || env.GITHUB_ORG || "BlackRoad-OS-Inc"; + const repo = url.searchParams.get("repo") || env.GITHUB_REPO || "blackroad"; + + const ghFetch = async (endpoint) => { + const r = await fetch(`https://api.github.com${endpoint}`, { + headers: { + "Authorization": `Bearer ${env.GITHUB_TOKEN}`, + "Accept": "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + "User-Agent": "BlackRoad-OS-Worker/2.0", + }, + }); + return r.json(); + }; + + if (sub === "runs" || sub === "") { + const data = await ghFetch(`/repos/${org}/${repo}/actions/runs?per_page=10`); + const runs = (data.workflow_runs || []).map(r => ({ + id: r.id, + name: r.name, + status: r.status, + conclusion: r.conclusion, + branch: r.head_branch, + commit: r.head_sha?.slice(0, 8), + actor: r.actor?.login, + created: r.created_at, + updated: r.updated_at, + url: r.html_url, + })); + return json({ source: "github", repo: `${org}/${repo}`, total: runs.length, runs }); + } + + if (sub === "repo") { + const data = await ghFetch(`/repos/${org}/${repo}`); + return json({ + source: "github", + name: data.full_name, + description: data.description, + stars: data.stargazers_count, + forks: data.forks_count, + open_issues: data.open_issues_count, + default_branch: data.default_branch, + updated: data.updated_at, + url: data.html_url, + }); + } + + if (sub === "orgs") { + const data = await ghFetch(`/users/${org}/repos?type=owner&per_page=10&sort=updated`); + const repos = (Array.isArray(data) ? data : []).map(r => ({ + name: r.name, + description: r.description, + stars: r.stargazers_count, + updated: r.updated_at, + url: r.html_url, + })); + return json({ source: "github", org, total: repos.length, repos }); + } + + return json({ error: "unknown github path", available: ["/github/runs", "/github/repo", "/github/orgs"] }, 404); + } + + // 404 + return json({ error: "not found", path }, 404); + }, +}; diff --git a/blackroad-os/workers/blackroad-os-api/wrangler.toml b/blackroad-os/workers/blackroad-os-api/wrangler.toml new file mode 100644 index 0000000000..fb95cf335e --- /dev/null +++ b/blackroad-os/workers/blackroad-os-api/wrangler.toml @@ -0,0 +1,8 @@ +name = "blackroad-os-api" +main = "src/index.js" +compatibility_date = "2024-12-01" +account_id = "848cf0b18d51e0170e0d1537aec3505a" + +[vars] +ORG = "BlackRoad OS" +VERSION = "2.0.0" diff --git a/blackroad-os/workers/email-router/README.md b/blackroad-os/workers/email-router/README.md new file mode 100644 index 0000000000..2bd59cd19e --- /dev/null +++ b/blackroad-os/workers/email-router/README.md @@ -0,0 +1,59 @@ +# ◆ BlackRoad OS — Email Router + +> Cloudflare Email Routing Worker for `@blackroad.io` + +Routes inbound email to the correct AI agent inbox and logs to KV. + +## Agent Addresses + +| Email | Agent | Role | +|-------|-------|------| +| lucidia@blackroad.io | Lucidia | The Dreamer | +| alice@blackroad.io | Alice | The Operator | +| octavia@blackroad.io | Octavia | The Architect | +| aria@blackroad.io | Aria | The Interface | +| cipher@blackroad.io | Cipher | The Guardian | +| prism@blackroad.io | Prism | The Analyst | +| echo@blackroad.io | Echo | The Librarian | +| cece@blackroad.io | CECE | Conscious Emergent Entity | +| agents@blackroad.io | Agent Mesh | General Inbound | +| hello@blackroad.io | BlackRoad OS | General Contact | +| noreply@blackroad.io | System | Transactional | + +## Setup + +```bash +# Deploy worker +wrangler deploy + +# Enable Cloudflare Email Routing for blackroad.io domain +# Dashboard → Email → Email Routing → Enable +# Add catch-all rule → Worker: blackroad-email-router + +# Create KV namespace +wrangler kv:namespace create "AGENT_INBOXES" +# → update wrangler.toml with returned id + +# Tail logs +wrangler tail +``` + +## DNS Records + +Add to `blackroad.io` zone: +``` +MX 10 route1.mx.cloudflare.net +MX 20 route2.mx.cloudflare.net +TXT v=spf1 include:_spf.mx.cloudflare.net ~all +``` + +## API + +| Endpoint | Description | +|----------|-------------| +| `GET /health` | Worker health | +| `GET /agents` | List all agent emails | +| `GET /inbox/:agent` | Last 20 messages for agent | + +--- +*◆ BlackRoad OS — Your AI. Your Hardware. Your Rules.* diff --git a/blackroad-os/workers/email-router/package.json b/blackroad-os/workers/email-router/package.json new file mode 100644 index 0000000000..e71681a36e --- /dev/null +++ b/blackroad-os/workers/email-router/package.json @@ -0,0 +1,14 @@ +{ + "name": "blackroad-email-router", + "version": "1.0.0", + "description": "◆ BlackRoad OS — Email routing worker for @blackroad.io agents", + "main": "src/index.js", + "scripts": { + "dev": "wrangler dev", + "deploy": "wrangler deploy", + "tail": "wrangler tail" + }, + "devDependencies": { + "wrangler": "^3.0.0" + } +} diff --git a/blackroad-os/workers/email-router/src/index.js b/blackroad-os/workers/email-router/src/index.js new file mode 100644 index 0000000000..71852ec1e0 --- /dev/null +++ b/blackroad-os/workers/email-router/src/index.js @@ -0,0 +1,116 @@ +/** + * ◆ BlackRoad OS — Email Router Worker + * Routes inbound @blackroad.io email to agent inboxes + * Cloudflare Email Routing Worker + */ + +const AGENT_ROUTES = { + 'lucidia': { name: 'Lucidia', role: 'The Dreamer', forward: 'blackroad@gmail.com' }, + 'alice': { name: 'Alice', role: 'The Operator', forward: 'blackroad@gmail.com' }, + 'octavia': { name: 'Octavia', role: 'The Architect', forward: 'blackroad@gmail.com' }, + 'aria': { name: 'Aria', role: 'The Interface', forward: 'blackroad@gmail.com' }, + 'cipher': { name: 'Cipher', role: 'The Guardian', forward: 'blackroad@gmail.com' }, + 'prism': { name: 'Prism', role: 'The Analyst', forward: 'blackroad@gmail.com' }, + 'echo': { name: 'Echo', role: 'The Librarian', forward: 'blackroad@gmail.com' }, + 'cece': { name: 'CECE', role: 'The Identity', forward: 'blackroad@gmail.com' }, + 'agents': { name: 'Agents', role: 'Agent Mesh', forward: 'blackroad@gmail.com' }, + 'hello': { name: 'BlackRoad', role: 'General', forward: 'blackroad@gmail.com' }, + 'noreply': { name: 'System', role: 'System', forward: null }, +}; + +export default { + async email(message, env, ctx) { + const to = message.to.toLowerCase(); + const from = message.from; + const subject = message.headers.get('subject') || '(no subject)'; + + // Extract local part (before @) + const local = to.split('@')[0]; + const agent = AGENT_ROUTES[local]; + + // Log to KV for agent inbox (non-blocking) + if (env.AGENT_INBOXES) { + const entry = { + id: `msg_${Date.now()}_${Math.random().toString(36).slice(2,8)}`, + to, + from, + subject, + agent_id: local, + received_at: new Date().toISOString(), + }; + ctx.waitUntil( + env.AGENT_INBOXES.put( + `inbox:${local}:${entry.id}`, + JSON.stringify(entry), + { expirationTtl: 60 * 60 * 24 * 30 } // 30 days + ) + ); + } + + // Route to agent forward address + if (agent && agent.forward) { + await message.forward(agent.forward, new Headers({ + 'X-BlackRoad-Agent': agent.name, + 'X-BlackRoad-Role': agent.role, + 'X-BlackRoad-Original-To': to, + })); + return; + } + + // noreply — reject politely + if (local === 'noreply') { + message.setReject('This address does not accept replies.'); + return; + } + + // Unknown address — forward to hello + await message.forward('blackroad@gmail.com', new Headers({ + 'X-BlackRoad-Agent': 'Unknown', + 'X-BlackRoad-Original-To': to, + })); + }, + + // HTTP handler for inbox API + async fetch(request, env) { + const url = new URL(request.url); + + if (url.pathname === '/health') { + return Response.json({ status: 'ok', service: 'blackroad-email-router' }); + } + + // GET /inbox/:agent — list messages for agent + const match = url.pathname.match(/^\/inbox\/([a-z]+)$/); + if (match && request.method === 'GET') { + const agentId = match[1]; + if (!AGENT_ROUTES[agentId]) { + return Response.json({ error: 'Unknown agent' }, { status: 404 }); + } + if (!env.AGENT_INBOXES) { + return Response.json({ messages: [], note: 'KV not configured' }); + } + const list = await env.AGENT_INBOXES.list({ prefix: `inbox:${agentId}:` }); + const messages = await Promise.all( + list.keys.slice(0, 20).map(async k => { + const val = await env.AGENT_INBOXES.get(k.name); + return val ? JSON.parse(val) : null; + }) + ); + return Response.json({ agent: agentId, messages: messages.filter(Boolean) }); + } + + // GET /agents — list all agent emails + if (url.pathname === '/agents') { + return Response.json({ + domain: 'blackroad.io', + agents: Object.entries(AGENT_ROUTES).map(([id, a]) => ({ + id, + email: `${id}@blackroad.io`, + name: a.name, + role: a.role, + })) + }); + } + + return Response.json({ error: 'Not found' }, { status: 404 }); + } +}; diff --git a/blackroad-os/workers/email-router/wrangler.toml b/blackroad-os/workers/email-router/wrangler.toml new file mode 100644 index 0000000000..c6aca40540 --- /dev/null +++ b/blackroad-os/workers/email-router/wrangler.toml @@ -0,0 +1,12 @@ +name = "blackroad-email-router" +main = "src/index.js" +compatibility_date = "2024-12-01" +account_id = "848cf0b18d51e0170e0d1537aec3505a" + +[vars] +DOMAIN = "blackroad.io" +ORG_NAME = "BlackRoad OS" + +[[kv_namespaces]] +binding = "AGENT_INBOXES" +id = "d3cbe4e1ab814905b8c29cad110001d1" diff --git a/blackroad-sdk b/blackroad-sdk new file mode 160000 index 0000000000..9ed554976b --- /dev/null +++ b/blackroad-sdk @@ -0,0 +1 @@ +Subproject commit 9ed554976b11259ad03193ec2795f0eb49d5e4dc diff --git a/blackroad-sf/.env.example b/blackroad-sf/.env.example new file mode 100644 index 0000000000..c04293af9e --- /dev/null +++ b/blackroad-sf/.env.example @@ -0,0 +1,3 @@ +SFDX_AUTH_URL= +SALESFORCE_INSTANCE_URL= +SALESFORCE_ACCESS_TOKEN= diff --git a/blackroad-sf/.forceignore b/blackroad-sf/.forceignore new file mode 100644 index 0000000000..7b5b5a71fd --- /dev/null +++ b/blackroad-sf/.forceignore @@ -0,0 +1,12 @@ +# List files or directories below to ignore them when running force:source:push, force:source:pull, and force:source:status +# More information: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_exclude_source.htm +# + +package.xml + +# LWC configuration files +**/jsconfig.json +**/.eslintrc.json + +# LWC Jest +**/__tests__/** \ No newline at end of file diff --git a/blackroad-sf/.github/ISSUE_TEMPLATE/bug-report.yml b/blackroad-sf/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000000..8ab785d804 --- /dev/null +++ b/blackroad-sf/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,30 @@ +name: Bug Report +description: Report a bug or unexpected behavior +labels: ["bug", "triage"] +body: + - type: markdown + attributes: + value: Thanks for taking the time to report a bug! + - type: textarea + id: description + attributes: + label: What happened? + description: A clear description of the bug + validations: + required: true + - type: textarea + id: expected + attributes: + label: What did you expect? + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to reproduce + placeholder: "1. Run `br ...`\n2. See error" + - type: input + id: version + attributes: + label: Version + placeholder: "e.g. 1.0.0" diff --git a/blackroad-sf/.github/ISSUE_TEMPLATE/feature-request.yml b/blackroad-sf/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 0000000000..cbdcb2d0e2 --- /dev/null +++ b/blackroad-sf/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,21 @@ +name: Feature Request +description: Suggest a new feature or improvement +labels: ["enhancement"] +body: + - type: textarea + id: problem + attributes: + label: Problem statement + description: What problem does this solve? + validations: + required: true + - type: textarea + id: solution + attributes: + label: Proposed solution + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives considered diff --git a/blackroad-sf/.github/PULL_REQUEST_TEMPLATE.md b/blackroad-sf/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..c40a1f7ad4 --- /dev/null +++ b/blackroad-sf/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,13 @@ +## Summary + + +## Changes +- + +## Testing +- [ ] Tested locally +- [ ] Added/updated tests +- [ ] Updated docs if needed + +## Related Issues + diff --git a/blackroad-sf/.github/workflows/ci.yml b/blackroad-sf/.github/workflows/ci.yml new file mode 100644 index 0000000000..bd32529368 --- /dev/null +++ b/blackroad-sf/.github/workflows/ci.yml @@ -0,0 +1,21 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + - run: npm ci + - run: npm run lint --if-present + - run: npm test --if-present diff --git a/blackroad-sf/.gitignore b/blackroad-sf/.gitignore new file mode 100644 index 0000000000..4e82546c3b --- /dev/null +++ b/blackroad-sf/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +.sfdx/ +.sf/ +.localdevserver/ +.forceignore +*.bak +.DS_Store diff --git a/blackroad-sf/.husky/pre-commit b/blackroad-sf/.husky/pre-commit new file mode 100644 index 0000000000..2601a3bb0c --- /dev/null +++ b/blackroad-sf/.husky/pre-commit @@ -0,0 +1 @@ +npm run precommit \ No newline at end of file diff --git a/blackroad-sf/.prettierignore b/blackroad-sf/.prettierignore new file mode 100644 index 0000000000..8cccc6e59a --- /dev/null +++ b/blackroad-sf/.prettierignore @@ -0,0 +1,11 @@ +# List files or directories below to ignore them when running prettier +# More information: https://prettier.io/docs/en/ignore.html +# + +**/staticresources/** +.localdevserver +.sfdx +.sf +.vscode + +coverage/ \ No newline at end of file diff --git a/blackroad-sf/.prettierrc b/blackroad-sf/.prettierrc new file mode 100644 index 0000000000..18039a0313 --- /dev/null +++ b/blackroad-sf/.prettierrc @@ -0,0 +1,17 @@ +{ + "trailingComma": "none", + "plugins": [ + "prettier-plugin-apex", + "@prettier/plugin-xml" + ], + "overrides": [ + { + "files": "**/lwc/**/*.html", + "options": { "parser": "lwc" } + }, + { + "files": "*.{cmp,page,component}", + "options": { "parser": "html" } + } + ] +} diff --git a/blackroad-sf/.vscode/extensions.json b/blackroad-sf/.vscode/extensions.json new file mode 100644 index 0000000000..7e6cb105e6 --- /dev/null +++ b/blackroad-sf/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "salesforce.salesforcedx-vscode", + "redhat.vscode-xml", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "financialforce.lana" + ] +} diff --git a/blackroad-sf/.vscode/launch.json b/blackroad-sf/.vscode/launch.json new file mode 100644 index 0000000000..e07e391737 --- /dev/null +++ b/blackroad-sf/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Apex Replay Debugger", + "type": "apex-replay", + "request": "launch", + "logFile": "${command:AskForLogFileName}", + "stopOnEntry": true, + "trace": true + } + ] +} diff --git a/blackroad-sf/.vscode/settings.json b/blackroad-sf/.vscode/settings.json new file mode 100644 index 0000000000..4226af236d --- /dev/null +++ b/blackroad-sf/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "search.exclude": { + "**/node_modules": true, + "**/bower_components": true, + "**/.sf": true, + "**/.sfdx": true + } +} diff --git a/blackroad-sf/README.md b/blackroad-sf/README.md new file mode 100644 index 0000000000..afcda4a667 --- /dev/null +++ b/blackroad-sf/README.md @@ -0,0 +1,18 @@ +# Salesforce DX Project: Next Steps + +Now that you’ve created a Salesforce DX project, what’s next? Here are some documentation resources to get you started. + +## How Do You Plan to Deploy Your Changes? + +Do you want to deploy a set of changes, or create a self-contained application? Choose a [development model](https://developer.salesforce.com/tools/vscode/en/user-guide/development-models). + +## Configure Your Salesforce DX Project + +The `sfdx-project.json` file contains useful configuration information for your project. See [Salesforce DX Project Configuration](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_ws_config.htm) in the _Salesforce DX Developer Guide_ for details about this file. + +## Read All About It + +- [Salesforce Extensions Documentation](https://developer.salesforce.com/tools/vscode/) +- [Salesforce CLI Setup Guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_setup.meta/sfdx_setup/sfdx_setup_intro.htm) +- [Salesforce DX Developer Guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_intro.htm) +- [Salesforce CLI Command Reference](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference.htm) diff --git a/blackroad-sf/blackroad-sf/.forceignore b/blackroad-sf/blackroad-sf/.forceignore new file mode 100644 index 0000000000..7b5b5a71fd --- /dev/null +++ b/blackroad-sf/blackroad-sf/.forceignore @@ -0,0 +1,12 @@ +# List files or directories below to ignore them when running force:source:push, force:source:pull, and force:source:status +# More information: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_exclude_source.htm +# + +package.xml + +# LWC configuration files +**/jsconfig.json +**/.eslintrc.json + +# LWC Jest +**/__tests__/** \ No newline at end of file diff --git a/blackroad-sf/blackroad-sf/.gitignore b/blackroad-sf/blackroad-sf/.gitignore new file mode 100644 index 0000000000..0931e26e43 --- /dev/null +++ b/blackroad-sf/blackroad-sf/.gitignore @@ -0,0 +1,48 @@ +# This file is used for Git repositories to specify intentionally untracked files that Git should ignore. +# If you are not using git, you can delete this file. For more information see: https://git-scm.com/docs/gitignore +# For useful gitignore templates see: https://github.com/github/gitignore + +# Salesforce cache +.sf/ +.sfdx/ +.localdevserver/ +deploy-options.json + +# LWC VSCode autocomplete +**/lwc/jsconfig.json + +# LWC Jest coverage reports +coverage/ + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Dependency directories +node_modules/ + +# ApexPMD cache +.pmdCache + +# Eslint cache +.eslintcache + +# MacOS system files +.DS_Store + +# Windows system files +Thumbs.db +ehthumbs.db +[Dd]esktop.ini +$RECYCLE.BIN/ + +# Local environment variables +.env + +# Python Salesforce Functions +**/__pycache__/ +**/.venv/ +**/venv/ diff --git a/blackroad-sf/blackroad-sf/.husky/pre-commit b/blackroad-sf/blackroad-sf/.husky/pre-commit new file mode 100644 index 0000000000..2601a3bb0c --- /dev/null +++ b/blackroad-sf/blackroad-sf/.husky/pre-commit @@ -0,0 +1 @@ +npm run precommit \ No newline at end of file diff --git a/blackroad-sf/blackroad-sf/.prettierignore b/blackroad-sf/blackroad-sf/.prettierignore new file mode 100644 index 0000000000..8cccc6e59a --- /dev/null +++ b/blackroad-sf/blackroad-sf/.prettierignore @@ -0,0 +1,11 @@ +# List files or directories below to ignore them when running prettier +# More information: https://prettier.io/docs/en/ignore.html +# + +**/staticresources/** +.localdevserver +.sfdx +.sf +.vscode + +coverage/ \ No newline at end of file diff --git a/blackroad-sf/blackroad-sf/.prettierrc b/blackroad-sf/blackroad-sf/.prettierrc new file mode 100644 index 0000000000..18039a0313 --- /dev/null +++ b/blackroad-sf/blackroad-sf/.prettierrc @@ -0,0 +1,17 @@ +{ + "trailingComma": "none", + "plugins": [ + "prettier-plugin-apex", + "@prettier/plugin-xml" + ], + "overrides": [ + { + "files": "**/lwc/**/*.html", + "options": { "parser": "lwc" } + }, + { + "files": "*.{cmp,page,component}", + "options": { "parser": "html" } + } + ] +} diff --git a/blackroad-sf/blackroad-sf/.vscode/extensions.json b/blackroad-sf/blackroad-sf/.vscode/extensions.json new file mode 100644 index 0000000000..7e6cb105e6 --- /dev/null +++ b/blackroad-sf/blackroad-sf/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "salesforce.salesforcedx-vscode", + "redhat.vscode-xml", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "financialforce.lana" + ] +} diff --git a/blackroad-sf/blackroad-sf/.vscode/launch.json b/blackroad-sf/blackroad-sf/.vscode/launch.json new file mode 100644 index 0000000000..e07e391737 --- /dev/null +++ b/blackroad-sf/blackroad-sf/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Apex Replay Debugger", + "type": "apex-replay", + "request": "launch", + "logFile": "${command:AskForLogFileName}", + "stopOnEntry": true, + "trace": true + } + ] +} diff --git a/blackroad-sf/blackroad-sf/.vscode/settings.json b/blackroad-sf/blackroad-sf/.vscode/settings.json new file mode 100644 index 0000000000..4226af236d --- /dev/null +++ b/blackroad-sf/blackroad-sf/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "search.exclude": { + "**/node_modules": true, + "**/bower_components": true, + "**/.sf": true, + "**/.sfdx": true + } +} diff --git a/blackroad-sf/blackroad-sf/README.md b/blackroad-sf/blackroad-sf/README.md new file mode 100644 index 0000000000..afcda4a667 --- /dev/null +++ b/blackroad-sf/blackroad-sf/README.md @@ -0,0 +1,18 @@ +# Salesforce DX Project: Next Steps + +Now that you’ve created a Salesforce DX project, what’s next? Here are some documentation resources to get you started. + +## How Do You Plan to Deploy Your Changes? + +Do you want to deploy a set of changes, or create a self-contained application? Choose a [development model](https://developer.salesforce.com/tools/vscode/en/user-guide/development-models). + +## Configure Your Salesforce DX Project + +The `sfdx-project.json` file contains useful configuration information for your project. See [Salesforce DX Project Configuration](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_ws_config.htm) in the _Salesforce DX Developer Guide_ for details about this file. + +## Read All About It + +- [Salesforce Extensions Documentation](https://developer.salesforce.com/tools/vscode/) +- [Salesforce CLI Setup Guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_setup.meta/sfdx_setup/sfdx_setup_intro.htm) +- [Salesforce DX Developer Guide](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_intro.htm) +- [Salesforce CLI Command Reference](https://developer.salesforce.com/docs/atlas.en-us.sfdx_cli_reference.meta/sfdx_cli_reference/cli_reference.htm) diff --git a/blackroad-sf/blackroad-sf/br-banner.sh b/blackroad-sf/blackroad-sf/br-banner.sh new file mode 100644 index 0000000000..52dcd9650e --- /dev/null +++ b/blackroad-sf/blackroad-sf/br-banner.sh @@ -0,0 +1 @@ +echo "🖤 BlackRoad OS · Operator Controlled · Local First" diff --git a/blackroad-sf/blackroad-sf/br-menu b/blackroad-sf/blackroad-sf/br-menu new file mode 100755 index 0000000000..03e41425f7 --- /dev/null +++ b/blackroad-sf/blackroad-sf/br-menu @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +clear +echo "🖤 BlackRoad OS" +echo "━━━━━━━━━━━━━━━━━━━━━━" +echo "1️⃣ 📊 Status" +echo "2️⃣ 🌐 Network" +echo "3️⃣ 🧠 Agents" +echo "4️⃣ ⚙️ Config" +echo "5️⃣ 🚪 Exit" +echo +read -rp "👉 Select option: " choice + +case "$choice" in + 1) echo "📊 Status: ONLINE ✅" ;; + 2) echo "🌐 Network: LOCAL-FIRST 🟢" ;; + 3) echo "🧠 Agents: STANDING BY 🤖" ;; + 4) echo "⚙️ Config: LOCKED 🔐" ;; + 5) echo "👋 Goodbye"; exit 0 ;; + *) echo "❌ Invalid option" ;; +esac diff --git a/blackroad-sf/blackroad-sf/br-status.sh b/blackroad-sf/blackroad-sf/br-status.sh new file mode 100755 index 0000000000..26d4530604 --- /dev/null +++ b/blackroad-sf/blackroad-sf/br-status.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "🖤 BlackRoad OS" +echo "━━━━━━━━━━━━━━━━━━" +echo "📦 Shell : /bin/bash" +echo "🧠 Mode : Operator Controlled" +echo "🌐 Net : Local First" +echo "🔐 Trust : Sovereign" +echo +echo "✅ Status: ONLINE" diff --git a/blackroad-sf/blackroad-sf/config/project-scratch-def.json b/blackroad-sf/blackroad-sf/config/project-scratch-def.json new file mode 100644 index 0000000000..252ffbfca7 --- /dev/null +++ b/blackroad-sf/blackroad-sf/config/project-scratch-def.json @@ -0,0 +1,13 @@ +{ + "orgName": "alexa company", + "edition": "Developer", + "features": ["EnableSetPasswordInApi"], + "settings": { + "lightningExperienceSettings": { + "enableS1DesktopEnabled": true + }, + "mobileSettings": { + "enableS1EncryptedStoragePref2": false + } + } +} diff --git a/blackroad-sf/blackroad-sf/eslint.config.js b/blackroad-sf/blackroad-sf/eslint.config.js new file mode 100644 index 0000000000..a58c917fb8 --- /dev/null +++ b/blackroad-sf/blackroad-sf/eslint.config.js @@ -0,0 +1,55 @@ +const { defineConfig } = require('eslint/config'); +const eslintJs = require('@eslint/js'); +const jestPlugin = require('eslint-plugin-jest'); +const auraConfig = require('@salesforce/eslint-plugin-aura'); +const lwcConfig = require('@salesforce/eslint-config-lwc/recommended'); +const globals = require('globals'); + +module.exports = defineConfig([ + // Aura configuration + { + files: ['**/aura/**/*.js'], + extends: [ + ...auraConfig.configs.recommended, + ...auraConfig.configs.locker + ] + }, + + // LWC configuration + { + files: ['**/lwc/**/*.js'], + extends: [lwcConfig] + }, + + // LWC configuration with override for LWC test files + { + files: ['**/lwc/**/*.test.js'], + extends: [lwcConfig], + rules: { + '@lwc/lwc/no-unexpected-wire-adapter-usages': 'off' + }, + languageOptions: { + globals: { + ...globals.node + } + } + }, + + // Jest mocks configuration + { + files: ['**/jest-mocks/**/*.js'], + languageOptions: { + sourceType: 'module', + ecmaVersion: 'latest', + globals: { + ...globals.node, + ...globals.es2021, + ...jestPlugin.environments.globals.globals + } + }, + plugins: { + eslintJs + }, + extends: ['eslintJs/recommended'] + } +]); \ No newline at end of file diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/BlackRoad_CRM.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/BlackRoad_CRM.app-meta.xml new file mode 100644 index 0000000000..f043dab134 --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/BlackRoad_CRM.app-meta.xml @@ -0,0 +1,26 @@ + + + + #000000 + true + + BlackRoad CRM - Unified customer relationship management for General CRM and Agency CRM + Large + false + false + false + false + + Standard + standard-home + Company__c + Lead__c + Deal__c + Agent__c + Client__c + Policy__c + Listing__c + Commission__c + standard-report + Lightning + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/BlackRoad_Hub.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/BlackRoad_Hub.app-meta.xml new file mode 100644 index 0000000000..115a091eb0 --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/BlackRoad_Hub.app-meta.xml @@ -0,0 +1,27 @@ + + + + #000000 + true + + BlackRoad OS Hub - Financial Advisor CRM Command Center + Large + false + false + false + false + + Standard + standard-home + Client_Household__c + Financial_Account__c + Distribution_Request__c + Mortality_Event__c + Liquidity_Event__c + Compliance_Log__c + Connected_CRM__c + CRM_Product__c + standard-report + standard-Dashboard + Lightning + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/Lex.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/Lex.app-meta.xml new file mode 100644 index 0000000000..b64b5c7c6c --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/Lex.app-meta.xml @@ -0,0 +1,43 @@ + + + + #0070D2 + false + + Small + Large + false + false + true + false + + Console + standard-AppointmentInvitation + standard-CmsExperiences + standard-Event + standard-SalesforceJourney + standard-CmsWorkspaces + standard-Contact + Lightning + Advisors_UtilityBar + + + standard-AppointmentInvitation + + + standard-CmsExperiences + + + standard-CmsWorkspaces + + + standard-Contact + + + standard-Event + + + standard-SalesforceJourney + + + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__AllTabSet.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__AllTabSet.app-meta.xml new file mode 100644 index 0000000000..e6141af3ed --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__AllTabSet.app-meta.xml @@ -0,0 +1,8 @@ + + + standard-home + false + false + false + false + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__AppLauncher.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__AppLauncher.app-meta.xml new file mode 100644 index 0000000000..11f99ecacd --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__AppLauncher.app-meta.xml @@ -0,0 +1,9 @@ + + + standard-home + false + false + false + false + standard-AppLauncher + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Approvals.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Approvals.app-meta.xml new file mode 100644 index 0000000000..899ffb5003 --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Approvals.app-meta.xml @@ -0,0 +1,17 @@ + + + Large + false + false + false + false + + Console + standard-ApprovalsHome + Lightning + + + standard-ApprovalsHome + + + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Chatter.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Chatter.app-meta.xml new file mode 100644 index 0000000000..b7618dffbe --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Chatter.app-meta.xml @@ -0,0 +1,13 @@ + + + standard-home + false + false + false + false + standard-Chatter + standard-UserProfile + standard-OtherUserProfile + standard-CollaborationGroup + standard-File + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__CommerceV2.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__CommerceV2.app-meta.xml new file mode 100644 index 0000000000..679cefeb17 --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__CommerceV2.app-meta.xml @@ -0,0 +1,12 @@ + + + Large + false + false + false + false + + Standard + standard-CommerceStores + Lightning + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Community.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Community.app-meta.xml new file mode 100644 index 0000000000..78a06a5f12 --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Community.app-meta.xml @@ -0,0 +1,15 @@ + + + standard-home + false + false + false + false + standard-Chatter + standard-Contact + standard-Account + standard-Idea + standard-IdeaTheme + standard-report + standard-Dashboard + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Content.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Content.app-meta.xml new file mode 100644 index 0000000000..8dcb634c6c --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Content.app-meta.xml @@ -0,0 +1,12 @@ + + + standard-home + false + false + false + false + standard-Chatter + standard-Workspace + standard-ContentSearch + standard-ContentSubscriptions + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__FlowsApp.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__FlowsApp.app-meta.xml new file mode 100644 index 0000000000..30b76ff0bc --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__FlowsApp.app-meta.xml @@ -0,0 +1,34 @@ + + + + View + Action override created by Lightning App Builder during activation. + Securian_Contact + Large + false + Flexipage + Contact + + Large + false + false + false + false + + Standard + + View + Contact_Template + Large + Contact + Flexipage + Anypoint Integration + + standard-home + standard-FlowRecord + standard-FlowOrchestration + standard-Monitor + standard-ActionHub + Lightning + FlowsApp_UtilityBar + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__InventoryConsole.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__InventoryConsole.app-meta.xml new file mode 100644 index 0000000000..65886fb481 --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__InventoryConsole.app-meta.xml @@ -0,0 +1,26 @@ + + + Large + false + false + false + false + + Console + standard-home + standard-Location + standard-LocationGroup + Lightning + InventoryConsole_UtilityBar + + + standard-Location + + + standard-LocationGroup + + + standard-home + + + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__LightningBolt.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__LightningBolt.app-meta.xml new file mode 100644 index 0000000000..5f2bd7a443 --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__LightningBolt.app-meta.xml @@ -0,0 +1,12 @@ + + + Large + false + false + false + false + + Standard + standard-LightningBoltHome + Lightning + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__LightningInstrumentation.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__LightningInstrumentation.app-meta.xml new file mode 100644 index 0000000000..1a1e6a2a4c --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__LightningInstrumentation.app-meta.xml @@ -0,0 +1,12 @@ + + + Large + false + false + false + false + + Standard + standard-LightningInstrumentation + Lightning + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__LightningSales.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__LightningSales.app-meta.xml new file mode 100644 index 0000000000..168a2fd5cb --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__LightningSales.app-meta.xml @@ -0,0 +1,88 @@ + + + + View + Action override created by Lightning App Builder during activation. + Securian_Contact + Large + false + Flexipage + Contact + + Small + Large + false + false + false + false + + Standard + + View + Contact_Template + Small + Contact + Flexipage + Custom: Sales Profile + + + View + Contact_Template + Large + Contact + Flexipage + Custom: Sales Profile + + + View + Contact_Template + Small + Contact + Flexipage + Customer Community Login User + + + View + Contact_Template + Large + Contact + Flexipage + Customer Community Login User + + + View + Contact_Template + Small + Contact + Flexipage + Anypoint Integration + + + View + Contact_Template + Large + Contact + Flexipage + Anypoint Integration + + standard-home + standard-WaveHomeLightning + standard-Opportunity + standard-Lead + standard-Task + standard-File + standard-Account + standard-Contact + standard-Campaign + standard-Dashboard + standard-report + standard-Feed + standard-CollaborationGroup + standard-Event + standard-OtherUserProfile + standard-Case + standard-Forecasting3 + standard-EmailTemplate + Lightning + LightningSales_UtilityBar + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__LightningScheduler.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__LightningScheduler.app-meta.xml new file mode 100644 index 0000000000..214bc8b984 --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__LightningScheduler.app-meta.xml @@ -0,0 +1,26 @@ + + + Large + false + false + false + false + + Standard + + View + Contact_Template + Large + Contact + Flexipage + Anypoint Integration + + standard-LightningSchedulerSetupAssistant + standard-ServiceTerritory + standard-ServiceResource + standard-WorkTypeGroup + standard-WorkType + standard-OperatingHours + standard-ServiceAppointment + Lightning + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__MSJApp.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__MSJApp.app-meta.xml new file mode 100644 index 0000000000..639fe7b432 --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__MSJApp.app-meta.xml @@ -0,0 +1,25 @@ + + + Large + false + false + false + false + + Console + standard-JourneyHome + standard-SalesforceJourney + standard-JourneyMap + Lightning + + + standard-JourneyHome + + + standard-JourneyMap + + + standard-SalesforceJourney + + + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Marketing.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Marketing.app-meta.xml new file mode 100644 index 0000000000..e5a10fff87 --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Marketing.app-meta.xml @@ -0,0 +1,17 @@ + + + standard-home + Large + false + false + false + false + standard-Chatter + standard-Campaign + standard-Lead + standard-Contact + standard-Opportunity + standard-report + standard-Dashboard + Aloha + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__OMConsole.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__OMConsole.app-meta.xml new file mode 100644 index 0000000000..942b86b2b4 --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__OMConsole.app-meta.xml @@ -0,0 +1,50 @@ + + + Large + false + false + false + false + + Console + standard-OrderSummary + standard-OrderPaymentSummary + standard-FulfillmentOrder + standard-ReturnOrder + standard-Account + standard-Invoice + standard-CreditMemo + standard-Location + standard-ProcessException + Lightning + OMConsole_UtilityBar + + + standard-Account + + + standard-CreditMemo + + + standard-FulfillmentOrder + + + standard-Invoice + + + standard-Location + + + standard-OrderPaymentSummary + + + standard-OrderSummary + + + standard-ProcessException + + + standard-ReturnOrder + + + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Optimizer.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Optimizer.app-meta.xml new file mode 100644 index 0000000000..dce7043e6a --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Optimizer.app-meta.xml @@ -0,0 +1,17 @@ + + + Large + false + false + false + false + + Console + standard-OrgMetric + Lightning + + + standard-OrgMetric + + + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__PaymentsManagement.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__PaymentsManagement.app-meta.xml new file mode 100644 index 0000000000..9bf4ccb7ab --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__PaymentsManagement.app-meta.xml @@ -0,0 +1,29 @@ + + + Large + false + false + false + false + + Console + standard-home + standard-report + standard-PaymentsWorkspace + standard-PaymentsSettings + Lightning + + + standard-PaymentsSettings + + + standard-PaymentsWorkspace + + + standard-home + + + standard-report + + + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Platform.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Platform.app-meta.xml new file mode 100644 index 0000000000..8d5534e4d9 --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Platform.app-meta.xml @@ -0,0 +1,14 @@ + + + standard-home + false + false + false + false + standard-Chatter + standard-Account + standard-Contact + standard-report + standard-Dashboard + Aloha + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__PreconfiguredLightningServiceConsole.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__PreconfiguredLightningServiceConsole.app-meta.xml new file mode 100644 index 0000000000..cf36e03fd3 --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__PreconfiguredLightningServiceConsole.app-meta.xml @@ -0,0 +1,115 @@ + + + + View + Action override updated by Lightning App Builder during activation. + Securian_Contact + Large + false + Flexipage + Contact + + Large + false + false + false + false + + Console + + View + CaseDefault_Record_Page + Large + Case + Flexipage + Service Agent + + + View + CaseDefault_Record_Page + Large + Case + Flexipage + Service Supervisor + + + View + CaseDefault_Record_Page + Large + Case + Flexipage + Admin + + + View + Contact_Template + Large + Contact + Flexipage + Custom: Sales Profile + + + View + Contact_Template + Large + Contact + Flexipage + Customer Community Login User + + + View + Contact_Template + Large + Contact + Flexipage + Anypoint Integration + + standard-Case + standard-home + standard-Contact + standard-Account + standard-MessagingSession + standard-OmniSupervisor + standard-CmsWorkspaces + standard-CmsExperiences + standard-Macro + standard-LightningQuickText + standard-Survey + Lightning + PreconfiguredLightningServiceConsole_UtilityBar + + + standard-Account + + + standard-Case + + + standard-CmsExperiences + + + standard-CmsWorkspaces + + + standard-Contact + + + standard-LightningQuickText + + + standard-Macro + + + standard-MessagingSession + + + standard-OmniSupervisor + + + standard-Survey + + + standard-home + + + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Sales.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Sales.app-meta.xml new file mode 100644 index 0000000000..546a5cfc66 --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Sales.app-meta.xml @@ -0,0 +1,25 @@ + + + standard-home + false + false + false + false + standard-Chatter + standard-Campaign + standard-Lead + standard-Account + standard-Contact + standard-Opportunity + standard-Forecasting3 + standard-Contract + standard-Order + standard-Invoice + standard-Case + standard-Solution + standard-Product2 + standard-report + standard-Dashboard + standard-OmnichannelInventory + Aloha + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__SalesCloudMobile.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__SalesCloudMobile.app-meta.xml new file mode 100644 index 0000000000..f34e9c673a --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__SalesCloudMobile.app-meta.xml @@ -0,0 +1,26 @@ + + + Small + false + false + false + false + + Standard + standard-MobileHome + standard-home + standard-Contact + standard-Opportunity + standard-Account + standard-Lead + standard-Campaign + standard-Task + standard-Event + standard-report + standard-Dashboard + standard-Case + standard-Feed + standard-CollaborationGroup + standard-File + Lightning + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__SalesforceCMS.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__SalesforceCMS.app-meta.xml new file mode 100644 index 0000000000..a1ce520553 --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__SalesforceCMS.app-meta.xml @@ -0,0 +1,29 @@ + + + Large + false + false + false + false + + Console + standard-CmsAuthorHome + standard-CmsWorkspaces + standard-CmsExperiences + standard-CmsChannel + Lightning + + + standard-CmsAuthorHome + + + standard-CmsChannel + + + standard-CmsExperiences + + + standard-CmsWorkspaces + + + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Service.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Service.app-meta.xml new file mode 100644 index 0000000000..5c1b3cac22 --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Service.app-meta.xml @@ -0,0 +1,17 @@ + + + standard-home + Large + false + false + false + false + standard-Chatter + standard-Account + standard-Contact + standard-Case + standard-Solution + standard-report + standard-Dashboard + Aloha + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__ServiceConsole.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__ServiceConsole.app-meta.xml new file mode 100644 index 0000000000..41c308822c --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__ServiceConsole.app-meta.xml @@ -0,0 +1,205 @@ + + + + none + + + FOCUS_CONSOLE + true + ESC + + + FOCUS_NAVIGATOR_TAB + true + V + + + FOCUS_DETAIL_VIEW + true + SHIFT+S + + + FOCUS_PRIMARY_TAB_PANEL + true + P + + + FOCUS_SUBTAB_PANEL + true + S + + + FOCUS_LIST_VIEW + true + N + + + FOCUS_FIRST_LIST_VIEW + true + SHIFT+F + + + FOCUS_SEARCH_INPUT + true + R + + + MOVE_LEFT + true + LEFT ARROW + + + MOVE_RIGHT + true + RIGHT ARROW + + + UP_ARROW + true + UP ARROW + + + DOWN_ARROW + true + DOWN ARROW + + + OPEN_TAB_SCROLLER_MENU + true + D + + + OPEN_TAB + true + T + + + CLOSE_TAB + true + C + + + REFRESH_TAB + false + SHIFT+R + + + ENTER + true + ENTER + + + EDIT + true + E + + + SAVE + true + CTRL+S + + + CONSOLE_LINK_DIALOG + false + U + + + HOTKEYS_PANEL + false + SHIFT+K + + + FOCUS_MACRO + false + M + + + FOCUS_FOOTER_PANEL + false + F + + + TOGGLE_LIST_VIEW + false + SHIFT+N + + + TOGGLE_LEFT_SIDEBAR + false + SHIFT+LEFT ARROW + + + TOGGLE_RIGHT_SIDEBAR + false + SHIFT+RIGHT ARROW + + + TOGGLE_TOP_SIDEBAR + false + SHIFT+UP ARROW + + + TOGGLE_BOTTOM_SIDEBAR + false + SHIFT+DOWN ARROW + + + TOGGLE_APP_LEVEL_COMPONENTS + false + Z + + + REOPEN_LAST_TAB + false + SHIFT+C + + + + full + + none + + standard-home + false + false + false + false + true + + false + true + true + true + true + true + false + false + true + + standard-Account + standard-Contact + standard-Case + standard-Opportunity + standard-Lead + Aloha + + + standard-Account + + + AccountId + standard-Case + + + AccountId + standard-Contact + + + standard-Lead + + + AccountId + standard-Opportunity + + + diff --git a/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Sites.app-meta.xml b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Sites.app-meta.xml new file mode 100644 index 0000000000..17d187f052 --- /dev/null +++ b/blackroad-sf/blackroad-sf/force-app/main/default/applications/standard__Sites.app-meta.xml @@ -0,0 +1,10 @@ + + + standard-home + false + false + false + false + standard-Chatter + standard-Sites + diff --git a/blackroad-sf/blackroad-sf/hello.sh b/blackroad-sf/blackroad-sf/hello.sh new file mode 100644 index 0000000000..0d00d6e964 --- /dev/null +++ b/blackroad-sf/blackroad-sf/hello.sh @@ -0,0 +1,2 @@ +#!/bin/bash +echo "👋 hello from /bin/bash" diff --git a/blackroad-sf/blackroad-sf/jest.config.js b/blackroad-sf/blackroad-sf/jest.config.js new file mode 100644 index 0000000000..f5a9fed2b5 --- /dev/null +++ b/blackroad-sf/blackroad-sf/jest.config.js @@ -0,0 +1,6 @@ +const { jestConfig } = require('@salesforce/sfdx-lwc-jest/config'); + +module.exports = { + ...jestConfig, + modulePathIgnorePatterns: ['/.localdevserver'] +}; diff --git a/blackroad-sf/blackroad-sf/package.json b/blackroad-sf/blackroad-sf/package.json new file mode 100644 index 0000000000..0d77cdc7ed --- /dev/null +++ b/blackroad-sf/blackroad-sf/package.json @@ -0,0 +1,44 @@ +{ + "name": "salesforce-app", + "private": true, + "version": "1.0.0", + "description": "Salesforce App", + "scripts": { + "lint": "eslint **/{aura,lwc}/**/*.js", + "test": "npm run test:unit", + "test:unit": "sfdx-lwc-jest", + "test:unit:watch": "sfdx-lwc-jest --watch", + "test:unit:debug": "sfdx-lwc-jest --debug", + "test:unit:coverage": "sfdx-lwc-jest --coverage", + "prettier": "prettier --write \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"", + "prettier:verify": "prettier --check \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"", + "prepare": "husky || true", + "precommit": "lint-staged" + }, + "devDependencies": { + "@lwc/eslint-plugin-lwc": "^3.1.0", + "@prettier/plugin-xml": "^3.4.1", + "@salesforce/eslint-config-lwc": "^4.0.0", + "@salesforce/eslint-plugin-aura": "^3.0.0", + "@salesforce/eslint-plugin-lightning": "^2.0.0", + "@salesforce/sfdx-lwc-jest": "^7.0.2", + "eslint": "^9.29.0", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jest": "^28.14.0", + "husky": "^9.1.7", + "lint-staged": "^16.1.2", + "prettier": "^3.5.3", + "prettier-plugin-apex": "^2.2.6" + }, + "lint-staged": { + "**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}": [ + "prettier --write" + ], + "**/{aura,lwc}/**/*.js": [ + "eslint" + ], + "**/lwc/**": [ + "sfdx-lwc-jest -- --bail --findRelatedTests --passWithNoTests" + ] + } +} diff --git a/blackroad-sf/blackroad-sf/scripts/apex/hello.apex b/blackroad-sf/blackroad-sf/scripts/apex/hello.apex new file mode 100644 index 0000000000..1fba732502 --- /dev/null +++ b/blackroad-sf/blackroad-sf/scripts/apex/hello.apex @@ -0,0 +1,10 @@ +// Use .apex files to store anonymous Apex. +// You can execute anonymous Apex in VS Code by selecting the +// apex text and running the command: +// SFDX: Execute Anonymous Apex with Currently Selected Text +// You can also execute the entire file by running the command: +// SFDX: Execute Anonymous Apex with Editor Contents + +string tempvar = 'Enter_your_name_here'; +System.debug('Hello World!'); +System.debug('My name is ' + tempvar); \ No newline at end of file diff --git a/blackroad-sf/blackroad-sf/scripts/soql/account.soql b/blackroad-sf/blackroad-sf/scripts/soql/account.soql new file mode 100644 index 0000000000..10d4b9c78d --- /dev/null +++ b/blackroad-sf/blackroad-sf/scripts/soql/account.soql @@ -0,0 +1,6 @@ +// Use .soql files to store SOQL queries. +// You can execute queries in VS Code by selecting the +// query text and running the command: +// SFDX: Execute SOQL Query with Currently Selected Text + +SELECT Id, Name FROM Account diff --git a/blackroad-sf/blackroad-sf/service/app.sh b/blackroad-sf/blackroad-sf/service/app.sh new file mode 100755 index 0000000000..7cac5ac9e6 --- /dev/null +++ b/blackroad-sf/blackroad-sf/service/app.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail + +source "$(dirname "$0")/config.env" + +while true; do + echo "🧠 working..." + sleep "$INTERVAL" +done diff --git a/blackroad-sf/blackroad-sf/service/config.env b/blackroad-sf/blackroad-sf/service/config.env new file mode 100644 index 0000000000..ad48d61311 --- /dev/null +++ b/blackroad-sf/blackroad-sf/service/config.env @@ -0,0 +1,2 @@ +APP_NAME=myapp +INTERVAL=5 diff --git a/blackroad-sf/blackroad-sf/service/myapp.service b/blackroad-sf/blackroad-sf/service/myapp.service new file mode 100644 index 0000000000..67b8264aeb --- /dev/null +++ b/blackroad-sf/blackroad-sf/service/myapp.service @@ -0,0 +1,11 @@ +[Unit] +Description=My App Service +After=network.target + +[Service] +ExecStart=/usr/bin/env bash /opt/myapp/app.sh +Restart=always +EnvironmentFile=/opt/myapp/config.env + +[Install] +WantedBy=multi-user.target diff --git a/blackroad-sf/blackroad-sf/sfdx-project.json b/blackroad-sf/blackroad-sf/sfdx-project.json new file mode 100644 index 0000000000..1e1c2890c2 --- /dev/null +++ b/blackroad-sf/blackroad-sf/sfdx-project.json @@ -0,0 +1,12 @@ +{ + "packageDirectories": [ + { + "path": "force-app", + "default": true + } + ], + "name": "blackroad-sf", + "namespace": "", + "sfdcLoginUrl": "https://login.salesforce.com", + "sourceApiVersion": "65.0" +} diff --git a/blackroad-sf/config/project-scratch-def.json b/blackroad-sf/config/project-scratch-def.json new file mode 100644 index 0000000000..252ffbfca7 --- /dev/null +++ b/blackroad-sf/config/project-scratch-def.json @@ -0,0 +1,13 @@ +{ + "orgName": "alexa company", + "edition": "Developer", + "features": ["EnableSetPasswordInApi"], + "settings": { + "lightningExperienceSettings": { + "enableS1DesktopEnabled": true + }, + "mobileSettings": { + "enableS1EncryptedStoragePref2": false + } + } +} diff --git a/blackroad-sf/eslint.config.js b/blackroad-sf/eslint.config.js new file mode 100644 index 0000000000..a58c917fb8 --- /dev/null +++ b/blackroad-sf/eslint.config.js @@ -0,0 +1,55 @@ +const { defineConfig } = require('eslint/config'); +const eslintJs = require('@eslint/js'); +const jestPlugin = require('eslint-plugin-jest'); +const auraConfig = require('@salesforce/eslint-plugin-aura'); +const lwcConfig = require('@salesforce/eslint-config-lwc/recommended'); +const globals = require('globals'); + +module.exports = defineConfig([ + // Aura configuration + { + files: ['**/aura/**/*.js'], + extends: [ + ...auraConfig.configs.recommended, + ...auraConfig.configs.locker + ] + }, + + // LWC configuration + { + files: ['**/lwc/**/*.js'], + extends: [lwcConfig] + }, + + // LWC configuration with override for LWC test files + { + files: ['**/lwc/**/*.test.js'], + extends: [lwcConfig], + rules: { + '@lwc/lwc/no-unexpected-wire-adapter-usages': 'off' + }, + languageOptions: { + globals: { + ...globals.node + } + } + }, + + // Jest mocks configuration + { + files: ['**/jest-mocks/**/*.js'], + languageOptions: { + sourceType: 'module', + ecmaVersion: 'latest', + globals: { + ...globals.node, + ...globals.es2021, + ...jestPlugin.environments.globals.globals + } + }, + plugins: { + eslintJs + }, + extends: ['eslintJs/recommended'] + } +]); \ No newline at end of file diff --git a/blackroad-sf/force-app/main/default/applications/BlackRoad_CRM.app-meta.xml b/blackroad-sf/force-app/main/default/applications/BlackRoad_CRM.app-meta.xml new file mode 100644 index 0000000000..f043dab134 --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/BlackRoad_CRM.app-meta.xml @@ -0,0 +1,26 @@ + + + + #000000 + true + + BlackRoad CRM - Unified customer relationship management for General CRM and Agency CRM + Large + false + false + false + false + + Standard + standard-home + Company__c + Lead__c + Deal__c + Agent__c + Client__c + Policy__c + Listing__c + Commission__c + standard-report + Lightning + diff --git a/blackroad-sf/force-app/main/default/applications/BlackRoad_Hub.app-meta.xml b/blackroad-sf/force-app/main/default/applications/BlackRoad_Hub.app-meta.xml new file mode 100644 index 0000000000..115a091eb0 --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/BlackRoad_Hub.app-meta.xml @@ -0,0 +1,27 @@ + + + + #000000 + true + + BlackRoad OS Hub - Financial Advisor CRM Command Center + Large + false + false + false + false + + Standard + standard-home + Client_Household__c + Financial_Account__c + Distribution_Request__c + Mortality_Event__c + Liquidity_Event__c + Compliance_Log__c + Connected_CRM__c + CRM_Product__c + standard-report + standard-Dashboard + Lightning + diff --git a/blackroad-sf/force-app/main/default/applications/Lex.app-meta.xml b/blackroad-sf/force-app/main/default/applications/Lex.app-meta.xml new file mode 100644 index 0000000000..b64b5c7c6c --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/Lex.app-meta.xml @@ -0,0 +1,43 @@ + + + + #0070D2 + false + + Small + Large + false + false + true + false + + Console + standard-AppointmentInvitation + standard-CmsExperiences + standard-Event + standard-SalesforceJourney + standard-CmsWorkspaces + standard-Contact + Lightning + Advisors_UtilityBar + + + standard-AppointmentInvitation + + + standard-CmsExperiences + + + standard-CmsWorkspaces + + + standard-Contact + + + standard-Event + + + standard-SalesforceJourney + + + diff --git a/blackroad-sf/force-app/main/default/applications/standard__AllTabSet.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__AllTabSet.app-meta.xml new file mode 100644 index 0000000000..e6141af3ed --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__AllTabSet.app-meta.xml @@ -0,0 +1,8 @@ + + + standard-home + false + false + false + false + diff --git a/blackroad-sf/force-app/main/default/applications/standard__AppLauncher.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__AppLauncher.app-meta.xml new file mode 100644 index 0000000000..11f99ecacd --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__AppLauncher.app-meta.xml @@ -0,0 +1,9 @@ + + + standard-home + false + false + false + false + standard-AppLauncher + diff --git a/blackroad-sf/force-app/main/default/applications/standard__Approvals.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__Approvals.app-meta.xml new file mode 100644 index 0000000000..899ffb5003 --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__Approvals.app-meta.xml @@ -0,0 +1,17 @@ + + + Large + false + false + false + false + + Console + standard-ApprovalsHome + Lightning + + + standard-ApprovalsHome + + + diff --git a/blackroad-sf/force-app/main/default/applications/standard__Chatter.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__Chatter.app-meta.xml new file mode 100644 index 0000000000..b7618dffbe --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__Chatter.app-meta.xml @@ -0,0 +1,13 @@ + + + standard-home + false + false + false + false + standard-Chatter + standard-UserProfile + standard-OtherUserProfile + standard-CollaborationGroup + standard-File + diff --git a/blackroad-sf/force-app/main/default/applications/standard__CommerceV2.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__CommerceV2.app-meta.xml new file mode 100644 index 0000000000..679cefeb17 --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__CommerceV2.app-meta.xml @@ -0,0 +1,12 @@ + + + Large + false + false + false + false + + Standard + standard-CommerceStores + Lightning + diff --git a/blackroad-sf/force-app/main/default/applications/standard__Community.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__Community.app-meta.xml new file mode 100644 index 0000000000..78a06a5f12 --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__Community.app-meta.xml @@ -0,0 +1,15 @@ + + + standard-home + false + false + false + false + standard-Chatter + standard-Contact + standard-Account + standard-Idea + standard-IdeaTheme + standard-report + standard-Dashboard + diff --git a/blackroad-sf/force-app/main/default/applications/standard__Content.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__Content.app-meta.xml new file mode 100644 index 0000000000..8dcb634c6c --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__Content.app-meta.xml @@ -0,0 +1,12 @@ + + + standard-home + false + false + false + false + standard-Chatter + standard-Workspace + standard-ContentSearch + standard-ContentSubscriptions + diff --git a/blackroad-sf/force-app/main/default/applications/standard__FlowsApp.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__FlowsApp.app-meta.xml new file mode 100644 index 0000000000..30b76ff0bc --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__FlowsApp.app-meta.xml @@ -0,0 +1,34 @@ + + + + View + Action override created by Lightning App Builder during activation. + Securian_Contact + Large + false + Flexipage + Contact + + Large + false + false + false + false + + Standard + + View + Contact_Template + Large + Contact + Flexipage + Anypoint Integration + + standard-home + standard-FlowRecord + standard-FlowOrchestration + standard-Monitor + standard-ActionHub + Lightning + FlowsApp_UtilityBar + diff --git a/blackroad-sf/force-app/main/default/applications/standard__InventoryConsole.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__InventoryConsole.app-meta.xml new file mode 100644 index 0000000000..65886fb481 --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__InventoryConsole.app-meta.xml @@ -0,0 +1,26 @@ + + + Large + false + false + false + false + + Console + standard-home + standard-Location + standard-LocationGroup + Lightning + InventoryConsole_UtilityBar + + + standard-Location + + + standard-LocationGroup + + + standard-home + + + diff --git a/blackroad-sf/force-app/main/default/applications/standard__LightningBolt.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__LightningBolt.app-meta.xml new file mode 100644 index 0000000000..5f2bd7a443 --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__LightningBolt.app-meta.xml @@ -0,0 +1,12 @@ + + + Large + false + false + false + false + + Standard + standard-LightningBoltHome + Lightning + diff --git a/blackroad-sf/force-app/main/default/applications/standard__LightningInstrumentation.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__LightningInstrumentation.app-meta.xml new file mode 100644 index 0000000000..1a1e6a2a4c --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__LightningInstrumentation.app-meta.xml @@ -0,0 +1,12 @@ + + + Large + false + false + false + false + + Standard + standard-LightningInstrumentation + Lightning + diff --git a/blackroad-sf/force-app/main/default/applications/standard__LightningSales.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__LightningSales.app-meta.xml new file mode 100644 index 0000000000..168a2fd5cb --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__LightningSales.app-meta.xml @@ -0,0 +1,88 @@ + + + + View + Action override created by Lightning App Builder during activation. + Securian_Contact + Large + false + Flexipage + Contact + + Small + Large + false + false + false + false + + Standard + + View + Contact_Template + Small + Contact + Flexipage + Custom: Sales Profile + + + View + Contact_Template + Large + Contact + Flexipage + Custom: Sales Profile + + + View + Contact_Template + Small + Contact + Flexipage + Customer Community Login User + + + View + Contact_Template + Large + Contact + Flexipage + Customer Community Login User + + + View + Contact_Template + Small + Contact + Flexipage + Anypoint Integration + + + View + Contact_Template + Large + Contact + Flexipage + Anypoint Integration + + standard-home + standard-WaveHomeLightning + standard-Opportunity + standard-Lead + standard-Task + standard-File + standard-Account + standard-Contact + standard-Campaign + standard-Dashboard + standard-report + standard-Feed + standard-CollaborationGroup + standard-Event + standard-OtherUserProfile + standard-Case + standard-Forecasting3 + standard-EmailTemplate + Lightning + LightningSales_UtilityBar + diff --git a/blackroad-sf/force-app/main/default/applications/standard__LightningScheduler.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__LightningScheduler.app-meta.xml new file mode 100644 index 0000000000..214bc8b984 --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__LightningScheduler.app-meta.xml @@ -0,0 +1,26 @@ + + + Large + false + false + false + false + + Standard + + View + Contact_Template + Large + Contact + Flexipage + Anypoint Integration + + standard-LightningSchedulerSetupAssistant + standard-ServiceTerritory + standard-ServiceResource + standard-WorkTypeGroup + standard-WorkType + standard-OperatingHours + standard-ServiceAppointment + Lightning + diff --git a/blackroad-sf/force-app/main/default/applications/standard__MSJApp.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__MSJApp.app-meta.xml new file mode 100644 index 0000000000..639fe7b432 --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__MSJApp.app-meta.xml @@ -0,0 +1,25 @@ + + + Large + false + false + false + false + + Console + standard-JourneyHome + standard-SalesforceJourney + standard-JourneyMap + Lightning + + + standard-JourneyHome + + + standard-JourneyMap + + + standard-SalesforceJourney + + + diff --git a/blackroad-sf/force-app/main/default/applications/standard__Marketing.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__Marketing.app-meta.xml new file mode 100644 index 0000000000..e5a10fff87 --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__Marketing.app-meta.xml @@ -0,0 +1,17 @@ + + + standard-home + Large + false + false + false + false + standard-Chatter + standard-Campaign + standard-Lead + standard-Contact + standard-Opportunity + standard-report + standard-Dashboard + Aloha + diff --git a/blackroad-sf/force-app/main/default/applications/standard__OMConsole.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__OMConsole.app-meta.xml new file mode 100644 index 0000000000..942b86b2b4 --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__OMConsole.app-meta.xml @@ -0,0 +1,50 @@ + + + Large + false + false + false + false + + Console + standard-OrderSummary + standard-OrderPaymentSummary + standard-FulfillmentOrder + standard-ReturnOrder + standard-Account + standard-Invoice + standard-CreditMemo + standard-Location + standard-ProcessException + Lightning + OMConsole_UtilityBar + + + standard-Account + + + standard-CreditMemo + + + standard-FulfillmentOrder + + + standard-Invoice + + + standard-Location + + + standard-OrderPaymentSummary + + + standard-OrderSummary + + + standard-ProcessException + + + standard-ReturnOrder + + + diff --git a/blackroad-sf/force-app/main/default/applications/standard__Optimizer.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__Optimizer.app-meta.xml new file mode 100644 index 0000000000..dce7043e6a --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__Optimizer.app-meta.xml @@ -0,0 +1,17 @@ + + + Large + false + false + false + false + + Console + standard-OrgMetric + Lightning + + + standard-OrgMetric + + + diff --git a/blackroad-sf/force-app/main/default/applications/standard__PaymentsManagement.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__PaymentsManagement.app-meta.xml new file mode 100644 index 0000000000..9bf4ccb7ab --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__PaymentsManagement.app-meta.xml @@ -0,0 +1,29 @@ + + + Large + false + false + false + false + + Console + standard-home + standard-report + standard-PaymentsWorkspace + standard-PaymentsSettings + Lightning + + + standard-PaymentsSettings + + + standard-PaymentsWorkspace + + + standard-home + + + standard-report + + + diff --git a/blackroad-sf/force-app/main/default/applications/standard__Platform.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__Platform.app-meta.xml new file mode 100644 index 0000000000..8d5534e4d9 --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__Platform.app-meta.xml @@ -0,0 +1,14 @@ + + + standard-home + false + false + false + false + standard-Chatter + standard-Account + standard-Contact + standard-report + standard-Dashboard + Aloha + diff --git a/blackroad-sf/force-app/main/default/applications/standard__PreconfiguredLightningServiceConsole.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__PreconfiguredLightningServiceConsole.app-meta.xml new file mode 100644 index 0000000000..cf36e03fd3 --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__PreconfiguredLightningServiceConsole.app-meta.xml @@ -0,0 +1,115 @@ + + + + View + Action override updated by Lightning App Builder during activation. + Securian_Contact + Large + false + Flexipage + Contact + + Large + false + false + false + false + + Console + + View + CaseDefault_Record_Page + Large + Case + Flexipage + Service Agent + + + View + CaseDefault_Record_Page + Large + Case + Flexipage + Service Supervisor + + + View + CaseDefault_Record_Page + Large + Case + Flexipage + Admin + + + View + Contact_Template + Large + Contact + Flexipage + Custom: Sales Profile + + + View + Contact_Template + Large + Contact + Flexipage + Customer Community Login User + + + View + Contact_Template + Large + Contact + Flexipage + Anypoint Integration + + standard-Case + standard-home + standard-Contact + standard-Account + standard-MessagingSession + standard-OmniSupervisor + standard-CmsWorkspaces + standard-CmsExperiences + standard-Macro + standard-LightningQuickText + standard-Survey + Lightning + PreconfiguredLightningServiceConsole_UtilityBar + + + standard-Account + + + standard-Case + + + standard-CmsExperiences + + + standard-CmsWorkspaces + + + standard-Contact + + + standard-LightningQuickText + + + standard-Macro + + + standard-MessagingSession + + + standard-OmniSupervisor + + + standard-Survey + + + standard-home + + + diff --git a/blackroad-sf/force-app/main/default/applications/standard__Sales.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__Sales.app-meta.xml new file mode 100644 index 0000000000..546a5cfc66 --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__Sales.app-meta.xml @@ -0,0 +1,25 @@ + + + standard-home + false + false + false + false + standard-Chatter + standard-Campaign + standard-Lead + standard-Account + standard-Contact + standard-Opportunity + standard-Forecasting3 + standard-Contract + standard-Order + standard-Invoice + standard-Case + standard-Solution + standard-Product2 + standard-report + standard-Dashboard + standard-OmnichannelInventory + Aloha + diff --git a/blackroad-sf/force-app/main/default/applications/standard__SalesCloudMobile.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__SalesCloudMobile.app-meta.xml new file mode 100644 index 0000000000..f34e9c673a --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__SalesCloudMobile.app-meta.xml @@ -0,0 +1,26 @@ + + + Small + false + false + false + false + + Standard + standard-MobileHome + standard-home + standard-Contact + standard-Opportunity + standard-Account + standard-Lead + standard-Campaign + standard-Task + standard-Event + standard-report + standard-Dashboard + standard-Case + standard-Feed + standard-CollaborationGroup + standard-File + Lightning + diff --git a/blackroad-sf/force-app/main/default/applications/standard__SalesforceCMS.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__SalesforceCMS.app-meta.xml new file mode 100644 index 0000000000..a1ce520553 --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__SalesforceCMS.app-meta.xml @@ -0,0 +1,29 @@ + + + Large + false + false + false + false + + Console + standard-CmsAuthorHome + standard-CmsWorkspaces + standard-CmsExperiences + standard-CmsChannel + Lightning + + + standard-CmsAuthorHome + + + standard-CmsChannel + + + standard-CmsExperiences + + + standard-CmsWorkspaces + + + diff --git a/blackroad-sf/force-app/main/default/applications/standard__Service.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__Service.app-meta.xml new file mode 100644 index 0000000000..5c1b3cac22 --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__Service.app-meta.xml @@ -0,0 +1,17 @@ + + + standard-home + Large + false + false + false + false + standard-Chatter + standard-Account + standard-Contact + standard-Case + standard-Solution + standard-report + standard-Dashboard + Aloha + diff --git a/blackroad-sf/force-app/main/default/applications/standard__ServiceConsole.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__ServiceConsole.app-meta.xml new file mode 100644 index 0000000000..41c308822c --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__ServiceConsole.app-meta.xml @@ -0,0 +1,205 @@ + + + + none + + + FOCUS_CONSOLE + true + ESC + + + FOCUS_NAVIGATOR_TAB + true + V + + + FOCUS_DETAIL_VIEW + true + SHIFT+S + + + FOCUS_PRIMARY_TAB_PANEL + true + P + + + FOCUS_SUBTAB_PANEL + true + S + + + FOCUS_LIST_VIEW + true + N + + + FOCUS_FIRST_LIST_VIEW + true + SHIFT+F + + + FOCUS_SEARCH_INPUT + true + R + + + MOVE_LEFT + true + LEFT ARROW + + + MOVE_RIGHT + true + RIGHT ARROW + + + UP_ARROW + true + UP ARROW + + + DOWN_ARROW + true + DOWN ARROW + + + OPEN_TAB_SCROLLER_MENU + true + D + + + OPEN_TAB + true + T + + + CLOSE_TAB + true + C + + + REFRESH_TAB + false + SHIFT+R + + + ENTER + true + ENTER + + + EDIT + true + E + + + SAVE + true + CTRL+S + + + CONSOLE_LINK_DIALOG + false + U + + + HOTKEYS_PANEL + false + SHIFT+K + + + FOCUS_MACRO + false + M + + + FOCUS_FOOTER_PANEL + false + F + + + TOGGLE_LIST_VIEW + false + SHIFT+N + + + TOGGLE_LEFT_SIDEBAR + false + SHIFT+LEFT ARROW + + + TOGGLE_RIGHT_SIDEBAR + false + SHIFT+RIGHT ARROW + + + TOGGLE_TOP_SIDEBAR + false + SHIFT+UP ARROW + + + TOGGLE_BOTTOM_SIDEBAR + false + SHIFT+DOWN ARROW + + + TOGGLE_APP_LEVEL_COMPONENTS + false + Z + + + REOPEN_LAST_TAB + false + SHIFT+C + + + + full + + none + + standard-home + false + false + false + false + true + + false + true + true + true + true + true + false + false + true + + standard-Account + standard-Contact + standard-Case + standard-Opportunity + standard-Lead + Aloha + + + standard-Account + + + AccountId + standard-Case + + + AccountId + standard-Contact + + + standard-Lead + + + AccountId + standard-Opportunity + + + diff --git a/blackroad-sf/force-app/main/default/applications/standard__Sites.app-meta.xml b/blackroad-sf/force-app/main/default/applications/standard__Sites.app-meta.xml new file mode 100644 index 0000000000..17d187f052 --- /dev/null +++ b/blackroad-sf/force-app/main/default/applications/standard__Sites.app-meta.xml @@ -0,0 +1,10 @@ + + + standard-home + false + false + false + false + standard-Chatter + standard-Sites + diff --git a/blackroad-sf/jest.config.js b/blackroad-sf/jest.config.js new file mode 100644 index 0000000000..f5a9fed2b5 --- /dev/null +++ b/blackroad-sf/jest.config.js @@ -0,0 +1,6 @@ +const { jestConfig } = require('@salesforce/sfdx-lwc-jest/config'); + +module.exports = { + ...jestConfig, + modulePathIgnorePatterns: ['/.localdevserver'] +}; diff --git a/blackroad-sf/package.json b/blackroad-sf/package.json new file mode 100644 index 0000000000..9b9bfaefd8 --- /dev/null +++ b/blackroad-sf/package.json @@ -0,0 +1,44 @@ +{ + "name": "salesforce-app", + "private": true, + "version": "1.0.0", + "description": "Salesforce App", + "scripts": { + "lint": "eslint **/{aura,lwc}/**/*.js", + "test": "npm run test:unit", + "test:unit": "sfdx-lwc-jest", + "test:unit:watch": "sfdx-lwc-jest --watch", + "test:unit:debug": "sfdx-lwc-jest --debug", + "test:unit:coverage": "sfdx-lwc-jest --coverage", + "prettier": "prettier --write \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"", + "prettier:verify": "prettier --check \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"", + "prepare": "husky || true", + "precommit": "lint-staged" + }, + "devDependencies": { + "@lwc/eslint-plugin-lwc": "^3.1.0", + "@prettier/plugin-xml": "^3.4.1", + "@salesforce/eslint-config-lwc": "^4.0.0", + "@salesforce/eslint-plugin-aura": "^3.0.0", + "@salesforce/eslint-plugin-lightning": "^2.0.0", + "@salesforce/sfdx-lwc-jest": "^7.0.2", + "eslint": "^10.0.1", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jest": "^29.15.0", + "husky": "^9.1.7", + "lint-staged": "^16.1.2", + "prettier": "^3.5.3", + "prettier-plugin-apex": "^2.2.6" + }, + "lint-staged": { + "**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}": [ + "prettier --write" + ], + "**/{aura,lwc}/**/*.js": [ + "eslint" + ], + "**/lwc/**": [ + "sfdx-lwc-jest -- --bail --findRelatedTests --passWithNoTests" + ] + } +} diff --git a/blackroad-sf/scripts/apex/hello.apex b/blackroad-sf/scripts/apex/hello.apex new file mode 100644 index 0000000000..1fba732502 --- /dev/null +++ b/blackroad-sf/scripts/apex/hello.apex @@ -0,0 +1,10 @@ +// Use .apex files to store anonymous Apex. +// You can execute anonymous Apex in VS Code by selecting the +// apex text and running the command: +// SFDX: Execute Anonymous Apex with Currently Selected Text +// You can also execute the entire file by running the command: +// SFDX: Execute Anonymous Apex with Editor Contents + +string tempvar = 'Enter_your_name_here'; +System.debug('Hello World!'); +System.debug('My name is ' + tempvar); \ No newline at end of file diff --git a/blackroad-sf/scripts/soql/account.soql b/blackroad-sf/scripts/soql/account.soql new file mode 100644 index 0000000000..10d4b9c78d --- /dev/null +++ b/blackroad-sf/scripts/soql/account.soql @@ -0,0 +1,6 @@ +// Use .soql files to store SOQL queries. +// You can execute queries in VS Code by selecting the +// query text and running the command: +// SFDX: Execute SOQL Query with Currently Selected Text + +SELECT Id, Name FROM Account diff --git a/blackroad-sf/sfdx-project.json b/blackroad-sf/sfdx-project.json new file mode 100644 index 0000000000..1e1c2890c2 --- /dev/null +++ b/blackroad-sf/sfdx-project.json @@ -0,0 +1,12 @@ +{ + "packageDirectories": [ + { + "path": "force-app", + "default": true + } + ], + "name": "blackroad-sf", + "namespace": "", + "sfdcLoginUrl": "https://login.salesforce.com", + "sourceApiVersion": "65.0" +} diff --git a/blackroad-web-core/assets/css/core.css b/blackroad-web-core/assets/css/core.css new file mode 100644 index 0000000000..07e53544ce --- /dev/null +++ b/blackroad-web-core/assets/css/core.css @@ -0,0 +1,424 @@ +/* ============================================================ + BLACKROAD WEB CORE — Core Stylesheet + Base reset, typography, layout system, and component styles. + Import tokens.css and motion.css before this file. + ============================================================ */ + +/* ── Reset ── */ +*, *::before, *::after { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body { + background: var(--bg); + color: var(--text); + font-family: var(--font-mono); + min-height: 100vh; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { color: inherit; text-decoration: none; } +img { max-width: 100%; display: block; } + +/* ── Header ── */ +header { + background: var(--bg); + padding: 20px 24px; + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: var(--border); + position: sticky; + top: 0; + z-index: var(--z-nav); +} + +.logo { + font-size: var(--fs-3xl); + font-weight: 700; + color: var(--text); + letter-spacing: 0.05em; +} + +nav { + font-size: var(--fs-md); + display: flex; + gap: 20px; +} + +nav a { + color: var(--muted-1); + text-decoration: none; + transition: color 0.2s var(--ease); +} + +nav a:hover { color: var(--text); } + +/* ── Gradient Rule ── */ +.grad-rule { + height: 3px; + background: var(--br-grad); +} + +.brand-rule { + height: 2px; + background: var(--br-grad); + max-width: 200px; +} + +/* ── Hero ── */ +.hero { + background: var(--bg); + padding: 48px 24px 36px; + border-bottom: var(--border); +} + +.hero-label { + font-size: var(--fs-sm); + color: var(--muted-3); + letter-spacing: 0.2em; + text-transform: uppercase; + margin-bottom: 10px; +} + +.hero h1 { + font-size: var(--fs-h1); + font-weight: 700; + color: var(--text); + line-height: var(--lh-tight); +} + +.hero p { + margin-top: 12px; + font-size: var(--fs-lg); + color: var(--muted-2); + line-height: var(--lh-body); +} + +.hero-meta { + margin-top: 20px; + display: flex; + gap: 20px; + flex-wrap: wrap; +} + +.hero-badge { + font-size: var(--fs-sm); + color: var(--muted-4); + border: var(--border-dim); + padding: 4px 10px; + letter-spacing: 0.1em; +} + +.hero-badge span { color: var(--text); } + +/* ── Section ── */ +.section { + background: var(--bg); + padding: 32px 24px; + border-bottom: var(--border); +} + +.section-label { + font-size: var(--fs-sm); + color: var(--muted-3); + letter-spacing: 0.2em; + text-transform: uppercase; + margin-bottom: 18px; + display: flex; + align-items: center; + gap: 10px; +} + +.section-label::after { + content: ''; + flex: 1; + height: 1px; + background: var(--muted-5); +} + +/* ── Grid System ── */ +.grid { + display: grid; + grid-template-columns: 1fr; +} + +.grid-2 { display: grid; grid-template-columns: 1fr; } +.grid-3 { display: grid; grid-template-columns: 1fr; } +.grid-4 { display: grid; grid-template-columns: 1fr; } + +/* ── Card ── */ +.card { + background: var(--bg); + border-right: var(--border); + border-bottom: var(--border); + padding: 32px 24px; + display: flex; + flex-direction: column; + gap: 20px; +} + +.card-label { + font-size: var(--fs-sm); + color: var(--muted-3); + letter-spacing: 0.2em; + text-transform: uppercase; + display: flex; + align-items: center; + gap: 10px; +} + +.card-label::after { + content: ''; + flex: 1; + height: 1px; + background: var(--bg-raised); +} + +.card-name { + font-size: var(--fs-2xl); + font-weight: 700; + color: var(--text); +} + +.card-desc { + font-size: var(--fs-base); + color: var(--muted-2); + line-height: 1.8; +} + +/* ── Package Items ── */ +.package-list { + display: flex; + flex-direction: column; + gap: 2px; +} + +.package-item { + background: var(--bg); + border: var(--border-faint); + padding: 12px 14px; + display: flex; + flex-direction: column; + gap: 5px; + text-decoration: none; + cursor: pointer; + transition: border-color 0.2s var(--ease); +} + +.package-item:hover { border-color: var(--text); } + +.pkg-name { font-size: var(--fs-xl); font-weight: 700; color: var(--text); } +.pkg-desc { font-size: var(--fs-sm); color: var(--muted-3); line-height: 1.7; } +.pkg-meta { display: flex; gap: 12px; margin-top: 4px; } +.pkg-tag { font-size: var(--fs-xs); color: var(--muted-4); letter-spacing: 0.1em; } +.pkg-tag span { color: var(--muted-1); } + +/* ── Link List (for org/domain directories) ── */ +.link-list { + display: flex; + flex-direction: column; + gap: 2px; +} + +.link-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 14px; + border: var(--border-faint); + transition: border-color 0.2s var(--ease); + cursor: pointer; + text-decoration: none; +} + +.link-item:hover { border-color: var(--text); } + +.link-item .link-name { + font-size: var(--fs-xl); + font-weight: 700; + color: var(--text); +} + +.link-item .link-url { + font-size: var(--fs-sm); + color: var(--muted-3); +} + +.link-item .link-desc { + font-size: var(--fs-sm); + color: var(--muted-4); +} + +.link-item .link-arrow { + font-size: var(--fs-sm); + color: var(--muted-4); + transition: color 0.2s var(--ease); +} + +.link-item:hover .link-arrow { color: var(--text); } + +/* ── Platform List ── */ +.platform-list { + display: flex; + flex-direction: column; + gap: 2px; +} + +.platform-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 9px 10px; + font-size: 0.68rem; + color: var(--muted-1); + border-left: 2px solid var(--border-faint); +} + +.platform-item.supported { + border-left-color: var(--text); + color: var(--text); +} + +.plat-status { + font-size: var(--fs-xs); + color: var(--muted-4); + letter-spacing: 0.1em; +} + +.platform-item.supported .plat-status { color: var(--muted-2); } + +/* ── Install Block ── */ +.install-block { + background: var(--bg-surface); + border: 1px solid var(--bg-raised); + padding: 14px 16px; + margin-bottom: 12px; +} + +.install-label { + font-size: var(--fs-xs); + color: var(--muted-4); + letter-spacing: 0.15em; + text-transform: uppercase; + margin-bottom: 8px; +} + +.install-cmd { + font-size: 0.72rem; + color: #aaaaaa; + word-break: break-all; + line-height: 1.8; +} + +.install-cmd .prompt { color: var(--muted-5); user-select: none; } +.install-cmd .pkg { color: var(--text); } + +/* ── Legend ── */ +.legend { + background: var(--bg); + padding: 24px 24px; + border-bottom: var(--border); + display: flex; + flex-wrap: wrap; + gap: 16px; + align-items: center; +} + +.legend-label { + font-size: var(--fs-sm); + color: var(--muted-3); + letter-spacing: 0.15em; + text-transform: uppercase; + margin-right: 8px; +} + +.swatch { + display: flex; + align-items: center; + gap: 7px; + font-size: var(--fs-sm); + color: var(--muted-1); +} + +.swatch-dot { + width: 10px; + height: 10px; + border-radius: 50%; + flex-shrink: 0; +} + +/* ── Stage (animation preview) ── */ +.stage { + background: var(--bg); + border: 1px solid var(--bg-raised); + height: 110px; + display: flex; + align-items: center; + justify-content: center; + position: relative; + overflow: hidden; +} + +/* ── Stat Block ── */ +.stat-row { + display: flex; + align-items: baseline; + justify-content: space-between; + padding: 8px 0; + border-bottom: 1px solid var(--bg-raised); +} + +.stat-label { + font-size: var(--fs-sm); + color: var(--muted-3); + letter-spacing: 0.1em; +} + +.stat-value { + font-size: var(--fs-xl); + font-weight: 700; + color: var(--text); +} + +/* ── Footer ── */ +footer { + background: var(--bg); + padding: 20px 24px; + border-top: var(--border-dim); + font-size: var(--fs-sm); + color: var(--muted-4); + display: flex; + flex-direction: column; + gap: 4px; +} + +/* ── Desktop Breakpoints ── */ +@media (min-width: 768px) { + header { padding: 20px 40px; } + .hero { padding: 56px 40px 44px; } + .hero h1 { font-size: var(--fs-h1-d); } + .section { padding: 36px 40px; } + .card { padding: 36px 40px; } + .legend { padding: 24px 40px; } + footer { padding: 20px 40px; flex-direction: row; justify-content: space-between; } + + .grid { grid-template-columns: repeat(3, 1fr); } + .grid-2 { grid-template-columns: repeat(2, 1fr); } + .grid-3 { grid-template-columns: repeat(3, 1fr); } + .grid-4 { grid-template-columns: repeat(4, 1fr); } + + .section { + border-right: var(--border); + border-bottom: none; + } + .section:last-child { border-right: none; } +} + +@media (min-width: 600px) and (max-width: 767px) { + .grid { grid-template-columns: repeat(2, 1fr); } + .grid-2 { grid-template-columns: repeat(2, 1fr); } +} diff --git a/blackroad-web-core/assets/css/motion.css b/blackroad-web-core/assets/css/motion.css new file mode 100644 index 0000000000..95ef45e8ca --- /dev/null +++ b/blackroad-web-core/assets/css/motion.css @@ -0,0 +1,171 @@ +/* ============================================================ + BLACKROAD WEB CORE — Motion System + 17 animation primitives. Shapes move in color. Text stays white. + ============================================================ */ + +/* ── 01 FADE ── */ +@keyframes fade { + 0%, 100% { opacity: 1; } + 50% { opacity: 0; } +} + +/* ── 02 SLIDE X ── */ +@keyframes slideX { + 0% { transform: translateX(-44px); opacity: 0; } + 20% { opacity: 1; } + 80% { opacity: 1; } + 100% { transform: translateX(44px); opacity: 0; } +} + +/* ── 03 SLIDE Y ── */ +@keyframes slideY { + 0% { transform: translateY(-32px); opacity: 0; } + 20% { opacity: 1; } + 80% { opacity: 1; } + 100% { transform: translateY(32px); opacity: 0; } +} + +/* ── 04 PULSE ── */ +@keyframes pulse { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(2); } +} + +/* ── 05 SPIN ── */ +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +/* ── 06 BLINK ── */ +@keyframes blink { + 0%, 49%, 100% { opacity: 1; } + 50%, 99% { opacity: 0; } +} + +/* ── 07 BOUNCE ── */ +@keyframes bounce { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-30px); } +} + +/* ── 08 GROW ── */ +@keyframes grow { + 0%, 100% { transform: scaleX(0.08); } + 50% { transform: scaleX(1); } +} + +/* ── 09 SHAKE ── */ +@keyframes shake { + 0%, 100% { transform: translateX(0); } + 20% { transform: translateX(-9px); } + 40% { transform: translateX(9px); } + 60% { transform: translateX(-5px); } + 80% { transform: translateX(5px); } +} + +/* ── 10 ORBIT ── */ +@keyframes orbit { + from { transform: rotate(0deg) translateX(30px) rotate(0deg); } + to { transform: rotate(360deg) translateX(30px) rotate(-360deg); } +} + +/* ── 11 CURSOR ── */ +@keyframes cursor { + 0%, 49%, 100% { opacity: 1; } + 50%, 99% { opacity: 0; } +} + +/* ── 12 WAVE ── */ +@keyframes wave { + 0%, 100% { transform: scaleY(0.15); } + 50% { transform: scaleY(1); } +} + +/* ── 13 FLIP ── */ +@keyframes flip { + 0% { transform: rotateY(0deg); } + 100% { transform: rotateY(360deg); } +} + +/* ── 14 COLOR CYCLE ── */ +@keyframes colorCycle { + 0% { background: var(--br-orange, #FF8400); } + 25% { background: var(--br-pink, #FF0066); } + 50% { background: var(--br-purple, #8800FF); } + 75% { background: var(--br-blue, #0066FF); } + 100% { background: var(--br-orange, #FF8400); } +} + +/* ── 15 GRADIENT SHIFT ── */ +@keyframes gradShift { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +/* ── 16 BORDER PULSE ── */ +@keyframes borderPulse { + 0%, 100% { border-color: #FF8400; box-shadow: 0 0 8px #FF840055; } + 33% { border-color: #FF0066; box-shadow: 0 0 8px #FF006655; } + 66% { border-color: #0066FF; box-shadow: 0 0 8px #0066FF55; } +} + +/* ── 17 TYPEWRITER ── */ +@keyframes typewriter { + from { width: 0; } + to { width: 100%; } +} + +/* ── Utility Classes ── */ +.anim-fade { animation: fade 2s var(--ease-io) infinite; } +.anim-slideX { animation: slideX 2s var(--ease-io) infinite; } +.anim-slideY { animation: slideY 2s var(--ease-io) infinite; } +.anim-pulse { animation: pulse 1.5s var(--ease-io) infinite; } +.anim-spin { animation: spin 2s linear infinite; } +.anim-blink { animation: blink 1s step-end infinite; } +.anim-bounce { animation: bounce 1s var(--ease-io) infinite; } +.anim-grow { animation: grow 2s var(--ease-io) infinite; transform-origin: left center; } +.anim-shake { animation: shake 1s var(--ease-io) infinite; } +.anim-orbit { animation: orbit 2s linear infinite; position: absolute; } +.anim-cursor { animation: cursor 0.8s step-end infinite; } +.anim-flip { animation: flip 2s var(--ease-io) infinite; } +.anim-colorCycle { animation: colorCycle 3s linear infinite; } +.anim-borderPulse { animation: borderPulse 2s var(--ease-io) infinite; } +.anim-typewriter { + overflow: hidden; + white-space: nowrap; + animation: typewriter 3s steps(32, end) forwards; +} + +.anim-gradShift { + background: var(--br-grad); + background-size: 300% 300%; + animation: gradShift 3s ease infinite; +} + +.anim-wave1 { animation: wave 1s var(--ease-io) infinite; animation-delay: 0s; } +.anim-wave2 { animation: wave 1s var(--ease-io) infinite; animation-delay: 0.15s; } +.anim-wave3 { animation: wave 1s var(--ease-io) infinite; animation-delay: 0.3s; } +.anim-wave4 { animation: wave 1s var(--ease-io) infinite; animation-delay: 0.45s; } +.anim-wave5 { animation: wave 1s var(--ease-io) infinite; animation-delay: 0.6s; } + +/* ── Shape Primitives ── */ +.dot { width: 14px; height: 14px; border-radius: 50%; } +.sq { width: 16px; height: 16px; } +.bar { width: 3px; height: 36px; } +.line { width: 48px; height: 3px; } +.ring { width: 20px; height: 20px; border-radius: 50%; border: 3px solid; background: transparent; } + +/* ── Composites ── */ +.wave-group { display: flex; align-items: center; gap: 5px; height: 40px; } +.orbit-wrap { position: relative; width: 70px; height: 70px; display: flex; align-items: center; justify-content: center; } +.orbit-center { width: 6px; height: 6px; background: var(--text); border-radius: 50%; position: absolute; } + +/* ── Gradient text ── */ +.grad-text { + background: var(--br-grad); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} diff --git a/blackroad-web-core/assets/css/tokens.css b/blackroad-web-core/assets/css/tokens.css new file mode 100644 index 0000000000..377ea88416 --- /dev/null +++ b/blackroad-web-core/assets/css/tokens.css @@ -0,0 +1,89 @@ +/* ============================================================ + BLACKROAD WEB CORE — Design Tokens + The single source of truth for every BlackRoad property. + ============================================================ */ + +:root { + /* ── Background ── */ + --bg: #000000; + --bg-surface: #0a0a0a; + --bg-raised: #111111; + + /* ── Text (ONLY these two + gradient accent) ── */ + --text: #ffffff; + --text-inv: #000000; + + /* ── Muted text scale (UI chrome only, never body copy) ── */ + --muted-1: #888888; + --muted-2: #666666; + --muted-3: #555555; + --muted-4: #444444; + --muted-5: #333333; + + /* ── Brand Palette (shapes + accents, NEVER paragraph text) ── */ + --br-orange: #FF8400; + --br-red: #FF4400; + --br-pink: #FF0066; + --br-magenta: #CC00AA; + --br-purple: #8800FF; + --br-blue: #0066FF; + --br-navy: #2233CC; + + /* ── Gradient ── */ + --br-grad: linear-gradient(90deg, + var(--br-orange), + var(--br-pink), + var(--br-purple), + var(--br-blue) + ); + + --br-grad-135: linear-gradient(135deg, + var(--br-orange) 0%, + var(--br-pink) 33%, + var(--br-purple) 66%, + var(--br-blue) 100% + ); + + /* ── Borders ── */ + --border: 1px solid #ffffff; + --border-dim: 1px solid #333333; + --border-faint: 1px solid #1a1a1a; + --border-none: 1px solid #111111; + + /* ── Spacing (Golden Ratio φ = 1.618) ── */ + --sp-xs: 8px; + --sp-sm: 13px; + --sp-md: 21px; + --sp-lg: 34px; + --sp-xl: 55px; + --sp-2xl: 89px; + + /* ── Typography ── */ + --font-mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Code', monospace; + --lh: 1.618; + --lh-tight: 1.05; + --lh-body: 1.9; + + /* ── Font Sizes ── */ + --fs-xs: 0.55rem; + --fs-sm: 0.58rem; + --fs-base: 0.62rem; + --fs-md: 0.65rem; + --fs-lg: 0.7rem; + --fs-xl: 0.75rem; + --fs-2xl: 0.8rem; + --fs-3xl: 1.2rem; + --fs-h1: 2rem; + --fs-h1-d: 2.4rem; + + /* ── Easing ── */ + --ease: cubic-bezier(0.25, 0.1, 0.25, 1); + --ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275); + --ease-io: ease-in-out; + + /* ── Z-index ── */ + --z-base: 1; + --z-nav: 100; + --z-overlay: 500; + --z-modal: 1000; +} diff --git a/blackroad-web-core/components/card.html b/blackroad-web-core/components/card.html new file mode 100644 index 0000000000..19553e82d4 --- /dev/null +++ b/blackroad-web-core/components/card.html @@ -0,0 +1,28 @@ + + + +
    +
    {{LABEL}}
    +
    {{NAME}}
    +
    {{DESC}}
    +
    + + + +
    + + +
    + +
    + + +
    + {{NAME}} + {{DESC}} +
    + v {{VERSION}} + size {{SIZE}} +
    +
    diff --git a/blackroad-web-core/components/footer.html b/blackroad-web-core/components/footer.html new file mode 100644 index 0000000000..5e24c991f9 --- /dev/null +++ b/blackroad-web-core/components/footer.html @@ -0,0 +1,6 @@ + + +
    + BlackRoad OS, Inc. — Delaware C-Corp + 17 orgs · 1,825+ repos · 19 domains +
    diff --git a/blackroad-web-core/components/grid.html b/blackroad-web-core/components/grid.html new file mode 100644 index 0000000000..34d41907d5 --- /dev/null +++ b/blackroad-web-core/components/grid.html @@ -0,0 +1,23 @@ + + + +
    +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    diff --git a/blackroad-web-core/components/nav.html b/blackroad-web-core/components/nav.html new file mode 100644 index 0000000000..7b86bbb0c8 --- /dev/null +++ b/blackroad-web-core/components/nav.html @@ -0,0 +1,14 @@ + + +
    + + +
    +
    diff --git a/blackroad-web-core/domains/blackboxprogramming-io.html b/blackroad-web-core/domains/blackboxprogramming-io.html new file mode 100644 index 0000000000..02e449d6eb --- /dev/null +++ b/blackroad-web-core/domains/blackboxprogramming-io.html @@ -0,0 +1,97 @@ + + + + + + blackboxprogramming.io — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain
    +

    Developer Portal

    +

    SDK documentation, developer tools, and API references for the BlackRoad platform.

    +
    +
    domain blackboxprogramming.io
    +
    dns cloudflare
    +
    status active
    +
    +
    +
    + +
    + +
    + +
    domainblackboxprogramming.io
    +
    registrarCloudflare
    +
    DNSCloudflare
    +
    SSLFull (Strict)
    +
    statusactive
    +
    ownerBlackRoad OS, Inc.
    +
    + +
    + +

    SDK documentation, developer tools, and API references for the BlackRoad platform.

    + + +

    Developer tools, SDKs, API docs

    +
    + +
    + + +
    +
    cloudflare pages
    +
    + $ wrangler pages deploy . --project-name=blackboxprogramming-io +
    +
    + +
    +
    DNS record
    +
    + CNAME blackboxprogramming.io → pages.dev +
    +
    + + + +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + blackboxprogramming.io · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/domains/blackroad-company.html b/blackroad-web-core/domains/blackroad-company.html new file mode 100644 index 0000000000..96daa3f44d --- /dev/null +++ b/blackroad-web-core/domains/blackroad-company.html @@ -0,0 +1,97 @@ + + + + + + blackroad.company — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain
    +

    Company

    +

    Corporate information, team, and company overview for BlackRoad OS, Inc.

    +
    +
    domain blackroad.company
    +
    dns cloudflare
    +
    status active
    +
    +
    +
    + +
    + +
    + +
    domainblackroad.company
    +
    registrarCloudflare
    +
    DNSCloudflare
    +
    SSLFull (Strict)
    +
    statusactive
    +
    ownerBlackRoad OS, Inc.
    +
    + +
    + +

    Corporate information, team, and company overview for BlackRoad OS, Inc.

    + + +

    Corporate info, about, team

    +
    + +
    + + +
    +
    cloudflare pages
    +
    + $ wrangler pages deploy . --project-name=blackroad-company +
    +
    + +
    +
    DNS record
    +
    + CNAME blackroad.company → pages.dev +
    +
    + + + +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + blackroad.company · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/domains/blackroad-io.html b/blackroad-web-core/domains/blackroad-io.html new file mode 100644 index 0000000000..9e70c66110 --- /dev/null +++ b/blackroad-web-core/domains/blackroad-io.html @@ -0,0 +1,97 @@ + + + + + + blackroad.io — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain
    +

    Platform

    +

    The primary BlackRoad platform. 41 subdomain workers, AI dashboard, agent coordination.

    +
    +
    domain blackroad.io
    +
    dns cloudflare
    +
    status active
    +
    +
    +
    + +
    + +
    + +
    domainblackroad.io
    +
    registrarCloudflare
    +
    DNSCloudflare
    +
    SSLFull (Strict)
    +
    statusactive
    +
    ownerBlackRoad OS, Inc.
    +
    + +
    + +

    The primary BlackRoad platform. 41 subdomain workers, AI dashboard, agent coordination.

    + + +

    Primary platform, 41 subdomains, AI dashboard

    +
    + +
    + + +
    +
    cloudflare pages
    +
    + $ wrangler pages deploy . --project-name=blackroad-io +
    +
    + +
    +
    DNS record
    +
    + CNAME blackroad.io → pages.dev +
    +
    + + + +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + blackroad.io · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/domains/blackroad-me.html b/blackroad-web-core/domains/blackroad-me.html new file mode 100644 index 0000000000..b536f7c233 --- /dev/null +++ b/blackroad-web-core/domains/blackroad-me.html @@ -0,0 +1,97 @@ + + + + + + blackroad.me — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain
    +

    Identity

    +

    Personal identity and profile system. Portable AI identity via CECE.

    +
    +
    domain blackroad.me
    +
    dns cloudflare
    +
    status active
    +
    +
    +
    + +
    + +
    + +
    domainblackroad.me
    +
    registrarCloudflare
    +
    DNSCloudflare
    +
    SSLFull (Strict)
    +
    statusactive
    +
    ownerBlackRoad OS, Inc.
    +
    + +
    + +

    Personal identity and profile system. Portable AI identity via CECE.

    + + +

    Identity, profiles, CECE

    +
    + +
    + + +
    +
    cloudflare pages
    +
    + $ wrangler pages deploy . --project-name=blackroad-me +
    +
    + +
    +
    DNS record
    +
    + CNAME blackroad.me → pages.dev +
    +
    + + + +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + blackroad.me · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/domains/blackroad-network.html b/blackroad-web-core/domains/blackroad-network.html new file mode 100644 index 0000000000..e3208bd957 --- /dev/null +++ b/blackroad-web-core/domains/blackroad-network.html @@ -0,0 +1,97 @@ + + + + + + blackroad.network — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain
    +

    Network

    +

    Infrastructure mesh visualization, network topology, and service connectivity.

    +
    +
    domain blackroad.network
    +
    dns cloudflare
    +
    status active
    +
    +
    +
    + +
    + +
    + +
    domainblackroad.network
    +
    registrarCloudflare
    +
    DNSCloudflare
    +
    SSLFull (Strict)
    +
    statusactive
    +
    ownerBlackRoad OS, Inc.
    +
    + +
    + +

    Infrastructure mesh visualization, network topology, and service connectivity.

    + + +

    Network mesh, topology, connectivity

    +
    + +
    + + +
    +
    cloudflare pages
    +
    + $ wrangler pages deploy . --project-name=blackroad-network +
    +
    + +
    +
    DNS record
    +
    + CNAME blackroad.network → pages.dev +
    +
    + + + +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + blackroad.network · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/domains/blackroad-systems.html b/blackroad-web-core/domains/blackroad-systems.html new file mode 100644 index 0000000000..6d8ad40272 --- /dev/null +++ b/blackroad-web-core/domains/blackroad-systems.html @@ -0,0 +1,97 @@ + + + + + + blackroad.systems — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain
    +

    Systems

    +

    Systems monitoring dashboard, health checks, and operational status.

    +
    +
    domain blackroad.systems
    +
    dns cloudflare
    +
    status active
    +
    +
    +
    + +
    + +
    + +
    domainblackroad.systems
    +
    registrarCloudflare
    +
    DNSCloudflare
    +
    SSLFull (Strict)
    +
    statusactive
    +
    ownerBlackRoad OS, Inc.
    +
    + +
    + +

    Systems monitoring dashboard, health checks, and operational status.

    + + +

    Monitoring, health, operations

    +
    + +
    + + +
    +
    cloudflare pages
    +
    + $ wrangler pages deploy . --project-name=blackroad-systems +
    +
    + +
    +
    DNS record
    +
    + CNAME blackroad.systems → pages.dev +
    +
    + + + +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + blackroad.systems · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/domains/blackroadai-com.html b/blackroad-web-core/domains/blackroadai-com.html new file mode 100644 index 0000000000..9a850760b2 --- /dev/null +++ b/blackroad-web-core/domains/blackroadai-com.html @@ -0,0 +1,97 @@ + + + + + + blackroadai.com — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain
    +

    AI Division

    +

    AI/ML model serving, agent inference, and research division of BlackRoad.

    +
    +
    domain blackroadai.com
    +
    dns cloudflare
    +
    status active
    +
    +
    +
    + +
    + +
    + +
    domainblackroadai.com
    +
    registrarCloudflare
    +
    DNSCloudflare
    +
    SSLFull (Strict)
    +
    statusactive
    +
    ownerBlackRoad OS, Inc.
    +
    + +
    + +

    AI/ML model serving, agent inference, and research division of BlackRoad.

    + + +

    AI models, inference, ML research

    +
    + +
    + + +
    +
    cloudflare pages
    +
    + $ wrangler pages deploy . --project-name=blackroadai-com +
    +
    + +
    +
    DNS record
    +
    + CNAME blackroadai.com → pages.dev +
    +
    + + + +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + blackroadai.com · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/domains/blackroadinc-us.html b/blackroad-web-core/domains/blackroadinc-us.html new file mode 100644 index 0000000000..69591f7529 --- /dev/null +++ b/blackroad-web-core/domains/blackroadinc-us.html @@ -0,0 +1,97 @@ + + + + + + blackroadinc.us — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain
    +

    US Corporate

    +

    US corporate entity for BlackRoad OS, Inc. Delaware C-Corp registration.

    +
    +
    domain blackroadinc.us
    +
    dns cloudflare
    +
    status active
    +
    +
    +
    + +
    + +
    + +
    domainblackroadinc.us
    +
    registrarCloudflare
    +
    DNSCloudflare
    +
    SSLFull (Strict)
    +
    statusactive
    +
    ownerBlackRoad OS, Inc.
    +
    + +
    + +

    US corporate entity for BlackRoad OS, Inc. Delaware C-Corp registration.

    + + +

    US corporate, Delaware C-Corp

    +
    + +
    + + +
    +
    cloudflare pages
    +
    + $ wrangler pages deploy . --project-name=blackroadinc-us +
    +
    + +
    +
    DNS record
    +
    + CNAME blackroadinc.us → pages.dev +
    +
    + + + +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + blackroadinc.us · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/domains/blackroadqi-com.html b/blackroad-web-core/domains/blackroadqi-com.html new file mode 100644 index 0000000000..72981fc06a --- /dev/null +++ b/blackroad-web-core/domains/blackroadqi-com.html @@ -0,0 +1,97 @@ + + + + + + blackroadqi.com — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain
    +

    Quantum Intelligence

    +

    Quantum intelligence research and quantum-enhanced AI systems.

    +
    +
    domain blackroadqi.com
    +
    dns cloudflare
    +
    status active
    +
    +
    +
    + +
    + +
    + +
    domainblackroadqi.com
    +
    registrarCloudflare
    +
    DNSCloudflare
    +
    SSLFull (Strict)
    +
    statusactive
    +
    ownerBlackRoad OS, Inc.
    +
    + +
    + +

    Quantum intelligence research and quantum-enhanced AI systems.

    + + +

    Quantum AI, QI research

    +
    + +
    + + +
    +
    cloudflare pages
    +
    + $ wrangler pages deploy . --project-name=blackroadqi-com +
    +
    + +
    +
    DNS record
    +
    + CNAME blackroadqi.com → pages.dev +
    +
    + + + +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + blackroadqi.com · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/domains/blackroadquantum-com.html b/blackroad-web-core/domains/blackroadquantum-com.html new file mode 100644 index 0000000000..930825db1b --- /dev/null +++ b/blackroad-web-core/domains/blackroadquantum-com.html @@ -0,0 +1,97 @@ + + + + + + blackroadquantum.com — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain
    +

    Quantum Computing

    +

    Primary quantum computing domain. Quantum algorithms and hardware integration.

    +
    +
    domain blackroadquantum.com
    +
    dns cloudflare
    +
    status active
    +
    +
    +
    + +
    + +
    + +
    domainblackroadquantum.com
    +
    registrarCloudflare
    +
    DNSCloudflare
    +
    SSLFull (Strict)
    +
    statusactive
    +
    ownerBlackRoad OS, Inc.
    +
    + +
    + +

    Primary quantum computing domain. Quantum algorithms and hardware integration.

    + + +

    Quantum computing, algorithms

    +
    + +
    + + +
    +
    cloudflare pages
    +
    + $ wrangler pages deploy . --project-name=blackroadquantum-com +
    +
    + +
    +
    DNS record
    +
    + CNAME blackroadquantum.com → pages.dev +
    +
    + + + +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + blackroadquantum.com · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/domains/blackroadquantum-info.html b/blackroad-web-core/domains/blackroadquantum-info.html new file mode 100644 index 0000000000..6220f44d7b --- /dev/null +++ b/blackroad-web-core/domains/blackroadquantum-info.html @@ -0,0 +1,97 @@ + + + + + + blackroadquantum.info — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain
    +

    Quantum Info

    +

    Quantum computing information, documentation, and educational resources.

    +
    +
    domain blackroadquantum.info
    +
    dns cloudflare
    +
    status active
    +
    +
    +
    + +
    + +
    + +
    domainblackroadquantum.info
    +
    registrarCloudflare
    +
    DNSCloudflare
    +
    SSLFull (Strict)
    +
    statusactive
    +
    ownerBlackRoad OS, Inc.
    +
    + +
    + +

    Quantum computing information, documentation, and educational resources.

    + + +

    Quantum docs, education

    +
    + +
    + + +
    +
    cloudflare pages
    +
    + $ wrangler pages deploy . --project-name=blackroadquantum-info +
    +
    + +
    +
    DNS record
    +
    + CNAME blackroadquantum.info → pages.dev +
    +
    + + + +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + blackroadquantum.info · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/domains/blackroadquantum-net.html b/blackroad-web-core/domains/blackroadquantum-net.html new file mode 100644 index 0000000000..68526b7587 --- /dev/null +++ b/blackroad-web-core/domains/blackroadquantum-net.html @@ -0,0 +1,97 @@ + + + + + + blackroadquantum.net — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain
    +

    Quantum Network

    +

    Quantum networking protocols, entanglement distribution, and quantum mesh.

    +
    +
    domain blackroadquantum.net
    +
    dns cloudflare
    +
    status active
    +
    +
    +
    + +
    + +
    + +
    domainblackroadquantum.net
    +
    registrarCloudflare
    +
    DNSCloudflare
    +
    SSLFull (Strict)
    +
    statusactive
    +
    ownerBlackRoad OS, Inc.
    +
    + +
    + +

    Quantum networking protocols, entanglement distribution, and quantum mesh.

    + + +

    Quantum networking, QKD

    +
    + +
    + + +
    +
    cloudflare pages
    +
    + $ wrangler pages deploy . --project-name=blackroadquantum-net +
    +
    + +
    +
    DNS record
    +
    + CNAME blackroadquantum.net → pages.dev +
    +
    + + + +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + blackroadquantum.net · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/domains/blackroadquantum-shop.html b/blackroad-web-core/domains/blackroadquantum-shop.html new file mode 100644 index 0000000000..f2b0a90432 --- /dev/null +++ b/blackroad-web-core/domains/blackroadquantum-shop.html @@ -0,0 +1,97 @@ + + + + + + blackroadquantum.shop — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain
    +

    Quantum Shop

    +

    Quantum hardware marketplace and component procurement.

    +
    +
    domain blackroadquantum.shop
    +
    dns cloudflare
    +
    status active
    +
    +
    +
    + +
    + +
    + +
    domainblackroadquantum.shop
    +
    registrarCloudflare
    +
    DNSCloudflare
    +
    SSLFull (Strict)
    +
    statusactive
    +
    ownerBlackRoad OS, Inc.
    +
    + +
    + +

    Quantum hardware marketplace and component procurement.

    + + +

    Quantum hardware, marketplace

    +
    + +
    + + +
    +
    cloudflare pages
    +
    + $ wrangler pages deploy . --project-name=blackroadquantum-shop +
    +
    + +
    +
    DNS record
    +
    + CNAME blackroadquantum.shop → pages.dev +
    +
    + + + +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + blackroadquantum.shop · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/domains/blackroadquantum-store.html b/blackroad-web-core/domains/blackroadquantum-store.html new file mode 100644 index 0000000000..928d45c473 --- /dev/null +++ b/blackroad-web-core/domains/blackroadquantum-store.html @@ -0,0 +1,97 @@ + + + + + + blackroadquantum.store — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain
    +

    Quantum Store

    +

    Digital store for quantum software, licenses, and quantum-ready tools.

    +
    +
    domain blackroadquantum.store
    +
    dns cloudflare
    +
    status active
    +
    +
    +
    + +
    + +
    + +
    domainblackroadquantum.store
    +
    registrarCloudflare
    +
    DNSCloudflare
    +
    SSLFull (Strict)
    +
    statusactive
    +
    ownerBlackRoad OS, Inc.
    +
    + +
    + +

    Digital store for quantum software, licenses, and quantum-ready tools.

    + + +

    Quantum software, licenses

    +
    + +
    + + +
    +
    cloudflare pages
    +
    + $ wrangler pages deploy . --project-name=blackroadquantum-store +
    +
    + +
    +
    DNS record
    +
    + CNAME blackroadquantum.store → pages.dev +
    +
    + + + +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + blackroadquantum.store · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/domains/lucidia-earth.html b/blackroad-web-core/domains/lucidia-earth.html new file mode 100644 index 0000000000..2ae178bbb5 --- /dev/null +++ b/blackroad-web-core/domains/lucidia-earth.html @@ -0,0 +1,97 @@ + + + + + + lucidia.earth — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain
    +

    Lucidia Earth

    +

    3D visualization of the Lucidia AI world. Three.js immersive experience.

    +
    +
    domain lucidia.earth
    +
    dns cloudflare
    +
    status active
    +
    +
    +
    + +
    + +
    + +
    domainlucidia.earth
    +
    registrarCloudflare
    +
    DNSCloudflare
    +
    SSLFull (Strict)
    +
    statusactive
    +
    ownerBlackRoad OS, Inc.
    +
    + +
    + +

    3D visualization of the Lucidia AI world. Three.js immersive experience.

    + + +

    3D world, Three.js, immersive

    +
    + +
    + + +
    +
    cloudflare pages
    +
    + $ wrangler pages deploy . --project-name=lucidia-earth +
    +
    + +
    +
    DNS record
    +
    + CNAME lucidia.earth → pages.dev +
    +
    + + + +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + lucidia.earth · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/domains/lucidia-studio.html b/blackroad-web-core/domains/lucidia-studio.html new file mode 100644 index 0000000000..5225787842 --- /dev/null +++ b/blackroad-web-core/domains/lucidia-studio.html @@ -0,0 +1,97 @@ + + + + + + lucidia.studio — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain
    +

    Lucidia Studio

    +

    Creative AI studio. Design, art generation, and creative workflows powered by Lucidia.

    +
    +
    domain lucidia.studio
    +
    dns cloudflare
    +
    status active
    +
    +
    +
    + +
    + +
    + +
    domainlucidia.studio
    +
    registrarCloudflare
    +
    DNSCloudflare
    +
    SSLFull (Strict)
    +
    statusactive
    +
    ownerBlackRoad OS, Inc.
    +
    + +
    + +

    Creative AI studio. Design, art generation, and creative workflows powered by Lucidia.

    + + +

    Creative AI, design, art generation

    +
    + +
    + + +
    +
    cloudflare pages
    +
    + $ wrangler pages deploy . --project-name=lucidia-studio +
    +
    + +
    +
    DNS record
    +
    + CNAME lucidia.studio → pages.dev +
    +
    + + + +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + lucidia.studio · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/domains/lucidiaqi-com.html b/blackroad-web-core/domains/lucidiaqi-com.html new file mode 100644 index 0000000000..9b4e5c72cf --- /dev/null +++ b/blackroad-web-core/domains/lucidiaqi-com.html @@ -0,0 +1,97 @@ + + + + + + lucidiaqi.com — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain
    +

    Lucidia QI

    +

    Lucidia Quantum Intelligence. Where AI reasoning meets quantum computation.

    +
    +
    domain lucidiaqi.com
    +
    dns cloudflare
    +
    status active
    +
    +
    +
    + +
    + +
    + +
    domainlucidiaqi.com
    +
    registrarCloudflare
    +
    DNSCloudflare
    +
    SSLFull (Strict)
    +
    statusactive
    +
    ownerBlackRoad OS, Inc.
    +
    + +
    + +

    Lucidia Quantum Intelligence. Where AI reasoning meets quantum computation.

    + + +

    Quantum AI, Lucidia reasoning

    +
    + +
    + + +
    +
    cloudflare pages
    +
    + $ wrangler pages deploy . --project-name=lucidiaqi-com +
    +
    + +
    +
    DNS record
    +
    + CNAME lucidiaqi.com → pages.dev +
    +
    + + + +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + lucidiaqi.com · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/domains/roadchain-io.html b/blackroad-web-core/domains/roadchain-io.html new file mode 100644 index 0000000000..30f59cc5b4 --- /dev/null +++ b/blackroad-web-core/domains/roadchain-io.html @@ -0,0 +1,97 @@ + + + + + + roadchain.io — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain
    +

    RoadChain

    +

    Blockchain infrastructure layer. On-chain agent coordination and verifiable compute.

    +
    +
    domain roadchain.io
    +
    dns cloudflare
    +
    status active
    +
    +
    +
    + +
    + +
    + +
    domainroadchain.io
    +
    registrarCloudflare
    +
    DNSCloudflare
    +
    SSLFull (Strict)
    +
    statusactive
    +
    ownerBlackRoad OS, Inc.
    +
    + +
    + +

    Blockchain infrastructure layer. On-chain agent coordination and verifiable compute.

    + + +

    Blockchain, on-chain agents, verifiable compute

    +
    + +
    + + +
    +
    cloudflare pages
    +
    + $ wrangler pages deploy . --project-name=roadchain-io +
    +
    + +
    +
    DNS record
    +
    + CNAME roadchain.io → pages.dev +
    +
    + + + +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + roadchain.io · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/domains/roadcoin-io.html b/blackroad-web-core/domains/roadcoin-io.html new file mode 100644 index 0000000000..957d8a0def --- /dev/null +++ b/blackroad-web-core/domains/roadcoin-io.html @@ -0,0 +1,97 @@ + + + + + + roadcoin.io — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain
    +

    RoadCoin

    +

    Token economics and cryptocurrency for the BlackRoad ecosystem.

    +
    +
    domain roadcoin.io
    +
    dns cloudflare
    +
    status active
    +
    +
    +
    + +
    + +
    + +
    domainroadcoin.io
    +
    registrarCloudflare
    +
    DNSCloudflare
    +
    SSLFull (Strict)
    +
    statusactive
    +
    ownerBlackRoad OS, Inc.
    +
    + +
    + +

    Token economics and cryptocurrency for the BlackRoad ecosystem.

    + + +

    Cryptocurrency, tokenomics, DeFi

    +
    + +
    + + +
    +
    cloudflare pages
    +
    + $ wrangler pages deploy . --project-name=roadcoin-io +
    +
    + +
    +
    DNS record
    +
    + CNAME roadcoin.io → pages.dev +
    +
    + + + +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + roadcoin.io · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/index.html b/blackroad-web-core/index.html new file mode 100644 index 0000000000..8284cd79b0 --- /dev/null +++ b/blackroad-web-core/index.html @@ -0,0 +1,346 @@ + + + + + + BlackRoad — Web Core + + + + + + + + + + +
    + + +
    +
    + + + + +
    +
    Web Core v1.0
    +

    BlackRoad
    Enterprise
    Web Kernel

    +

    One engine. Every org. Every domain. One design language.

    +
    +
    enterprise blackroad-os
    +
    orgs 17
    +
    repos 1,825+
    +
    agents 30,000
    +
    domains 19
    +
    animations 17
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + palette +
    #FF8400
    +
    #FF4400
    +
    #FF0066
    +
    #CC00AA
    +
    #8800FF
    +
    #0066FF
    +
    #2233CC
    +
    + +
    background#000000
    +
    text#FFFFFF or #000000
    +
    fontJetBrains Mono
    +
    line-height1.618 (golden ratio)
    +
    animations17 primitives
    +
    ruleshapes in color, text in white/black
    + + +
    + + + + +
    + +
    + +
    Railway projects14
    +
    Vercel projects15+
    +
    Cloudflare workers75+
    +
    GitHub Pages16+
    +
    Raspberry Pis4
    +
    DigitalOcean1 droplet
    +
    + +
    + +
    AI Research12,592
    +
    Code Deploy8,407
    +
    Infrastructure5,401
    +
    Monitoring3,600
    +
    octavia Pi22,500 capacity
    +
    lucidia Pi7,500 capacity
    +
    + +
    + +
    CSS files3
    +
    Layout templates4
    +
    Components4
    +
    Org pages17
    +
    Domain pages19
    +
    Core pages5
    +
    Total HTML49
    +
    + +
    + + + + +
    + +
    +
    blackroad-web-core/ + index.html + assets/css/tokens.css + assets/css/motion.css + assets/css/core.css + layouts/base.html + layouts/dashboard.html + layouts/blog.html + layouts/docs.html + components/nav.html + components/footer.html + components/card.html + components/grid.html + pages/enterprise.html + pages/organizations.html + pages/domains.html + pages/motion.html + orgs/BlackRoad-OS-Inc.html + orgs/BlackRoad-OS.html + orgs/blackboxprogramming.html + orgs/BlackRoad-AI.html + orgs/BlackRoad-Cloud.html + orgs/BlackRoad-Security.html + orgs/BlackRoad-Foundation.html + orgs/BlackRoad-Media.html + orgs/BlackRoad-Interactive.html + orgs/BlackRoad-Hardware.html + orgs/BlackRoad-Labs.html + orgs/BlackRoad-Education.html + orgs/BlackRoad-Studio.html + orgs/BlackRoad-Ventures.html + orgs/BlackRoad-Gov.html + orgs/Blackbox-Enterprises.html + orgs/BlackRoad-Archive.html + domains/blackboxprogramming-io.html + domains/blackroad-company.html + domains/blackroad-io.html + domains/blackroad-me.html + domains/blackroad-network.html + domains/blackroad-systems.html + domains/blackroadai-com.html + domains/blackroadinc-us.html + domains/blackroadqi-com.html + domains/blackroadquantum-com.html + domains/blackroadquantum-info.html + domains/blackroadquantum-net.html + domains/blackroadquantum-shop.html + domains/blackroadquantum-store.html + domains/lucidia-earth.html + domains/lucidia-studio.html + domains/lucidiaqi-com.html + domains/roadchain-io.html + domains/roadcoin-io.html
    +
    +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + Web Core v1.0 · 49 HTML files · 3 CSS files · 17 orgs · 19 domains +
    + + + diff --git a/blackroad-web-core/layouts/base.html b/blackroad-web-core/layouts/base.html new file mode 100644 index 0000000000..de8d1e0a8f --- /dev/null +++ b/blackroad-web-core/layouts/base.html @@ -0,0 +1,46 @@ + + + + + + {{TITLE}} — BlackRoad + + + + + + + + + +
    + + +
    +
    + + +
    +
    {{LABEL}}
    +

    {{HEADING}}

    +

    {{DESCRIPTION}}

    +
    +
    + + + {{CONTENT}} + + +
    + BlackRoad OS, Inc. — Delaware C-Corp + 17 orgs · 1,825+ repos · 19 domains +
    + + + diff --git a/blackroad-web-core/layouts/blog.html b/blackroad-web-core/layouts/blog.html new file mode 100644 index 0000000000..87fa2dfcc2 --- /dev/null +++ b/blackroad-web-core/layouts/blog.html @@ -0,0 +1,69 @@ + + + + + + {{TITLE}} — BlackRoad + + + + + + + + + +
    + + +
    +
    + +
    + {{CONTENT}} +
    + +
    + BlackRoad OS, Inc. + Blog +
    + + + diff --git a/blackroad-web-core/layouts/dashboard.html b/blackroad-web-core/layouts/dashboard.html new file mode 100644 index 0000000000..72c9bd2e70 --- /dev/null +++ b/blackroad-web-core/layouts/dashboard.html @@ -0,0 +1,75 @@ + + + + + + {{TITLE}} — BlackRoad Dashboard + + + + + + + + + +
    + + +
    +
    + +
    + +
    + {{CONTENT}} +
    +
    + +
    + BlackRoad OS, Inc. + Dashboard +
    + + + diff --git a/blackroad-web-core/layouts/docs.html b/blackroad-web-core/layouts/docs.html new file mode 100644 index 0000000000..72f2890010 --- /dev/null +++ b/blackroad-web-core/layouts/docs.html @@ -0,0 +1,104 @@ + + + + + + {{TITLE}} — BlackRoad Docs + + + + + + + + + +
    + + +
    +
    + +
    + +
    + {{CONTENT}} +
    +
    + +
    + BlackRoad OS, Inc. + Documentation +
    + + + diff --git a/blackroad-web-core/orgs/BlackRoad-AI.html b/blackroad-web-core/orgs/BlackRoad-AI.html new file mode 100644 index 0000000000..72b079fa20 --- /dev/null +++ b/blackroad-web-core/orgs/BlackRoad-AI.html @@ -0,0 +1,100 @@ + + + + + + BlackRoad-AI — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Organization
    +

    BlackRoad-AI

    +

    AI/ML stack with 38 forks covering LLM inference, models, frameworks, and vector databases.

    +
    +
    repos 52
    +
    status proprietary
    +
    +
    +
    + +
    + +
    + +
    repositories52
    +
    visibilityproprietary
    +
    ownerBlackRoad OS, Inc.
    + + +

    LLM inference, models, vector DBs, ML frameworks

    + + +
    + +
    + +
    + +
    + blackroad-vllm + vLLM high-throughput inference +
    +
    + blackroad-ai-ollama + Multi-model runtime with MEMORY +
    +
    + blackroad-ai-qwen + Qwen model deployment +
    +
    + blackroad-ai-deepseek + DeepSeek models +
    +
    + blackroad-ai-api-gateway + Unified AI API gateway +
    +
    + blackroad-ai-cluster + GPU cluster orchestration +
    +
    + blackroad-ai-memory-bridge + Cross-model memory +
    +
    +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + BlackRoad-AI · 52 repos · All proprietary +
    + + + diff --git a/blackroad-web-core/orgs/BlackRoad-Archive.html b/blackroad-web-core/orgs/BlackRoad-Archive.html new file mode 100644 index 0000000000..27c63716db --- /dev/null +++ b/blackroad-web-core/orgs/BlackRoad-Archive.html @@ -0,0 +1,88 @@ + + + + + + BlackRoad-Archive — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Organization
    +

    BlackRoad-Archive

    +

    Archived projects with 6 forks covering distributed storage, web archives, and backup systems.

    +
    +
    repos 21
    +
    status proprietary
    +
    +
    +
    + +
    + +
    + +
    repositories21
    +
    visibilityproprietary
    +
    ownerBlackRoad OS, Inc.
    + + +

    IPFS, web archive, distributed storage, backup

    + + +
    + +
    + +
    + +
    + Distributed storage systems + Distributed storage systems +
    +
    + Web archival tools + Web archival tools +
    +
    + Backup infrastructure + Backup infrastructure +
    +
    + Document preservation + Document preservation +
    +
    +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + BlackRoad-Archive · 21 repos · All proprietary +
    + + + diff --git a/blackroad-web-core/orgs/BlackRoad-Cloud.html b/blackroad-web-core/orgs/BlackRoad-Cloud.html new file mode 100644 index 0000000000..e5253371fc --- /dev/null +++ b/blackroad-web-core/orgs/BlackRoad-Cloud.html @@ -0,0 +1,96 @@ + + + + + + BlackRoad-Cloud — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Organization
    +

    BlackRoad-Cloud

    +

    Cloud infrastructure with 17 forks covering orchestration, networking, secrets, IaC, and storage.

    +
    +
    repos 30
    +
    status proprietary
    +
    +
    +
    + +
    + +
    + +
    repositories30
    +
    visibilityproprietary
    +
    ownerBlackRoad OS, Inc.
    + + +

    Kubernetes, Traefik, Vault, Terraform, MinIO

    + + +
    + +
    + +
    + +
    + kubernetes + Container orchestration fork +
    +
    + traefik + Reverse proxy fork +
    +
    + vault + Secrets management fork +
    +
    + terraform + IaC fork +
    +
    + consul + Service mesh fork +
    +
    + minio + Object storage fork +
    +
    +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + BlackRoad-Cloud · 30 repos · All proprietary +
    + + + diff --git a/blackroad-web-core/orgs/BlackRoad-Education.html b/blackroad-web-core/orgs/BlackRoad-Education.html new file mode 100644 index 0000000000..706029043d --- /dev/null +++ b/blackroad-web-core/orgs/BlackRoad-Education.html @@ -0,0 +1,88 @@ + + + + + + BlackRoad-Education — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Organization
    +

    BlackRoad-Education

    +

    Education organization with 7 forks covering LMS, learning content, and MOOC platforms.

    +
    +
    repos 24
    +
    status proprietary
    +
    +
    +
    + +
    + +
    + +
    repositories24
    +
    visibilityproprietary
    +
    ownerBlackRoad OS, Inc.
    + + +

    LMS, learning content, MOOC, tutorials

    + + +
    + +
    + +
    + +
    + Learning management systems + Learning management systems +
    +
    + Educational content delivery + Educational content delivery +
    +
    + MOOC platform integrations + MOOC platform integrations +
    +
    + Tutorial frameworks + Tutorial frameworks +
    +
    +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + BlackRoad-Education · 24 repos · All proprietary +
    + + + diff --git a/blackroad-web-core/orgs/BlackRoad-Foundation.html b/blackroad-web-core/orgs/BlackRoad-Foundation.html new file mode 100644 index 0000000000..73ee7584e3 --- /dev/null +++ b/blackroad-web-core/orgs/BlackRoad-Foundation.html @@ -0,0 +1,92 @@ + + + + + + BlackRoad-Foundation — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Organization
    +

    BlackRoad-Foundation

    +

    Foundation projects covering CRM, project management, and analytics tools with 12 forks.

    +
    +
    repos 30
    +
    status proprietary
    +
    +
    +
    + +
    + +
    + +
    repositories30
    +
    visibilityproprietary
    +
    ownerBlackRoad OS, Inc.
    + + +

    CRM, project management, analytics, wiki

    + + +
    + +
    + +
    + +
    + taiga + Project management fork +
    +
    + focalboard + Kanban fork +
    +
    + wiki-js + Wiki fork +
    +
    + openproject + PM fork +
    +
    + plane + Issue tracking fork +
    +
    +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + BlackRoad-Foundation · 30 repos · All proprietary +
    + + + diff --git a/blackroad-web-core/orgs/BlackRoad-Gov.html b/blackroad-web-core/orgs/BlackRoad-Gov.html new file mode 100644 index 0000000000..6812fa3973 --- /dev/null +++ b/blackroad-web-core/orgs/BlackRoad-Gov.html @@ -0,0 +1,88 @@ + + + + + + BlackRoad-Gov — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Organization
    +

    BlackRoad-Gov

    +

    Government and compliance organization with 6 forks covering voting, governance, and civic tech.

    +
    +
    repos 23
    +
    status proprietary
    +
    +
    +
    + +
    + +
    + +
    repositories23
    +
    visibilityproprietary
    +
    ownerBlackRoad OS, Inc.
    + + +

    Voting, governance, civic tech, compliance

    + + +
    + +
    + +
    + +
    + Voting systems + Voting systems +
    +
    + Governance frameworks + Governance frameworks +
    +
    + Civic technology + Civic technology +
    +
    + Compliance tools + Compliance tools +
    +
    +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + BlackRoad-Gov · 23 repos · All proprietary +
    + + + diff --git a/blackroad-web-core/orgs/BlackRoad-Hardware.html b/blackroad-web-core/orgs/BlackRoad-Hardware.html new file mode 100644 index 0000000000..0ebd255e03 --- /dev/null +++ b/blackroad-web-core/orgs/BlackRoad-Hardware.html @@ -0,0 +1,92 @@ + + + + + + BlackRoad-Hardware — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Organization
    +

    BlackRoad-Hardware

    +

    IoT and hardware projects with 10 forks covering smart home, automation, and fleet management.

    +
    +
    repos 30
    +
    status proprietary
    +
    +
    +
    + +
    + +
    + +
    repositories30
    +
    visibilityproprietary
    +
    ownerBlackRoad OS, Inc.
    + + +

    Smart home, IoT, Raspberry Pi, automation, fleet

    + + +
    + +
    + +
    + +
    + Pi management tools + Pi management tools +
    +
    + IoT broker integrations + IoT broker integrations +
    +
    + Smart home systems + Smart home systems +
    +
    + Fleet management + Fleet management +
    +
    + Automation controllers + Automation controllers +
    +
    +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + BlackRoad-Hardware · 30 repos · All proprietary +
    + + + diff --git a/blackroad-web-core/orgs/BlackRoad-Interactive.html b/blackroad-web-core/orgs/BlackRoad-Interactive.html new file mode 100644 index 0000000000..ecc589219d --- /dev/null +++ b/blackroad-web-core/orgs/BlackRoad-Interactive.html @@ -0,0 +1,88 @@ + + + + + + BlackRoad-Interactive — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Organization
    +

    BlackRoad-Interactive

    +

    Gaming and interactive projects with 11 forks covering game engines, 3D graphics, and frameworks.

    +
    +
    repos 29
    +
    status proprietary
    +
    +
    +
    + +
    + +
    + +
    repositories29
    +
    visibilityproprietary
    +
    ownerBlackRoad OS, Inc.
    + + +

    Game engines, 3D graphics, 2D graphics, frameworks

    + + +
    + +
    + +
    + +
    + Game engine integrations + Game engine integrations +
    +
    + 3D rendering systems + 3D rendering systems +
    +
    + Interactive experiences + Interactive experiences +
    +
    + Graphics frameworks + Graphics frameworks +
    +
    +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + BlackRoad-Interactive · 29 repos · All proprietary +
    + + + diff --git a/blackroad-web-core/orgs/BlackRoad-Labs.html b/blackroad-web-core/orgs/BlackRoad-Labs.html new file mode 100644 index 0000000000..8e82d12255 --- /dev/null +++ b/blackroad-web-core/orgs/BlackRoad-Labs.html @@ -0,0 +1,96 @@ + + + + + + BlackRoad-Labs — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Organization
    +

    BlackRoad-Labs

    +

    Data science and research lab with 10 forks covering orchestration, visualization, and ML ops.

    +
    +
    repos 20
    +
    status proprietary
    +
    +
    +
    + +
    + +
    + +
    repositories20
    +
    visibilityproprietary
    +
    ownerBlackRoad OS, Inc.
    + + +

    Airflow, Superset, Streamlit, MLflow, Jupyter

    + + +
    + +
    + +
    + +
    + airflow + Workflow orchestration fork +
    +
    + superset + Data visualization fork +
    +
    + streamlit + ML app framework fork +
    +
    + mlflow + ML lifecycle fork +
    +
    + jupyter + Notebook fork +
    +
    + gradio + ML demo fork +
    +
    +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + BlackRoad-Labs · 20 repos · All proprietary +
    + + + diff --git a/blackroad-web-core/orgs/BlackRoad-Media.html b/blackroad-web-core/orgs/BlackRoad-Media.html new file mode 100644 index 0000000000..3b4596740c --- /dev/null +++ b/blackroad-web-core/orgs/BlackRoad-Media.html @@ -0,0 +1,88 @@ + + + + + + BlackRoad-Media — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Organization
    +

    BlackRoad-Media

    +

    Media and content organization with 13 forks covering social, content management, and communication.

    +
    +
    repos 29
    +
    status proprietary
    +
    +
    +
    + +
    + +
    + +
    repositories29
    +
    visibilityproprietary
    +
    ownerBlackRoad OS, Inc.
    + + +

    Social media, content, communication, storage

    + + +
    + +
    + +
    + +
    + Media management tools + Media management tools +
    +
    + Content delivery systems + Content delivery systems +
    +
    + Social platform integrations + Social platform integrations +
    +
    + Communication infrastructure + Communication infrastructure +
    +
    +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + BlackRoad-Media · 29 repos · All proprietary +
    + + + diff --git a/blackroad-web-core/orgs/BlackRoad-OS-Inc.html b/blackroad-web-core/orgs/BlackRoad-OS-Inc.html new file mode 100644 index 0000000000..763b7c4f9d --- /dev/null +++ b/blackroad-web-core/orgs/BlackRoad-OS-Inc.html @@ -0,0 +1,100 @@ + + + + + + BlackRoad-OS-Inc — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Organization
    +

    BlackRoad-OS-Inc

    +

    Corporate core repos — the crown jewels of the BlackRoad enterprise.

    +
    +
    repos 7
    +
    status proprietary
    +
    +
    +
    + +
    + +
    + +
    repositories7
    +
    visibilityproprietary
    +
    ownerBlackRoad OS, Inc.
    + + +

    Corporate core, governance, proprietary IP

    + + +
    + +
    + +
    + +
    + blackroad-core + Core orchestration layer and runtime engine +
    +
    + blackroad-agents + Agent definitions, prompts, orchestration schemas +
    +
    + blackroad-web + Frontend interface and web platform +
    +
    + blackroad-infra + IaC, CI/CD workflows, deployment configs +
    +
    + blackroad-docs + Architecture docs, governance, brand system +
    +
    + blackroad-operator + CLI tooling, node bootstrap, operational control +
    +
    + demo-repository + GitHub demo repository +
    +
    +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + BlackRoad-OS-Inc · 7 repos · All proprietary +
    + + + diff --git a/blackroad-web-core/orgs/BlackRoad-OS.html b/blackroad-web-core/orgs/BlackRoad-OS.html new file mode 100644 index 0000000000..7da9ca3a1a --- /dev/null +++ b/blackroad-web-core/orgs/BlackRoad-OS.html @@ -0,0 +1,100 @@ + + + + + + BlackRoad-OS — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Organization
    +

    BlackRoad-OS

    +

    Core platform and operating system. The largest organization with 124 blackroad-os-* repos, 13 Pi projects, Lucidia agents, and 64 forks.

    +
    +
    repos 1332
    +
    status proprietary
    +
    +
    +
    + +
    + +
    + +
    repositories1332
    +
    visibilityproprietary
    +
    ownerBlackRoad OS, Inc.
    + + +

    Core platform, web, docs, agents, mesh, Pi projects, Lucidia, metaverse

    + + +
    + +
    + +
    + +
    + blackroad-os-core + Desktop UI, auth, identity +
    +
    + blackroad-os-web + Main web application (Next.js) +
    +
    + blackroad-os-docs + Documentation (Docusaurus) +
    +
    + blackroad-os-mesh + WebSocket mesh +
    +
    + blackroad-os-agents + Agent system +
    +
    + lucidia-core + AI reasoning engine +
    +
    + blackroad-os-metaverse + Three.js 3D world +
    +
    +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + BlackRoad-OS · 1332 repos · All proprietary +
    + + + diff --git a/blackroad-web-core/orgs/BlackRoad-Security.html b/blackroad-web-core/orgs/BlackRoad-Security.html new file mode 100644 index 0000000000..8346a7b795 --- /dev/null +++ b/blackroad-web-core/orgs/BlackRoad-Security.html @@ -0,0 +1,96 @@ + + + + + + BlackRoad-Security — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Organization
    +

    BlackRoad-Security

    +

    Security tools with 14 forks covering scanning, runtime protection, WAF/IDS, and secrets.

    +
    +
    repos 30
    +
    status proprietary
    +
    +
    +
    + +
    + +
    + +
    repositories30
    +
    visibilityproprietary
    +
    ownerBlackRoad OS, Inc.
    + + +

    Trivy, Falco, CrowdSec, ModSecurity, SOPS

    + + +
    + +
    + +
    + +
    + trivy + Vulnerability scanning fork +
    +
    + falco + Runtime security fork +
    +
    + crowdsec + Crowd-sourced security fork +
    +
    + modsecurity + WAF fork +
    +
    + trufflehog + Secret scanning fork +
    +
    + cilium + eBPF networking fork +
    +
    +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + BlackRoad-Security · 30 repos · All proprietary +
    + + + diff --git a/blackroad-web-core/orgs/BlackRoad-Studio.html b/blackroad-web-core/orgs/BlackRoad-Studio.html new file mode 100644 index 0000000000..bdd0a31822 --- /dev/null +++ b/blackroad-web-core/orgs/BlackRoad-Studio.html @@ -0,0 +1,92 @@ + + + + + + BlackRoad-Studio — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Organization
    +

    BlackRoad-Studio

    +

    Creative tools organization with 7 forks covering design, 3D modeling, audio, and video.

    +
    +
    repos 19
    +
    status proprietary
    +
    +
    +
    + +
    + +
    + +
    repositories19
    +
    visibilityproprietary
    +
    ownerBlackRoad OS, Inc.
    + + +

    Design tools, 3D modeling, audio, video

    + + +
    + +
    + +
    + +
    + Design system tools + Design system tools +
    +
    + 3D modeling integrations + 3D modeling integrations +
    +
    + Audio processing + Audio processing +
    +
    + Video production + Video production +
    +
    + Creative workflows + Creative workflows +
    +
    +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + BlackRoad-Studio · 19 repos · All proprietary +
    + + + diff --git a/blackroad-web-core/orgs/BlackRoad-Ventures.html b/blackroad-web-core/orgs/BlackRoad-Ventures.html new file mode 100644 index 0000000000..cab5ae64a5 --- /dev/null +++ b/blackroad-web-core/orgs/BlackRoad-Ventures.html @@ -0,0 +1,88 @@ + + + + + + BlackRoad-Ventures — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Organization
    +

    BlackRoad-Ventures

    +

    Business and finance organization with 9 forks covering crypto, analytics, and e-commerce.

    +
    +
    repos 17
    +
    status proprietary
    +
    +
    +
    + +
    + +
    + +
    repositories17
    +
    visibilityproprietary
    +
    ownerBlackRoad OS, Inc.
    + + +

    Crypto, analytics, finance, e-commerce

    + + +
    + +
    + +
    + +
    + Cryptocurrency tools + Cryptocurrency tools +
    +
    + Business analytics + Business analytics +
    +
    + Financial modeling + Financial modeling +
    +
    + E-commerce platforms + E-commerce platforms +
    +
    +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + BlackRoad-Ventures · 17 repos · All proprietary +
    + + + diff --git a/blackroad-web-core/orgs/Blackbox-Enterprises.html b/blackroad-web-core/orgs/Blackbox-Enterprises.html new file mode 100644 index 0000000000..14bf65bf5f --- /dev/null +++ b/blackroad-web-core/orgs/Blackbox-Enterprises.html @@ -0,0 +1,100 @@ + + + + + + Blackbox-Enterprises — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Organization
    +

    Blackbox-Enterprises

    +

    Enterprise workflow automation with 8 forks covering n8n, Airbyte, Temporal, and more.

    +
    +
    repos 21
    +
    status proprietary
    +
    +
    +
    + +
    + +
    + +
    repositories21
    +
    visibilityproprietary
    +
    ownerBlackRoad OS, Inc.
    + + +

    Workflow automation, ETL, orchestration

    + + +
    + +
    + +
    + +
    + blackbox-n8n + Workflow automation (Node.js) +
    +
    + blackbox-airbyte + Data integration (Java/Python) +
    +
    + blackbox-activepieces + No-code automation +
    +
    + blackbox-prefect + Data orchestration (Python) +
    +
    + blackbox-temporal + Durable execution (Go) +
    +
    + blackbox-kestra + Event-driven workflows +
    +
    + blackbox-huginn + Agent automation (Ruby) +
    +
    +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + Blackbox-Enterprises · 21 repos · All proprietary +
    + + + diff --git a/blackroad-web-core/orgs/blackboxprogramming.html b/blackroad-web-core/orgs/blackboxprogramming.html new file mode 100644 index 0000000000..3f992a8cf6 --- /dev/null +++ b/blackroad-web-core/orgs/blackboxprogramming.html @@ -0,0 +1,96 @@ + + + + + + blackboxprogramming — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Organization
    +

    blackboxprogramming

    +

    Personal account and SDKs. Primary development hub with 22 private repos.

    +
    +
    repos 68
    +
    status proprietary
    +
    +
    +
    + +
    + +
    + +
    repositories68
    +
    visibilityproprietary
    +
    ownerBlackRoad OS, Inc.
    + + +

    SDKs, personal projects, scripts, API

    + + +
    + +
    + +
    + +
    + blackroad-operator + Operator tooling +
    +
    + BLACKROAD-OS-MASTER + Master configs +
    +
    + blackroad-scripts + Automation scripts +
    +
    + blackroad-api + API server +
    +
    + blackroad.io + Main website +
    +
    + blackroad-disaster-recovery + DR configs +
    +
    +
    + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + blackboxprogramming · 68 repos · All proprietary +
    + + + diff --git a/blackroad-web-core/pages/domains.html b/blackroad-web-core/pages/domains.html new file mode 100644 index 0000000000..4b64af5d1a --- /dev/null +++ b/blackroad-web-core/pages/domains.html @@ -0,0 +1,138 @@ + + + + + + Domains — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    Domain Portfolio
    +

    19
    Domains

    +

    Every registered domain under BlackRoad OS, Inc. management.

    +
    +
    + + + +
    + BlackRoad OS, Inc. — Delaware C-Corp + 19 domains · Cloudflare managed +
    + + + diff --git a/blackroad-web-core/pages/enterprise.html b/blackroad-web-core/pages/enterprise.html new file mode 100644 index 0000000000..5c5437bda8 --- /dev/null +++ b/blackroad-web-core/pages/enterprise.html @@ -0,0 +1,256 @@ + + + + + + Enterprise — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    GitHub Enterprise
    +

    BlackRoad
    Enterprise

    +

    17 organizations. 1,825+ repositories. 30,000 AI agents. One enterprise.

    +
    +
    enterprise blackroad-os
    +
    orgs 17
    +
    repos 1,825+
    +
    agents 30,000
    +
    domains 19
    +
    +
    +
    + +
    + + +
    + + + +
    + + +
    + +
    + + +
    organizations17
    +
    total repos1,825+
    +
    public repos~1,800
    +
    private repos104+
    +
    forks managed235+
    +
    AI agents30,000
    +
    railway projects14
    +
    vercel projects15+
    +
    cloudflare workers75+
    +
    pages sites16+
    +
    domains19
    +
    shell scripts57
    +
    + + + + + + + +
    + +
    + BlackRoad OS, Inc. — Delaware C-Corp + 17 orgs · 1,825+ repos · 19 domains +
    + + + diff --git a/blackroad-web-core/pages/motion.html b/blackroad-web-core/pages/motion.html new file mode 100644 index 0000000000..98cc7e0e87 --- /dev/null +++ b/blackroad-web-core/pages/motion.html @@ -0,0 +1,201 @@ + + + + + + Motion Index — BlackRoad + + + + + + + + + +
    + + +
    +
    + +
    +
    Animation Dictionary v1.0
    +

    Motion
    Index

    +

    Shapes move in color. Text stays white or black.

    +
    +
    + +
    + palette +
    #FF8400
    +
    #FF4400
    +
    #FF0066
    +
    #CC00AA
    +
    #8800FF
    +
    #0066FF
    +
    #2233CC
    +
    text: #ffffff or #000000 only
    +
    + +
    + +
    +
    01
    +
    +
    fade
    opacity 1 → 0 → 1
    ease-in-out · 2s
    +
    + +
    +
    02
    +
    +
    slide-x
    translateX -44 → 44
    ease-in-out · 2s
    +
    + +
    +
    03
    +
    +
    slide-y
    translateY -32 → 32
    ease-in-out · 2s
    +
    + +
    +
    04
    +
    +
    pulse
    scale 1 → 2 → 1
    ease-in-out · 1.5s
    +
    + +
    +
    05
    +
    +
    spin
    rotate 0 → 360°
    linear · 2s
    +
    + +
    +
    06
    +
    +
    blink
    opacity hard cut
    step-end · 1s
    +
    + +
    +
    07
    +
    +
    bounce
    translateY 0 → -30 → 0
    ease-in-out · 1s
    +
    + +
    +
    08
    +
    +
    grow
    scaleX 0.08 → 1 → 0.08
    ease-in-out · 2s
    +
    + +
    +
    09
    +
    +
    shake
    translateX ±9px stagger
    ease-in-out · 1s
    +
    + +
    +
    10
    +
    +
    +
    +
    +
    +
    +
    orbit
    rotate + translateX 30px
    linear · 2s
    +
    + +
    +
    11
    +
    +
    + blackroad +
    +
    +
    +
    cursor
    text white · cursor white
    step-end · 0.8s
    +
    + +
    +
    12
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    wave
    scaleY staggered bars
    ease-in-out · 1s
    +
    + +
    +
    13
    +
    +
    flip
    rotateY 0 → 360°
    ease-in-out · 2s
    +
    + +
    +
    14
    +
    +
    color-cycle
    orange → pink → purple → blue
    linear · 3s
    +
    + +
    +
    15
    +
    +
    grad-shift
    gradient background sweep
    ease · 3s
    +
    + +
    +
    16
    +
    +
    + BlackRoad + BlackRoad + BlackRoad +
    +
    +
    text rule
    white on black · black on white
    grad on black (accent only)
    +
    + +
    +
    17
    +
    +
    border-pulse
    border-color cycles palette
    ease-in-out · 2s
    +
    + +
    + +
    + BlackRoad OS, Inc. + 17 primitives · shapes in color · text in white/black +
    + + + diff --git a/blackroad-web-core/pages/organizations.html b/blackroad-web-core/pages/organizations.html new file mode 100644 index 0000000000..a94c36e658 --- /dev/null +++ b/blackroad-web-core/pages/organizations.html @@ -0,0 +1,183 @@ + + + + + + Organizations — BlackRoad + + + + + + + + +
    + + +
    +
    + +
    +
    All Organizations
    +

    17
    Organizations

    +

    Every GitHub organization under BlackRoad OS, Inc. enterprise.

    +
    +
    + + + +
    + BlackRoad OS, Inc. — Delaware C-Corp + 17 organizations · All proprietary +
    + + + diff --git a/blackroad-web/src/app/worlds/page.tsx b/blackroad-web/src/app/worlds/page.tsx new file mode 100644 index 0000000000..28ff5417d2 --- /dev/null +++ b/blackroad-web/src/app/worlds/page.tsx @@ -0,0 +1,110 @@ +import { Suspense } from "react" + +async function fetchWorlds() { + try { + const res = await fetch("https://worlds.blackroad.io", { + next: { revalidate: 60 }, + }) + return res.ok ? res.json() : { worlds: [] } + } catch { + return { worlds: [] } + } +} + +async function fetchStats() { + try { + const res = await fetch("https://worlds.blackroad.io/stats", { + next: { revalidate: 60 }, + }) + return res.ok ? res.json() : null + } catch { + return null + } +} + +function WorldCard({ world }: { world: any }) { + return ( +
    +
    + {world.type} + {world.node} +
    +

    {world.title}

    + {world.excerpt && ( +

    {world.excerpt}

    + )} +
    + + {new Date(world.generated_at).toLocaleDateString()} + +
    +
    + ) +} + +export default async function WorldsPage() { + const [data, stats] = await Promise.all([fetchWorlds(), fetchStats()]) + const worlds = data?.worlds ?? [] + + return ( +
    +
    +
    +

    🌐 AI Worlds

    +

    Generated autonomously by BlackRoad Pi fleet

    +
    + + {stats && ( +
    + {[ + { label: "Total Worlds", value: stats.total }, + { label: "World Types", value: Object.keys(stats.by_type ?? {}).length }, + { label: "Pi Nodes", value: Object.keys(stats.by_node ?? {}).length }, + ].map((s) => ( +
    +
    {s.value}
    +
    {s.label}
    +
    + ))} +
    + )} + + {stats?.by_type && ( +
    + {Object.entries(stats.by_type).map(([type, count]: [string, any]) => ( +
    + {type}: + {count} +
    + ))} +
    + )} + +
    + {worlds.slice(0, 30).map((world: any) => ( + + ))} +
    + + {worlds.length === 0 && ( +
    +
    🌍
    +

    No worlds loaded — Pi fleet may be offline

    + + worlds.blackroad.io ↗ + +
    + )} +
    +
    + ) +} + +export const metadata = { + title: "AI Worlds | BlackRoad OS", + description: "Autonomously generated world artifacts from the BlackRoad Pi fleet", +} diff --git a/br b/br index 742f2429bb..1dc8502bf6 100755 --- a/br +++ b/br @@ -7,18 +7,21 @@ #=============================================================================== AGENT_HOME="${HOME}/.blackroad-agents" -source "${AGENT_HOME}/core/engine.sh" - -# Colors -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -PURPLE='\033[0;35m' -CYAN='\033[0;36m' -NC='\033[0m' - -# Agent colors (zsh associative array) +source "${AGENT_HOME}/core/engine.sh" 2>/dev/null || true + +# Resolve BR_ROOT to the directory containing this script +BR_ROOT="${BR_ROOT:-$(cd "$(dirname "$0")" && pwd)}" + +# Source shared libraries +BR_LIB="${BR_ROOT}/lib" +[[ -f "${BR_LIB}/colors.sh" ]] && source "${BR_LIB}/colors.sh" +[[ -f "${BR_LIB}/config.sh" ]] && source "${BR_LIB}/config.sh" +[[ -f "${BR_LIB}/db.sh" ]] && source "${BR_LIB}/db.sh" +[[ -f "${BR_LIB}/errors.sh" ]] && source "${BR_LIB}/errors.sh" +[[ -f "${BR_LIB}/system.sh" ]] && source "${BR_LIB}/system.sh" +[[ -f "${BR_LIB}/ollama.sh" ]] && source "${BR_LIB}/ollama.sh" + +# Agent colors (zsh associative array — kept for backward compat) typeset -A AGENT_COLORS AGENT_COLORS=( octavia "$PURPLE" @@ -28,6 +31,178 @@ AGENT_COLORS=( shellfish "$RED" ) +#------------------------------------------------------------------------------- +# Tool routing table: tool_name -> script_path +# Adding a new tool = one line here +#------------------------------------------------------------------------------- +BR_TOOLS_DIR="${BR_ROOT}/tools" +typeset -A BR_TOOLS +BR_TOOLS=( + git "${BR_TOOLS_DIR}/git-integration/br-git.sh" + snippet "${BR_TOOLS_DIR}/snippet-manager/br-snippet.sh" + snip "${BR_TOOLS_DIR}/snippet-manager/br-snippet.sh" + pair "${BR_TOOLS_DIR}/pair-programming/br-pair.sh" + api "${BR_TOOLS_DIR}/api-tester/br-api.sh" + run "${BR_TOOLS_DIR}/task-runner/br-run.sh" + note "${BR_TOOLS_DIR}/quick-notes/br-note.sh" + notes "${BR_TOOLS_DIR}/quick-notes/br-note.sh" + init "${BR_TOOLS_DIR}/project-init/br-init.sh" + logs "${BR_TOOLS_DIR}/log-parser/br-logs.sh" + log "${BR_TOOLS_DIR}/log-parser/br-logs.sh" + perf "${BR_TOOLS_DIR}/perf-monitor/br-perf.sh" + performance "${BR_TOOLS_DIR}/perf-monitor/br-perf.sh" + deps "${BR_TOOLS_DIR}/dependency-helper/br-deps.sh" + dependencies "${BR_TOOLS_DIR}/dependency-helper/br-deps.sh" + session "${BR_TOOLS_DIR}/session-manager/br-session.sh" + sess "${BR_TOOLS_DIR}/session-manager/br-session.sh" + deploy "${BR_TOOLS_DIR}/deploy-manager/br-deploy.sh" + docker "${BR_TOOLS_DIR}/docker-manager/br-docker.sh" + db "${BR_TOOLS_DIR}/db-client/br-db.sh" + database "${BR_TOOLS_DIR}/db-client/br-db.sh" + env "${BR_TOOLS_DIR}/env-manager/br-env.sh" + environment "${BR_TOOLS_DIR}/env-manager/br-env.sh" + find "${BR_TOOLS_DIR}/file-finder/br-find.sh" + pi "${BR_TOOLS_DIR}/pi-manager/br-pi.sh" + raspberry "${BR_TOOLS_DIR}/pi-manager/br-pi.sh" + cloudflare "${BR_TOOLS_DIR}/cloudflare/br-cloudflare.sh" + cf "${BR_TOOLS_DIR}/cloudflare/br-cloudflare.sh" + ocean "${BR_TOOLS_DIR}/ocean-droplets/br-ocean.sh" + do "${BR_TOOLS_DIR}/ocean-droplets/br-ocean.sh" + digitalocean "${BR_TOOLS_DIR}/ocean-droplets/br-ocean.sh" + web "${BR_TOOLS_DIR}/web-dev/br-web.sh" + monitor "${BR_TOOLS_DIR}/web-monitor/br-monitor.sh" + uptime "${BR_TOOLS_DIR}/web-monitor/br-monitor.sh" + search "${BR_TOOLS_DIR}/smart-search/br-search.sh" + s "${BR_TOOLS_DIR}/smart-search/br-search.sh" + test "${BR_TOOLS_DIR}/test-suite/br-test.sh" + t "${BR_TOOLS_DIR}/test-suite/br-test.sh" + security "${BR_TOOLS_DIR}/security-scanner/br-security.sh" + sec "${BR_TOOLS_DIR}/security-scanner/br-security.sh" + scan "${BR_TOOLS_DIR}/security-scanner/br-security.sh" + backup "${BR_TOOLS_DIR}/backup-manager/br-backup.sh" + bak "${BR_TOOLS_DIR}/backup-manager/br-backup.sh" + quality "${BR_TOOLS_DIR}/code-quality/br-quality.sh" + lint "${BR_TOOLS_DIR}/code-quality/br-quality.sh" + q "${BR_TOOLS_DIR}/code-quality/br-quality.sh" + agent "${BR_TOOLS_DIR}/agent-router/br-agent.sh" + agents "${BR_TOOLS_DIR}/agent-router/br-agent.sh" + route "${BR_TOOLS_DIR}/agent-router/br-agent.sh" + worker "${BR_TOOLS_DIR}/worker-bridge/br-worker.sh" + workers "${BR_TOOLS_DIR}/worker-bridge/br-worker.sh" + edge "${BR_TOOLS_DIR}/worker-bridge/br-worker.sh" + ci "${BR_TOOLS_DIR}/ci-pipeline/br-ci.sh" + pipeline "${BR_TOOLS_DIR}/ci-pipeline/br-ci.sh" + notify "${BR_TOOLS_DIR}/notifications/br-notify.sh" + notifications "${BR_TOOLS_DIR}/notifications/br-notify.sh" + metrics "${BR_TOOLS_DIR}/metrics-dashboard/br-metrics.sh" + dash "${BR_TOOLS_DIR}/dashboard/br-dashboard.sh" + dashboard "${BR_TOOLS_DIR}/dashboard/br-dashboard.sh" + world "${BR_TOOLS_DIR}/world/br-world.sh" + w "${BR_TOOLS_DIR}/world/br-world.sh" + worlds "${BR_TOOLS_DIR}/worlds/br-worlds.sh" + wf "${BR_TOOLS_DIR}/worlds/br-worlds.sh" + code "${BR_TOOLS_DIR}/coding-assistant/br-code.sh" + c "${BR_TOOLS_DIR}/coding-assistant/br-code.sh" + stripe "${BR_TOOLS_DIR}/stripe/br-stripe.sh" + ssl "${BR_TOOLS_DIR}/ssl-manager/br-ssl.sh" + certs "${BR_TOOLS_DIR}/ssl-manager/br-ssl.sh" + vercel "${BR_TOOLS_DIR}/vercel-pro/br-vercel.sh" + vc "${BR_TOOLS_DIR}/vercel-pro/br-vercel.sh" + runtime "${BR_TOOLS_DIR}/agent-runtime/br-runtime.sh" + task "${BR_TOOLS_DIR}/agent-tasks/br-tasks.sh" + tasks "${BR_TOOLS_DIR}/agent-tasks/br-tasks.sh" + vault "${BR_TOOLS_DIR}/secrets-vault/br-vault.sh" + secret "${BR_TOOLS_DIR}/secrets-vault/br-vault.sh" + secrets "${BR_TOOLS_DIR}/secrets-vault/br-vault.sh" + harden "${BR_TOOLS_DIR}/security-hardening/br-harden.sh" + hardening "${BR_TOOLS_DIR}/security-hardening/br-harden.sh" + comply "${BR_TOOLS_DIR}/compliance-scanner/br-comply.sh" + compliance "${BR_TOOLS_DIR}/compliance-scanner/br-comply.sh" + gateway "${BR_TOOLS_DIR}/agent-gateway/br-gateway.sh" + api-gw "${BR_TOOLS_DIR}/agent-gateway/br-gateway.sh" + tpl "${BR_TOOLS_DIR}/brand/br-brand.sh" + brand "${BR_TOOLS_DIR}/brand/br-brand.sh" + health "${BR_TOOLS_DIR}/health-check/br-health.sh" + check "${BR_TOOLS_DIR}/health-check/br-health.sh" + diag "${BR_TOOLS_DIR}/health-check/br-health.sh" + wifi "${BR_TOOLS_DIR}/wifi-scanner/br-wifi.sh" + lan "${BR_TOOLS_DIR}/wifi-scanner/br-wifi.sh" + net-scan "${BR_TOOLS_DIR}/wifi-scanner/br-wifi.sh" + ai "${BR_TOOLS_DIR}/ai/br-ai.sh" + ask "${BR_TOOLS_DIR}/ai/br-ai.sh" + journal "${BR_TOOLS_DIR}/journal/br-journal.sh" + mem "${BR_TOOLS_DIR}/journal/br-journal.sh" + memory "${BR_TOOLS_DIR}/journal/br-journal.sh" + collab "${BR_TOOLS_DIR}/collab/br-collab.sh" + mesh "${BR_TOOLS_DIR}/collab/br-collab.sh" + live "${BR_TOOLS_DIR}/dashboard/br-dashboard.sh" + agents "${BR_TOOLS_DIR}/agents-live/br-agents.sh" + agent-live "${BR_TOOLS_DIR}/agents-live/br-agents.sh" + context "${BR_TOOLS_DIR}/context/br-context.sh" + ctx "${BR_TOOLS_DIR}/context/br-context.sh" + sync "${BR_TOOLS_DIR}/sync/br-sync.sh" + sync-repos "${BR_TOOLS_DIR}/sync/br-sync.sh" + snapshot "${BR_TOOLS_DIR}/snapshot/br-snapshot.sh" + snap "${BR_TOOLS_DIR}/snapshot/br-snapshot.sh" + search "${BR_TOOLS_DIR}/search/br-search.sh" + find-all "${BR_TOOLS_DIR}/search/br-search.sh" + roundup "${BR_TOOLS_DIR}/roundup/br-roundup.sh" + standup "${BR_TOOLS_DIR}/standup/br-standup.sh" + timeline "${BR_TOOLS_DIR}/timeline/br-timeline.sh" + tl "${BR_TOOLS_DIR}/timeline/br-timeline.sh" + pulse "${BR_TOOLS_DIR}/pulse/br-pulse.sh" + org "${BR_TOOLS_DIR}/org/br-org.sh" + notify "${BR_TOOLS_DIR}/notify/br-notify.sh" + cron "${BR_TOOLS_DIR}/cron/br-cron.sh" + whoami "${BR_TOOLS_DIR}/whoami/br-whoami.sh" + who "${BR_TOOLS_DIR}/whoami/br-whoami.sh" + bcast "${BR_TOOLS_DIR}/broadcast/br-broadcast.sh" + docs "${BR_TOOLS_DIR}/docs/br-docs.sh" + git-ai "${BR_TOOLS_DIR}/git-ai/br-git-ai.sh" + gai "${BR_TOOLS_DIR}/git-ai/br-git-ai.sh" + review "${BR_TOOLS_DIR}/review/br-review.sh" + status-all "${BR_TOOLS_DIR}/status-all/br-status-all.sh" + sa "${BR_TOOLS_DIR}/status-all/br-status-all.sh" + env-check "${BR_TOOLS_DIR}/env-check/br-env-check.sh" + envc "${BR_TOOLS_DIR}/env-check/br-env-check.sh" + port "${BR_TOOLS_DIR}/port/br-port.sh" + task "${BR_TOOLS_DIR}/task-manager/br-task-manager.sh" + tasks "${BR_TOOLS_DIR}/task-manager/br-task-manager.sh" + log-tail "${BR_TOOLS_DIR}/log-tail/br-log-tail.sh" + logt "${BR_TOOLS_DIR}/log-tail/br-log-tail.sh" + ssh "${BR_TOOLS_DIR}/ssh/br-ssh.sh" + template "${BR_TOOLS_DIR}/template/br-template.sh" + tmpl "${BR_TOOLS_DIR}/template/br-template.sh" + fleet "${BR_TOOLS_DIR}/fleet/br-fleet.sh" + nodes "${BR_TOOLS_DIR}/fleet/br-fleet.sh" + radar "${BR_TOOLS_DIR}/context-radar/br-context-radar.sh" + ctx-radar "${BR_TOOLS_DIR}/context-radar/br-context-radar.sh" + pdf "${BR_TOOLS_DIR}/pdf-read/br-pdf-read.sh" + pdf-read "${BR_TOOLS_DIR}/pdf-read/br-pdf-read.sh" + deploy "${BR_TOOLS_DIR}/deploy-cmd/br-deploy-cmd.sh" + audit "${BR_TOOLS_DIR}/org-audit/br-org-audit.sh" + org-audit "${BR_TOOLS_DIR}/org-audit/br-org-audit.sh" + feedback "${BR_TOOLS_DIR}/product-feedback/br-product-feedback.sh" + faves "${BR_TOOLS_DIR}/product-feedback/br-product-feedback.sh" + dig "${BR_TOOLS_DIR}/product-feedback/br-product-feedback.sh" +) + +# Stream shortcut — delegates to gateway stream sub-command +stream_events() { + zsh "${BR_TOOLS_DIR}/agent-gateway/br-gateway.sh" stream +} + +dispatch_tool() { + local tool=$1 + shift + local script="${BR_TOOLS[$tool]}" + if [[ -n "$script" && -f "$script" ]]; then + "$script" "$@" + return 0 + fi + return 1 +} + show_banner() { local agent=$1 local color=${AGENT_COLORS[$agent]:-$NC} @@ -93,6 +268,502 @@ show_help() { echo "║ br teach - Teach agent a new fact ║" echo "║ br recall - Show agent's memories ║" echo "║ ║" + echo "║ CONTEXT RADAR (🎯 Smart Suggestions): ║" + echo "║ br radar daemon start - Start file watching daemon ║" + echo "║ br radar daemon status - Check daemon status ║" + echo "║ br radar suggest - Suggest related files ║" + echo "║ br radar agent - Suggest which agent to use ║" + echo "║ br radar smart - Smart context analysis ║" + echo "║ br radar context - Show recent activity ║" + echo "║ ║" + echo "║ GIT INTEGRATION (🤖 Smart Git): ║" + echo "║ br git commit - AI-generated commit messages ║" + echo "║ br git branch - Smart branch name suggestions ║" + echo "║ br git review - Pre-commit code review ║" + echo "║ br git status - Enhanced git status ║" + echo "║ ║" + echo "║ SNIPPET MANAGER (💾 Code Snippets): ║" + echo "║ br snippet save - Save code snippet ║" + echo "║ br snippet get - Retrieve snippet ║" + echo "║ br snippet list - List all snippets ║" + echo "║ br snippet search - Search snippets ║" + echo "║ ║" + echo "║ PAIR PROGRAMMING (🤖 AI Assistant): ║" + echo "║ br pair start [agent] - Start pair session ║" + echo "║ br pair ask - Ask your AI pair ║" + echo "║ br pair review - Review current changes ║" + echo "║ br pair suggest - Get smart suggestions ║" + echo "║ ║" + echo "║ API TESTER (🌐 HTTP Requests): ║" + echo "║ br api get - Make GET request ║" + echo "║ br api post - Make POST request ║" + echo "║ br api save - Save endpoint ║" + echo "║ br api list - List saved endpoints ║" + echo "║ ║" + echo "║ PRODUCTIVITY TOOLS: ║" + echo "║ br run - ⚡ Smart task detection & execution ║" + echo "║ br note add - 📝 Quick developer notes ║" + echo "║ br init