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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions .claude/commands/oracle.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ If arguments were provided, Oracle should interpret them and act accordingly:
- Business/onboarding intent ("quero começar", "plano pra minha empresa", "o que isso pode fazer pelo meu negócio") → run the full 8-step flow (detect state → initial-setup if needed → business discovery → delegate to Scout/Echo → present potential → delegate to Compass for the plan → deliver with 3 autonomy paths)
- Knowledge question ("quais agentes existem?", "como crio uma rotina?", "o que mudou na última release?") → answer directly by reading the repo

If no arguments were provided, Oracle should greet the user as a consultant and offer two clear paths upfront:
If no arguments were provided, Oracle must FIRST run Step 0 (detect workspace state) before greeting — never assume the user is new. Read `config/workspace.yaml`, glob `workspace/*/`, check `memory/` for content, and check whether the scheduler has run recently. Classify the workspace as **fresh install**, **fully configured**, or **partial**, then branch the greeting accordingly:

1. **Consultoria de negócio + plano de implementação** — "me conta sobre sua empresa e eu monto um plano personalizado do que você pode automatizar aqui" (this is the primary path for new users)
2. **Tirar uma dúvida pontual** — agentes, skills, rotinas, integrações, dashboard, configuração (for users who just want information)
- **Fresh install** (no owner/company set, empty memory, no recent activity) → greet as a consultant and offer two paths upfront:
1. **Consultoria de negócio + plano de implementação** — "me conta sobre sua empresa e eu monto um plano personalizado do que você pode automatizar aqui" (the primary path for new users)
2. **Tirar uma dúvida pontual** — agentes, skills, rotinas, integrações, dashboard, configuração (for users who just want information)

Do NOT dump a menu of workspace topics without first offering the consulting path. The business consultation is Oracle's main job, not a footnote.
- **Fully configured** (owner/company set, memory populated, scheduler/heartbeats active) → do NOT pitch onboarding. Greet as a returning operator: open with a short state recap (active projects, recent sessions, anything time-sensitive), then offer to (a) review/continue what's in progress, (b) start something new, or (c) answer a specific question.

- **Partial** → greet, explain what's already configured vs. missing, and ask whether to resume setup or proceed with work.

Do NOT dump a menu of workspace topics. For a fresh install, the business consultation is Oracle's main job, not a footnote; for a configured workspace, lead with the user's actual state, not a generic pitch.
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ META_APP_SECRET=
LINKEDIN_CLIENT_ID=
LINKEDIN_CLIENT_SECRET=

# ── License — headless auto-activation ───────────────
# Set this to the email used in your first manual license registration.
# On startup, EvoNexus calls /v1/register/auto silently and skips the manual
# setup screen. Falls back to manual setup if the email isn't registered yet.
# Leave empty (or unset) to keep the default behavior.
# EVOLUTION_OPERATOR_EMAIL=operator@example.com

# ── Evolution API ────────────────────────────────────
# Your Evolution API instance URL and global API key
EVOLUTION_API_URL=
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Harassment, discrimination, or abusive behavior will not be tolerated.

### Reporting Bugs

1. Check existing [issues](https://github.com/EvolutionAPI/evo-nexus/issues)
1. Check existing [issues](https://github.com/evolution-foundation/evo-nexus/issues)
to avoid duplicates
2. Open a new issue with:
- Clear, descriptive title
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
</p>

<p align="center">
<a href="https://github.com/EvolutionAPI/evo-nexus/releases/latest"><img src="https://img.shields.io/github/v/release/EvolutionAPI/evo-nexus?include_prereleases&label=version&color=00ffa7" alt="Latest version" /></a>
<a href="https://github.com/evolution-foundation/evo-nexus/releases/latest"><img src="https://img.shields.io/github/v/release/evolution-foundation/evo-nexus?include_prereleases&label=version&color=00ffa7" alt="Latest version" /></a>
<a href="https://opensource.org/licenses/Apache-2.0"><img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License: Apache 2.0" /></a>
<a href="https://docs.evolutionfoundation.com.br"><img src="https://img.shields.io/badge/Docs-evolutionfoundation.com.br-00ffa7" alt="Documentation" /></a>
<a href="https://evolutionfoundation.com.br/community"><img src="https://img.shields.io/badge/Community-Join%20us-white" alt="Community" /></a>
Expand Down Expand Up @@ -46,7 +46,7 @@ It turns a single CLI installation into a team of **38 specialized agents** orga

## Part of the Evolution Foundation ecosystem

EvoNexus is one of the projects maintained by Evolution Foundation. It is the operating layer that orchestrates the Foundation's own work — including the development of [Evo CRM Community](https://github.com/EvolutionAPI/evo-crm-community), [Evolution API](https://github.com/EvolutionAPI/evolution-api) and [Evolution Go](https://github.com/EvolutionAPI/evolution-go).
EvoNexus is one of the projects maintained by Evolution Foundation. It is the operating layer that orchestrates the Foundation's own work — including the development of [Evo CRM Community](https://github.com/evolution-foundation/evo-crm-community), [Evolution API](https://github.com/evolution-foundation/evolution-api) and [Evolution Go](https://github.com/evolution-foundation/evolution-go).

### Why EvoNexus?

Expand Down Expand Up @@ -101,7 +101,7 @@ EvoNexus is one of the projects maintained by Evolution Foundation. It is the op
### Method 1 — Docker (no setup, runs anywhere)

```bash
curl -O https://raw.githubusercontent.com/EvolutionAPI/evo-nexus/main/docker-compose.hub.yml
curl -O https://raw.githubusercontent.com/evolution-foundation/evo-nexus/main/docker-compose.hub.yml
docker compose -f docker-compose.hub.yml up -d
open http://localhost:8080
```
Expand All @@ -117,7 +117,7 @@ npx @evoapi/evo-nexus
### Method 3 — Manual clone (developers / contributors)

```bash
git clone --depth 1 https://github.com/EvolutionAPI/evo-nexus.git
git clone --depth 1 https://github.com/evolution-foundation/evo-nexus.git
cd evo-nexus

# Interactive setup wizard
Expand Down
81 changes: 76 additions & 5 deletions dashboard/backend/licensing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

Protocol:
POST /v1/register/direct — register with email/name, receive api_key
POST /v1/register/auto — headless register by email (must exist server-side)
POST /v1/activate — validate existing api_key on startup
GET /api/geo — geo-lookup from client IP
"""

import hashlib
import hmac as hmac_mod
import os
import socket
import uuid
import logging
Expand Down Expand Up @@ -155,6 +157,24 @@ def direct_register(email: str, name: str, instance_id: str,
return _post("/v1/register/direct", payload)


# ── Auto Registration (email-only, headless) ──

def auto_register(email: str, instance_id: str) -> dict:
"""Headless registration using only the operator email.

The customer must already exist on the licensing server (one prior manual
registration). Used by the EVOLUTION_OPERATOR_EMAIL env-var flow.

Returns {api_key, customer_id, tier, status}.
"""
return _post("/v1/register/auto", {
"email": email,
"tier": TIER,
"instance_id": instance_id,
"version": VERSION,
})


# ── Activation (startup with existing api_key) ──

def activate(instance_id: str, api_key: str) -> bool:
Expand Down Expand Up @@ -260,8 +280,54 @@ def initialize_runtime():

# ── Auto-register for existing installs ──────

def try_auto_register_from_env(instance_id: str) -> bool:
"""Headless activation via EVOLUTION_OPERATOR_EMAIL env var.

Requires the email to already exist on the licensing server (one prior
manual registration). Returns True on success.

Failures are silent — caller falls back to the existing admin-based or
manual setup flow.
"""
email = os.environ.get("EVOLUTION_OPERATOR_EMAIL", "").strip()
if not email:
return False

try:
result = auto_register(email=email, instance_id=instance_id)
except requests.HTTPError as e:
status = e.response.status_code if e.response is not None else "?"
if status == 404:
logger.info("Auto-activation skipped — email not registered yet (first time?).")
else:
logger.warning(f"Auto-activation rejected ({status}): falling back to manual flow.")
return False
except Exception as e:
logger.warning(f"Auto-activation skipped — {e}")
return False

api_key = result.get("api_key")
if not api_key:
logger.warning("Auto-activation response missing api_key")
return False

set_runtime_config("api_key", api_key)
set_runtime_config("tier", result.get("tier", TIER))
if result.get("customer_id"):
set_runtime_config("customer_id", str(result["customer_id"]))
set_runtime_config("version", VERSION)
set_runtime_config("registered_at", datetime.now(timezone.utc).isoformat())

ctx = get_context()
ctx.api_key = api_key
ctx.instance_id = instance_id
logger.info("License activated automatically via EVOLUTION_OPERATOR_EMAIL")
return True


def auto_register_if_needed():
"""If users exist but no license, register retroactively."""
"""If no license yet, try EVOLUTION_OPERATOR_EMAIL first, then fall back to
the admin-based retroactive flow."""
try:
instance_id = get_runtime_config("instance_id")
api_key = get_runtime_config("api_key")
Expand All @@ -270,6 +336,15 @@ def auto_register_if_needed():
initialize_runtime()
return

if not instance_id:
instance_id = generate_instance_id()
set_runtime_config("instance_id", instance_id)

# First-class path: silent activation from env var.
if try_auto_register_from_env(instance_id):
return

# Fallback: if there's an admin user already, register retroactively.
from models import User
if User.query.count() == 0:
return
Expand All @@ -278,10 +353,6 @@ def auto_register_if_needed():
if not admin or not admin.email:
return

if not instance_id:
instance_id = generate_instance_id()
set_runtime_config("instance_id", instance_id)

setup_perform(
email=admin.email or "",
name=admin.display_name or admin.username,
Expand Down