Skip to content
Draft
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
41 changes: 38 additions & 3 deletions .claude/skills/fin-daily-pulse/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
name: fin-daily-pulse
description: "Daily financial pulse — queries Stripe (MRR, charges, churn, failures) and Omie (accounts payable/receivable, invoices) to generate an HTML snapshot of the company's financial health. Trigger when user says 'financial pulse', 'financial snapshot', or 'financial metrics'."
description: "Daily financial pulse — queries Stripe (MRR, charges, churn, failures), Omie (accounts payable/receivable, invoices) and Evo Academy (courses, subscriptions, Summit tickets) to generate an HTML snapshot of the company's financial health. Trigger when user says 'financial pulse', 'financial snapshot', or 'financial metrics'."
---

# Financial Pulse — Daily Financial Snapshot

Daily routine that pulls data from Stripe and Omie to generate an HTML snapshot of financial health.
Daily routine that pulls data from Stripe, Omie and Evo Academy to generate an HTML snapshot of financial health.

**Always respond in English.**

Expand Down Expand Up @@ -47,15 +47,49 @@ Use the `/int-omie` skill to fetch:
- Fetch invoices pending issuance
- Count invoices issued in the current month


## Step 2.5 — Collect Evo Academy data (silently)

Call the Evo Academy Analytics API directly:
- **Base URL:** `$EVO_ACADEMY_BASE_URL` (env var)
- **Auth:** `Authorization: Bearer $EVO_ACADEMY_API_KEY`

### 2.5a. Summary do dia
```
GET /api/v1/analytics/summary?period=today
```
Captura: `revenue.total`, `orders.completed`, `orders.pending`, `orders.failed`, `subscriptions.active`, `students.new_in_period`

### 2.5b. Orders completados hoje
```
GET /api/v1/analytics/orders?status=completed&created_after=YYYY-MM-DD&per_page=100
```
(hoje em BRT; converter para UTC → `created_after = date.today().isoformat()`)
- Itere paginação por cursor até `meta.has_more = false`
- Some `amount` de todos os orders → receita bruta Evo Academy do dia
- Separe por tipo: renovações (`is_renewal=true`) vs novos (`is_renewal=false`)
- Agrupe por produto: cursos, assinaturas, ingressos, outros

### 2.5c. MRR de assinaturas ativas (Evo Academy)
```
GET /api/v1/analytics/subscriptions?status=active&per_page=100
```
- Itere até `meta.has_more = false`
- Some `plan.price` de cada assinatura ativa → MRR Evo Academy

## Step 3 — Day's transactions

Consolidate all financial transactions for the day:
- Stripe charges (revenue)
- Evo Academy orders (revenue — courses / subscriptions / tickets)
- Payments recorded in Omie (expenses)
- Refunds

Format each transaction with: type (Revenue/Expense/Refund), description, amount, status.

**Total revenue = Stripe today + Evo Academy today**
**Total MRR = Stripe MRR + Evo Academy MRR**

## Step 4 — Classify financial health

Define the health badge (CSS class):
Expand Down Expand Up @@ -105,7 +139,8 @@ Create the directory `workspace/finance/reports/daily/` if it does not exist.
## Financial Pulse generated

**File:** workspace/finance/reports/daily/[C] YYYY-MM-DD-financial-pulse.html
**MRR:** R$ X,XXX | **Subscriptions:** N | **Churn:** X%
**MRR total:** R$ X,XXX (Stripe: R$ X,XXX | Evo Academy: R$ X,XXX)
**Receita hoje:** R$ X,XXX | **Subscriptions:** N | **Churn:** X%
**Alerts:** {N} attention points
```

Expand Down
44 changes: 31 additions & 13 deletions .claude/skills/fin-monthly-close-kickoff/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: fin-monthly-close-kickoff
description: "Monthly close kickoff — initiates the month-end closing process with a checklist, simplified P&L, pending reconciliations, receivables, payables, and action items for the finance team. Trigger when user says 'monthly close', 'start closing', 'closing kickoff', or on the 1st of each month."
description: "Monthly close kickoff — initiates the month-end closing process with a checklist, simplified P&L (Stripe + Omie + Evo Academy), pending reconciliations, receivables, payables, and action items for the finance team. Trigger when user says 'monthly close', 'start closing', 'closing kickoff', or on the 1st of each month."
---

# Monthly Close Kickoff
Expand Down Expand Up @@ -33,14 +33,30 @@ Use `/int-omie`:
- Invoices issued during the month
- Invoices that should have been issued but were not

### 2c. Outstanding receivables

### 2c. Revenue (Evo Academy)
Call `GET /api/v1/analytics/summary?period=30d` (env: `$EVO_ACADEMY_BASE_URL`, auth: `Bearer $EVO_ACADEMY_API_KEY`):
- `revenue.total` → receita bruta do mês
- `orders.completed / pending / refunded` → contagem por status
- `subscriptions.active / cancelled` → base e churn do mês

Fetch todos os orders do mês: `GET /api/v1/analytics/orders?status=completed&created_after=YYYY-MM-01&created_before=YYYY-MM-31&per_page=100`
- Itere por cursor até `has_more=false`
- Some `amount` → receita total do mês
- Separe por produto: Evo Academy (R$950/mês), Evolution Builder (R$970/mês), Curso Agentic Engineer (R$2k/mês), Beta Access (R$370/mês), one-time (Blueprint Pack, Fast Start Pro), Evo Setup (R$5/mês)
- Identifique renovações (`is_renewal=true`) vs novos clientes

Fetch assinaturas ativas no fim do mês: `GET /api/v1/analytics/subscriptions?status=active&per_page=100`
- MRR Evo Academy = soma de `plan.price` das ativas

### 2d. Outstanding receivables
- List all open receivables (from the month or earlier)
- Highlight overdue items

### 2d. Next month's payables
### 2e. Next month's payables
- List payables due in the current month (the upcoming month)

### 2e. Previous month (for comparison)
### 2f. Previous month (for comparison)
- Read the previous month's financial report from `workspace/finance/reports/monthly/` if it exists
- Or use data from the last monthly close

Expand All @@ -51,6 +67,7 @@ Structure the income statement with:
| Account | Actual | Prior Month | Variance |
|---------|--------|-------------|----------|
| Gross Revenue (Stripe) | | | |
| Gross Revenue (Evo Academy) | | | |
| Gross Revenue (Omie/Services) | | | |
| (-) Taxes | | | |
| **Net Revenue** | | | |
Expand All @@ -68,14 +85,15 @@ Structure the income statement with:
Generate a checklist with initial status for each item:

1. **Reconcile Stripe** — verify all charges match received payments
2. **Reconcile Omie** — verify entries and exits in the ERP are correct
3. **Issue pending invoices** — list invoices that need to be issued (finance team)
4. **Collect overdue accounts** — list clients with late payments
5. **Categorize expenses** — verify all expenses are categorized
6. **Review entries** — verify manual or atypical entries
7. **Calculate taxes** — verify month's tax obligations
8. **Generate final income statement** — after reconciliations, generate the definitive P&L
9. **Approve close** — the responsible person reviews and approves
2. **Reconcile Evo Academy** — verify orders and subscriptions match expected MRR
3. **Reconcile Omie** — verify entries and exits in the ERP are correct
4. **Issue pending invoices** — list invoices that need to be issued (finance team)
5. **Collect overdue accounts** — list clients with late payments
6. **Categorize expenses** — verify all expenses are categorized
7. **Review entries** — verify manual or atypical entries
8. **Calculate taxes** — verify month's tax obligations
9. **Generate final income statement** — after reconciliations, generate the definitive P&L
10. **Approve close** — the responsible person reviews and approves

Possible statuses:
- `done` (checkmark) — already completed automatically
Expand Down Expand Up @@ -161,7 +179,7 @@ Create the directory `workspace/finance/reports/monthly/` if it does not exist.
**File:** workspace/finance/reports/monthly/[C] YYYY-MM-monthly-close.html
**Month:** {reference month}
**Revenue:** R$ X,XXX | **Expenses:** R$ X,XXX | **Result:** R$ X,XXX
**Checklist:** X/9 completed
**Checklist:** X/10 completed
**Finance team pending items:** {N} items
```

Expand Down
34 changes: 30 additions & 4 deletions .claude/skills/fin-weekly-report/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
name: fin-weekly-report
description: "Weekly financial report — consolidates Stripe and Omie data for the week: revenue, expenses, cash flow projection, overdue accounts, and variance analysis. Trigger when user says 'financial weekly', 'weekly financial report', or 'financial summary of the week'."
description: "Weekly financial report — consolidates Stripe, Omie and Evo Academy data for the week: revenue (courses, subscriptions, tickets), expenses, cash flow projection, overdue accounts, and variance analysis. Trigger when user says 'financial weekly', 'weekly financial report', or 'financial summary of the week'."
---

# Financial Weekly — Weekly Financial Report

Weekly routine that consolidates the week's financial data: revenue, expenses, Stripe, Omie, projected cash flow, and analysis.
Weekly routine that consolidates the week's financial data: revenue, expenses, Stripe, Omie, Evo Academy, projected cash flow, and analysis.

**Always respond in English.**

Expand All @@ -24,8 +24,25 @@ Use `/int-omie` to fetch:
- Confirmed receipts for the week
- Invoices issued during the week


### 1c. Evo Academy — revenue
Call `GET /api/v1/analytics/summary?period=7d` (env: `$EVO_ACADEMY_BASE_URL`, auth: `Bearer $EVO_ACADEMY_API_KEY`):
- `revenue.total` → receita bruta da semana
- `orders.completed` → número de vendas
- `subscriptions.active` / `subscriptions.cancelled` → net change

Fetch orders da semana: `GET /api/v1/analytics/orders?status=completed&created_after=YYYY-MM-DD&per_page=100`
- Itere por cursor até `has_more=false`
- Some `amount` → receita total Evo Academy na semana
- Separe: renovações vs novos, one-time vs assinatura

Fetch assinaturas novas na semana: `GET /api/v1/analytics/subscriptions?status=active&created_after=YYYY-MM-DD&per_page=100`
- MRR adicionado = soma dos `plan.price` de assinaturas criadas na semana

Group revenue by category:
- Stripe Subscriptions
- Evo Academy — Courses & Subscriptions
- Evo Academy — One-time (tickets, packs)
- Services / Consulting
- Partnerships
- Other
Expand Down Expand Up @@ -59,10 +76,19 @@ Consolidate the week's Omie metrics:
- Invoices issued during the week
- Confirmed receipts

## Step 4.5 — Detailed Evo Academy metrics

Consolidate Evo Academy's week metrics:
- MRR (sum of all active subscription `plan.price`) and variance vs prior week
- New subscriptions vs cancellations
- One-time revenue (tickets, packs, live events)
- Top-selling products of the week
- Students enrolled (`students.new_in_period`)

## Step 5 — Cash flow projection (4 weeks)

Based on collected data, project:
- Expected inflows (Stripe recurring + receivables)
- Expected inflows (Stripe recurring + Evo Academy subscriptions + receivables)
- Expected outflows (payables + recurring expenses)
- Balance and cumulative by week

Expand Down Expand Up @@ -135,7 +161,7 @@ Create the directory `workspace/finance/reports/weekly/` if it does not exist.

**File:** workspace/finance/reports/weekly/[C] YYYY-WXX-financial-weekly.html
**Revenue:** R$ X,XXX ({var}%) | **Expenses:** R$ X,XXX ({var}%)
**MRR:** R$ X,XXX | **Projected 30d balance:** R$ XX,XXX
**MRR total:** R$ X,XXX (Stripe: R$ X,XXX | Evo Academy: R$ X,XXX) | **Projected 30d balance:** R$ XX,XXX
**Alerts:** {N} overdue accounts | {N} pending invoices
```

Expand Down
5 changes: 4 additions & 1 deletion .claude/skills/prod-end-of-day/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ The log should include:

## Step 5 — Organize tasks

Run `/prod-review-todoist` to ensure tasks created during the day are categorized and translated.
Review Todoist tasks directly (do NOT invoke `/prod-review-todoist` as a sub-skill — it sends a duplicate Telegram notification):
- Run `todoist today` to list today's tasks
- For each uncategorized or non-PT-BR task: rename/recategorize via `todoist update`
- Report how many were organized

## Step 6 — Confirm

Expand Down
5 changes: 3 additions & 2 deletions .claude/skills/pulse-faq-sync/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@ The "Skipped" block is mandatory — it gives visibility on questions the commun

### Notify via Telegram

Upon completion, send a short summary via Telegram to the user:
Upon completion, send **exactly ONE** Telegram message with the full summary:
- Use the Telegram MCP: `reply(chat_id="YOUR_CHAT_ID", text="...")`
- Format: emoji + routine name + main result (1-3 lines)
- Format: emoji + routine name + main result (totals + alerts combined in one message)
- Do NOT split into multiple messages — combine summary and alerts into a single call
- If the routine had no updates, send anyway with "no updates"
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,14 @@ site/lib/
mempalace.yaml
entities.json
.claude/worktrees/
.codex
.codex/
logs/
community/
courses/
daily-logs/
finance/
meetings/
projects/
social/
strategy/
28 changes: 26 additions & 2 deletions ADWs/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import os
import sys
import json
import shutil
from datetime import datetime
from pathlib import Path

Expand Down Expand Up @@ -127,6 +128,27 @@ def _log_to_file(log_name, prompt, stdout, stderr, returncode, duration, usage=N
_ALLOWED_CLI_COMMANDS = frozenset({"claude", "openclaude"})


def _augment_path(base_path: str | None = None) -> str:
"""Extend PATH with common user-local install locations for CLI tools."""
path_parts = [p for p in (base_path or "").split(os.pathsep) if p]
for candidate in (
str(Path.home() / ".local" / "bin"),
str(Path.home() / ".npm-global" / "bin"),
"/usr/local/bin",
"/usr/bin",
"/bin",
):
if candidate not in path_parts:
path_parts.append(candidate)
return os.pathsep.join(path_parts)


def _resolve_cli_path(cli_command: str, env: dict) -> str:
"""Resolve CLI binary path from the effective environment."""
resolved = shutil.which(cli_command, path=env.get("PATH"))
return resolved or cli_command


def _spawn_cli(cli_command: str, prompt: str, agent: str | None, provider_env: dict) -> subprocess.Popen:
"""Spawn a CLI process using only hardcoded command strings.

Expand All @@ -139,6 +161,8 @@ def _spawn_cli(cli_command: str, prompt: str, agent: str | None, provider_env: d
base_args.append(prompt)

env = {**os.environ, **provider_env, "TERM": "dumb"}
env["PATH"] = _augment_path(env.get("PATH"))
resolved_cli = _resolve_cli_path(cli_command, env)
popen_kwargs = dict(
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
Expand All @@ -149,9 +173,9 @@ def _spawn_cli(cli_command: str, prompt: str, agent: str | None, provider_env: d

# Hardcoded dispatch — each branch uses a literal string for the executable
if cli_command == "openclaude":
return subprocess.Popen(["openclaude"] + base_args, **popen_kwargs) # noqa: S603
return subprocess.Popen([resolved_cli] + base_args, **popen_kwargs) # noqa: S603
else:
return subprocess.Popen(["claude"] + base_args, **popen_kwargs) # noqa: S603
return subprocess.Popen([resolved_cli] + base_args, **popen_kwargs) # noqa: S603
_ALLOWED_ENV_VARS = frozenset({
"CLAUDE_CODE_USE_OPENAI", "CLAUDE_CODE_USE_GEMINI", "CLAUDE_CODE_USE_BEDROCK",
"CLAUDE_CODE_USE_VERTEX", "OPENAI_BASE_URL", "OPENAI_API_KEY", "OPENAI_MODEL",
Expand Down
53 changes: 17 additions & 36 deletions dashboard/backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,13 +637,16 @@ def serve_frontend(path):
port = int(cfg["port"])
except Exception:
pass
# Start scheduler in background thread
# Scheduler runs as a standalone process (scheduler.py) started by start-services.sh.
# A thread here would create a duplicate instance — all routines would fire 2-3x.
# One-off scheduled tasks (ScheduledTask model) are checked by the standalone scheduler
# via _run_pending_tasks, which is called from its own loop.
import threading

def _run_pending_tasks():
"""Check for pending scheduled tasks and execute them."""
from datetime import datetime as _dt, timezone as _tz
from models import ScheduledTask
from routes.tasks import _execute_task

try:
now = _dt.now(_tz.utc)
Expand All @@ -659,45 +662,23 @@ def _run_pending_tasks():

t = threading.Thread(target=_execute_task_with_context, args=(task.id,), daemon=True)
t.start()
except Exception as e:
pass # Don't crash scheduler loop on task errors
except Exception:
pass

def _execute_task_with_context(task_id):
with app.app_context():
from routes.tasks import _execute_task
_execute_task(task_id)

def _run_scheduler():
log_path = WORKSPACE / "ADWs" / "logs" / "scheduler.log"
log_path.parent.mkdir(parents=True, exist_ok=True)
try:
import importlib.util
spec = importlib.util.spec_from_file_location("scheduler", WORKSPACE / "scheduler.py")
sched_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(sched_module)
sched_module.setup_schedule()

import schedule as sched_lib
import time as _time
from datetime import datetime as _dt

with open(log_path, "a") as log:
log.write(f"\n[{_dt.now().strftime('%Y-%m-%d %H:%M:%S')}] Scheduler started ({len(sched_lib.get_jobs())} routines)\n")
log.flush()

while True:
sched_lib.run_pending()
# Check for one-off scheduled tasks
with app.app_context():
_run_pending_tasks()
_time.sleep(30)
except Exception as e:
with open(log_path, "a") as log:
log.write(f"Scheduler error: {e}\n")
print(f"Scheduler failed to start: {e}")

sched_thread = threading.Thread(target=_run_scheduler, daemon=True, name="scheduler")
sched_thread.start()
print(f" Scheduler started in background")
def _poll_scheduled_tasks():
"""Lightweight thread that only polls ScheduledTask — no routine scheduling."""
import time as _time
while True:
with app.app_context():
_run_pending_tasks()
_time.sleep(30)

task_thread = threading.Thread(target=_poll_scheduled_tasks, daemon=True, name="task-poller")
task_thread.start()

app.run(host="0.0.0.0", port=port, debug=False)
Loading