@fazer-ai/agents 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -0
  3. package/dist/agents/claude.js +152 -0
  4. package/dist/agents/codex.js +155 -0
  5. package/dist/agents/detect.js +15 -0
  6. package/dist/agents/handoff.js +22 -0
  7. package/dist/agents/hermes-skills.js +177 -0
  8. package/dist/agents/hermes.js +474 -0
  9. package/dist/agents/index.js +57 -0
  10. package/dist/agents/manual.js +22 -0
  11. package/dist/agents/other.js +39 -0
  12. package/dist/agents/shell.js +15 -0
  13. package/dist/agents/types.js +2 -0
  14. package/dist/config.js +48 -0
  15. package/dist/exec.js +279 -0
  16. package/dist/hostinger.js +75 -0
  17. package/dist/hub-command.js +144 -0
  18. package/dist/index.js +726 -0
  19. package/dist/licenses.js +93 -0
  20. package/dist/mcp.js +100 -0
  21. package/dist/oauth.js +578 -0
  22. package/dist/onboarding-marker.js +48 -0
  23. package/dist/preferences.js +40 -0
  24. package/dist/skills/agents-dev/SKILL.md +37 -0
  25. package/dist/skills/agents-dev/gotchas.md +6 -0
  26. package/dist/skills/agents-dev/guardrails.md +6 -0
  27. package/dist/skills/agents-dev/references/00-get-the-code.md +28 -0
  28. package/dist/skills/agents-dev/references/01-layout-and-bun-check.md +29 -0
  29. package/dist/skills/agents-dev/references/02-free-full-and-invariants.md +7 -0
  30. package/dist/skills/agents-dev/references/03-implement.md +9 -0
  31. package/dist/skills/agents-dev/references/04-own-image-and-deploy.md +13 -0
  32. package/dist/skills/agents-onboarding/SKILL.md +80 -0
  33. package/dist/skills/agents-onboarding/gotchas.md +157 -0
  34. package/dist/skills/agents-onboarding/guardrails.md +65 -0
  35. package/dist/skills/agents-onboarding/references/00-prereqs-and-access.md +37 -0
  36. package/dist/skills/agents-onboarding/references/01-vps-dns-ssh.md +67 -0
  37. package/dist/skills/agents-onboarding/references/01b-brownfield.md +70 -0
  38. package/dist/skills/agents-onboarding/references/01c-pick-tier.md +61 -0
  39. package/dist/skills/agents-onboarding/references/02-coolify.md +109 -0
  40. package/dist/skills/agents-onboarding/references/03-chatwoot-pro.md +61 -0
  41. package/dist/skills/agents-onboarding/references/04-agents-image.md +46 -0
  42. package/dist/skills/agents-onboarding/references/05-langfuse.md +45 -0
  43. package/dist/skills/agents-onboarding/references/06-setup-and-mcp.md +47 -0
  44. package/dist/skills/agents-onboarding/references/08-agent-import.md +55 -0
  45. package/dist/skills/agents-onboarding/references/09-chatwoot-bind.md +41 -0
  46. package/dist/skills/agents-onboarding/references/10-validate-e2e.md +34 -0
  47. package/dist/skills/agents-onboarding/references/agent-features.md +61 -0
  48. package/dist/skills/agents-onboarding/references/chatwoot-hub-register.md +69 -0
  49. package/dist/skills/agents-onboarding/references/deploy-b-portainer.md +138 -0
  50. package/dist/skills/agents-onboarding/references/deploy-c-compose.md +64 -0
  51. package/dist/skills/agents-onboarding/samples/agents/README.md +23 -0
  52. package/dist/skills/agents-onboarding/samples/agents/maria-clinica-moreira.json +313 -0
  53. package/dist/skills/agents-onboarding/scripts/chatwoot-admin.py +248 -0
  54. package/dist/skills/agents-onboarding/scripts/coolify.py +552 -0
  55. package/dist/skills/agents-onboarding/scripts/docker-status.py +129 -0
  56. package/dist/skills/agents-onboarding/scripts/gen-onboarding-env.ts +187 -0
  57. package/dist/skills/agents-onboarding/scripts/harbor-login.py +118 -0
  58. package/dist/skills/agents-onboarding/scripts/langfuse-verify.py +118 -0
  59. package/dist/skills/agents-onboarding/scripts/portainer-brownfield.py +115 -0
  60. package/dist/skills/agents-onboarding/scripts/remote.py +198 -0
  61. package/dist/skills/agents-onboarding/scripts/sshkey.py +140 -0
  62. package/dist/skills/agents-onboarding/templates/chatwoot/.env.example +30 -0
  63. package/dist/skills/agents-onboarding/templates/chatwoot/README.md +65 -0
  64. package/dist/skills/agents-onboarding/templates/chatwoot/docker-compose.coolify.yml +136 -0
  65. package/dist/skills/agents-onboarding/templates/chatwoot/docker-compose.yml +139 -0
  66. package/dist/skills/agents-onboarding/templates/docker-compose.coolify.yml +73 -0
  67. package/dist/skills/agents-onboarding/templates/docker-compose.portainer.yml +132 -0
  68. package/dist/skills/agents-onboarding/templates/docker-compose.prod.yml +85 -0
  69. package/dist/skills/agents-onboarding/templates/langfuse/.env.example +59 -0
  70. package/dist/skills/agents-onboarding/templates/langfuse/README.md +132 -0
  71. package/dist/skills/agents-onboarding/templates/langfuse/docker-compose.coolify.yml +189 -0
  72. package/dist/skills/agents-onboarding/templates/langfuse/docker-compose.yml +185 -0
  73. package/dist/skills/agents-operation/SKILL.md +42 -0
  74. package/dist/skills/agents-operation/gotchas.md +61 -0
  75. package/dist/skills/agents-operation/guardrails.md +26 -0
  76. package/dist/skills/agents-operation/references/00-production-safety.md +24 -0
  77. package/dist/skills/agents-operation/references/01-diagnose.md +34 -0
  78. package/dist/skills/agents-operation/references/02-reproduce.md +22 -0
  79. package/dist/skills/agents-operation/references/03-adjust.md +36 -0
  80. package/dist/skills/agents-operation/references/04-validate-and-apply.md +31 -0
  81. package/dist/ui-select.js +279 -0
  82. package/dist/ui.js +167 -0
  83. package/package.json +53 -0
@@ -0,0 +1,139 @@
1
+ # Chatwoot for fazer.ai: GENERIC flavor (Portainer / EasyPanel / Dokploy / plain Docker).
2
+ #
3
+ # ONE file, BOTH editions, selected by env (see ./README.md):
4
+ # - OSS (no hub subscription): defaults, our public `ghcr.io/fazer-ai/chatwoot` fork, no Baileys.
5
+ # - Pro (hub subscription): set CHATWOOT_IMAGE to the Harbor image AND COMPOSE_PROFILES=pro
6
+ # (adds the Baileys WhatsApp provider). Requires `docker login harbor.fazer.ai` with the
7
+ # license registry credential (hub: generate_install_script).
8
+ #
9
+ # Secrets come from a .env you fill in (see ./.env.example); Coolify generates those automatically,
10
+ # so it has its own file (./docker-compose.coolify.yml). TLS + reverse proxy are EXTERNAL: this stack
11
+ # only publishes Chatwoot's HTTP port on the host (default 127.0.0.1:3001); terminate TLS in front
12
+ # (the fazer.ai agents bundled Caddy can front chatwoot.<domain> at it; see the agents-onboarding skill).
13
+ #
14
+ # cp .env.example .env # fill every CHANGE_ME, then:
15
+ # docker compose up -d # OSS
16
+ # COMPOSE_PROFILES=pro docker compose up -d # Pro (after the Harbor login)
17
+
18
+ x-chatwoot-env: &chatwoot-env
19
+ NODE_ENV: production
20
+ RAILS_ENV: production
21
+ INSTALLATION_ENV: docker
22
+ DEFAULT_LOCALE: '${CHATWOOT_LOCALE:-en}'
23
+ LOG_LEVEL: '${RAILS_LOG_LEVEL:-info}'
24
+ FRONTEND_URL: '${CHATWOOT_URL}'
25
+ INTERNAL_HOST_URL: 'http://chatwoot:3000'
26
+ POSTGRES_HOST: postgres
27
+ POSTGRES_PORT: '5432'
28
+ POSTGRES_USERNAME: '${POSTGRES_USER}'
29
+ POSTGRES_PASSWORD: '${POSTGRES_PASSWORD}'
30
+ POSTGRES_DATABASE: '${POSTGRES_DB:-chatwoot_production}'
31
+ SECRET_KEY_BASE: '${SECRET_KEY_BASE}'
32
+ REDIS_URL: 'redis://redis:6379'
33
+ REDIS_PASSWORD: '${REDIS_PASSWORD}'
34
+ # Pro-only (Baileys WhatsApp): harmless on OSS (no Baileys inbox can be created there).
35
+ BAILEYS_PROVIDER_DEFAULT_CLIENT_NAME: '${BAILEYS_CLIENT_NAME:-Chatwoot}'
36
+ BAILEYS_PROVIDER_DEFAULT_URL: 'http://baileys-api:3025'
37
+ BAILEYS_PROVIDER_DEFAULT_API_KEY: '${BAILEYS_DEFAULT_API_KEY:-}'
38
+ BAILEYS_PROVIDER_USE_INTERNAL_HOST_URL: 'true'
39
+
40
+ services:
41
+ chatwoot:
42
+ # OSS default (our fork); for Pro set CHATWOOT_IMAGE=harbor.fazer.ai/chatwoot/fazer-ai/chatwoot-pro:latest
43
+ image: '${CHATWOOT_IMAGE:-ghcr.io/fazer-ai/chatwoot:latest}'
44
+ pull_policy: always
45
+ volumes:
46
+ - 'storage:/app/storage'
47
+ depends_on:
48
+ postgres: { condition: service_healthy }
49
+ redis: { condition: service_healthy }
50
+ environment: *chatwoot-env
51
+ entrypoint: docker/entrypoints/rails.sh
52
+ command:
53
+ - sh
54
+ - '-c'
55
+ - 'bundle exec rails db:chatwoot_prepare && exec bundle exec rails s -p 3000 -b 0.0.0.0'
56
+ ports:
57
+ # Host:container. Front with your own TLS-terminating reverse proxy; bound to localhost by default.
58
+ - '${CHATWOOT_PORT:-127.0.0.1:3001}:3000'
59
+ restart: always
60
+ healthcheck:
61
+ test: ['CMD-SHELL', 'wget -qO- --header="Accept: text/html" http://127.0.0.1:3000/ || exit 1']
62
+ interval: 20s
63
+ timeout: 10s
64
+ retries: 10
65
+
66
+ sidekiq:
67
+ image: '${CHATWOOT_IMAGE:-ghcr.io/fazer-ai/chatwoot:latest}'
68
+ pull_policy: always
69
+ volumes:
70
+ - 'storage:/app/storage'
71
+ depends_on:
72
+ postgres: { condition: service_healthy }
73
+ redis: { condition: service_healthy }
74
+ environment: *chatwoot-env
75
+ command: ['bundle', 'exec', 'sidekiq', '-C', 'config/sidekiq.yml']
76
+ restart: always
77
+ healthcheck:
78
+ test: ['CMD-SHELL', 'ps aux | grep "[s]idekiq" || exit 1']
79
+ interval: 20s
80
+ timeout: 10s
81
+ retries: 5
82
+
83
+ postgres:
84
+ # pgvector (Chatwoot 4.x "Captain" AI uses the vector extension). Public image, no auth needed.
85
+ image: '${POSTGRES_IMAGE:-pgvector/pgvector:pg16}'
86
+ restart: always
87
+ volumes:
88
+ - 'postgres:/var/lib/postgresql/data'
89
+ environment:
90
+ POSTGRES_DB: '${POSTGRES_DB:-chatwoot_production}'
91
+ POSTGRES_USER: '${POSTGRES_USER}'
92
+ POSTGRES_PASSWORD: '${POSTGRES_PASSWORD}'
93
+ healthcheck:
94
+ test: ['CMD-SHELL', 'pg_isready -h localhost -p 5432 -U $${POSTGRES_USER} -d $${POSTGRES_DB}']
95
+ interval: 20s
96
+ timeout: 20s
97
+ retries: 10
98
+
99
+ redis:
100
+ image: 'redis:alpine'
101
+ restart: always
102
+ command: ['sh', '-c', 'redis-server --requirepass "$${REDIS_PASSWORD}"']
103
+ volumes:
104
+ - 'redis:/data'
105
+ environment:
106
+ REDIS_PASSWORD: '${REDIS_PASSWORD}'
107
+ healthcheck:
108
+ test: ['CMD-SHELL', 'redis-cli -h localhost -p 6379 -a $${REDIS_PASSWORD} ping | grep -q PONG']
109
+ interval: 20s
110
+ timeout: 20s
111
+ retries: 10
112
+
113
+ # Pro only: the Baileys WhatsApp provider. Starts ONLY with COMPOSE_PROFILES=pro.
114
+ baileys-api:
115
+ image: '${BAILEYS_IMAGE:-ghcr.io/fazer-ai/baileys-api:latest}'
116
+ profiles: ['pro']
117
+ pull_policy: always
118
+ volumes:
119
+ - 'storage:/app/storage'
120
+ environment:
121
+ NODE_ENV: production
122
+ REDIS_URL: 'redis://redis:6379'
123
+ REDIS_PASSWORD: '${REDIS_PASSWORD}'
124
+ LOG_LEVEL: '${BAILEYS_API_LOG_LEVEL:-info}'
125
+ BAILEYS_PROVIDER_DEFAULT_API_KEY: '${BAILEYS_DEFAULT_API_KEY}'
126
+ command: ['sh', '-c', 'bun manage-api-keys create user $${BAILEYS_DEFAULT_API_KEY} && bun start']
127
+ depends_on:
128
+ redis: { condition: service_healthy }
129
+ restart: always
130
+ healthcheck:
131
+ test: ['CMD-SHELL', 'wget -qO- http://localhost:3025/status || exit 1']
132
+ interval: 20s
133
+ timeout: 20s
134
+ retries: 10
135
+
136
+ volumes:
137
+ storage:
138
+ postgres:
139
+ redis:
@@ -0,0 +1,73 @@
1
+ services:
2
+ agents:
3
+ # Edition-driven: Free (default, public image) vs Pro — for Pro, set the AGENTS_IMAGE env on
4
+ # the Coolify service to the private Harbor image (project `agents`) AND register the Harbor
5
+ # registry credential in Coolify (hub: create_registry_credential, per-user, same as Chatwoot Pro).
6
+ # PLACEHOLDER: the public Free image is not published yet — swap `agents` once it exists.
7
+ image: '${AGENTS_IMAGE:-ghcr.io/fazer-ai/agents:latest}'
8
+ pull_policy: always
9
+ volumes:
10
+ - 'storage:/app/storage'
11
+ environment:
12
+ - SERVICE_URL_AGENTS
13
+ - NODE_ENV=production
14
+ - PORT=${PORT:-3000}
15
+ - PUBLIC_URL=${SERVICE_URL_AGENTS}
16
+ - LOG_LEVEL=${LOG_LEVEL:-info}
17
+ - JWT_SECRET=${SERVICE_PASSWORD_64_JWTSECRET}
18
+ - ENCRYPTION_KEY=${SERVICE_PASSWORD_64_ENCRYPTIONKEY}
19
+ - CORS_ORIGIN=${CORS_ORIGIN:-*}
20
+ - CDN_URL=${SERVICE_URL_AGENTS}
21
+ # NOTE: opt this onboarding / self-hosted deploy into MCP Dynamic Client Registration so the
22
+ # operator's AI agent (Claude Code / Codex) can `mcp login` to THIS instance's MCP — OAuth DCR
23
+ # mints the client and the metadata then publishes `registration_endpoint`. The app code default
24
+ # is OFF (closed/hardened, see docs/mcp.md); this agent-operated deploy turns it ON deliberately.
25
+ - MCP_DCR_ENABLED=true
26
+ # Persist uploaded branding (logo/favicon) + generated quote PDFs on the `storage` volume.
27
+ # The defaults (./data/*) live on the ephemeral container FS and vanish on every redeploy.
28
+ - BRANDING_STORAGE_DIR=/app/storage/branding
29
+ - QUOTES_STORAGE_DIR=/app/storage/quotes
30
+ # NOTE: TWO roles by design. The superuser (SERVICE_USER_DBUSER, also the postgres
31
+ # container's owner) runs migrations + bootstrap. The app role (SERVICE_USER_APPDBUSER,
32
+ # created by db-bootstrap.ts) is NON-superuser/NON-bypassrls so RLS isolation holds — the
33
+ # boot guard refuses to start the server on a privileged runtime role.
34
+ - 'MIGRATION_DATABASE_URL=postgres://${SERVICE_USER_DBUSER}:${SERVICE_PASSWORD_64_DBPASSWORD}@${DATABASE_HOST:-postgres}:${DATABASE_PORT:-5432}/${DATABASE_NAME:-secretaria_v4_db}'
35
+ - 'DATABASE_URL=postgres://${SERVICE_USER_APPDBUSER}:${SERVICE_PASSWORD_64_APPDBPASSWORD}@${DATABASE_HOST:-postgres}:${DATABASE_PORT:-5432}/${DATABASE_NAME:-secretaria_v4_db}'
36
+ - 'LANGGRAPH_DATABASE_URL=postgres://${SERVICE_USER_APPDBUSER}:${SERVICE_PASSWORD_64_APPDBPASSWORD}@${DATABASE_HOST:-postgres}:${DATABASE_PORT:-5432}/${DATABASE_NAME:-secretaria_v4_db}'
37
+ # NOTE: the boot sequence (provision runtime role -> migrate as owner -> run the interpreted
38
+ # server) is the image CMD in the Dockerfile. Do NOT override `command` here: it once drifted to
39
+ # a stale `./server` (from the pre-interpreted `bun build --compile` era) and crash-looped on
40
+ # `exec: ./server: not found` while the image CMD stayed correct.
41
+ restart: always
42
+ healthcheck:
43
+ test:
44
+ - CMD-SHELL
45
+ - 'wget -qO- http://localhost:3000/api/health'
46
+ interval: 20s
47
+ timeout: 10s
48
+ retries: 5
49
+ depends_on:
50
+ postgres:
51
+ condition: service_healthy
52
+ postgres:
53
+ # pgvector image (NOT plain postgres): the schema needs CREATE EXTENSION vector. pg17 matches
54
+ # the validated isolation spike.
55
+ image: 'pgvector/pgvector:pg17'
56
+ restart: always
57
+ volumes:
58
+ - 'postgres:/var/lib/postgresql/data'
59
+ environment:
60
+ - POSTGRES_DB=${DATABASE_NAME:-secretaria_v4_db}
61
+ - POSTGRES_USER=${SERVICE_USER_DBUSER}
62
+ - POSTGRES_PASSWORD=${SERVICE_PASSWORD_64_DBPASSWORD}
63
+ healthcheck:
64
+ test:
65
+ - CMD-SHELL
66
+ - 'pg_isready -h localhost -p 5432 -U $${POSTGRES_USER} -d $${POSTGRES_DB}'
67
+ interval: 20s
68
+ timeout: 20s
69
+ retries: 10
70
+
71
+ volumes:
72
+ storage:
73
+ postgres:
@@ -0,0 +1,132 @@
1
+ # Self-contained production stack for fazer.ai agents on Portainer (Tier B deploy) — and any host where
2
+ # you want TLS terminated INSIDE the stack instead of by an external proxy.
3
+ #
4
+ # Difference vs docker-compose.prod.yml: prod.yml only publishes the app's HTTP port and expects you to
5
+ # bring your own reverse proxy (Traefik/Caddy/NPM). THIS file bundles a Caddy service that terminates TLS
6
+ # and obtains a REAL certificate automatically (Let's Encrypt / ZeroSSL via the HTTP-01 challenge) for
7
+ # CADDY_DOMAIN — no Traefik labels, no Docker-socket polling. That is the Tier B "agent brings up Caddy"
8
+ # path in the onboarding plan.
9
+ #
10
+ # WHY SELF-CONTAINED (not a `-f` override of prod.yml): Portainer's "deploy from string" API
11
+ # (POST /api/stacks/create/standalone/string) accepts ONE compose document, so this file repeats prod.yml's
12
+ # agents + postgres verbatim and adds caddy. Keep those two blocks in sync with docker-compose.prod.yml.
13
+ #
14
+ # Fill the env with:
15
+ # bun scripts/gen-onboarding-env.ts --public-url https://agentes.example.com [--acme-email you@example.com]
16
+ # It emits CADDY_DOMAIN + the two-role DB URLs + secrets. On Portainer, paste this file as a Stack and set
17
+ # the same variables in the Stack env editor (or pass them as the API's Env[] array). To also serve the
18
+ # Portainer UI itself on a clean HTTPS domain, set PORTAINER_DOMAIN (e.g. portainer.example.com) — Caddy
19
+ # adds a second site reverse-proxying to the host's Portainer (9443, self-signed upstream).
20
+ #
21
+ # TWO database roles by design (docs/deploy.md): MIGRATION_DATABASE_URL = superuser (migrate + bootstrap);
22
+ # DATABASE_URL / LANGGRAPH_DATABASE_URL = NON-superuser runtime role (RLS holds; the boot guard enforces it).
23
+ # SINGLE replica by construction (scheduler / debounce / webhook / alert workers + realtime pub/sub are
24
+ # single-process). Do NOT scale agents to >1 here.
25
+
26
+ services:
27
+ caddy:
28
+ image: 'caddy:2'
29
+ restart: always
30
+ depends_on:
31
+ - agents
32
+ ports:
33
+ - '80:80'
34
+ - '443:443'
35
+ extra_hosts:
36
+ # Lets the optional Portainer-panel site (PORTAINER_DOMAIN) reach the host's Portainer UI on 9443.
37
+ - 'host.docker.internal:host-gateway'
38
+ environment:
39
+ - CADDY_DOMAIN=${CADDY_DOMAIN}
40
+ - ACME_EMAIL=${ACME_EMAIL:-}
41
+ - PORTAINER_DOMAIN=${PORTAINER_DOMAIN:-}
42
+ - PORTAINER_UPSTREAM=${PORTAINER_UPSTREAM:-https://host.docker.internal:9443}
43
+ # Build a Caddyfile from env at start (a string-only Portainer deploy ships no files), then run it.
44
+ # `$$VAR` is escaped from compose interpolation so the SHELL expands it at runtime from `environment:`;
45
+ # `${VAR}` in `environment:` above IS interpolated by compose from the stack env / .env.
46
+ entrypoint:
47
+ - sh
48
+ - -ceu
49
+ - |
50
+ cf=/etc/caddy/Caddyfile
51
+ : > "$$cf"
52
+ if [ -n "$${ACME_EMAIL}" ]; then
53
+ printf '{\n\temail %s\n}\n' "$${ACME_EMAIL}" >> "$$cf"
54
+ fi
55
+ printf '%s {\n\treverse_proxy agents:3000\n}\n' "$${CADDY_DOMAIN}" >> "$$cf"
56
+ if [ -n "$${PORTAINER_DOMAIN}" ]; then
57
+ printf '%s {\n\treverse_proxy %s {\n\t\ttransport http {\n\t\t\ttls_insecure_skip_verify\n\t\t}\n\t}\n}\n' "$${PORTAINER_DOMAIN}" "$${PORTAINER_UPSTREAM}" >> "$$cf"
58
+ fi
59
+ echo '----- generated Caddyfile -----'
60
+ cat "$$cf"
61
+ echo '-------------------------------'
62
+ exec caddy run --config "$$cf" --adapter caddyfile
63
+ volumes:
64
+ - 'caddy_data:/data'
65
+ - 'caddy_config:/config'
66
+
67
+ agents:
68
+ # Edition-driven: Free (default, public image) vs Pro (set AGENTS_IMAGE to the private
69
+ # Harbor image, project `agents`, after `docker login` — hub: create_registry_credential, per-user).
70
+ # PLACEHOLDER: the public Free image is not published yet — swap `agents` once it exists.
71
+ image: ${AGENTS_IMAGE:-ghcr.io/fazer-ai/agents:latest}
72
+ pull_policy: always
73
+ restart: always
74
+ # NOTE: no `command` here on purpose — the image entrypoint runs, in order: db-bootstrap.ts (idempotent
75
+ # runtime-role provisioning) → prisma migrate deploy → serve. Overriding risks drifting from that contract.
76
+ environment:
77
+ - NODE_ENV=production
78
+ - PORT=3000
79
+ - PUBLIC_URL=${PUBLIC_URL}
80
+ - CDN_URL=${PUBLIC_URL}
81
+ - CORS_ORIGIN=${CORS_ORIGIN:-*}
82
+ - LOG_LEVEL=${LOG_LEVEL:-info}
83
+ - JWT_SECRET=${JWT_SECRET}
84
+ - ENCRYPTION_KEY=${ENCRYPTION_KEY}
85
+ # Superuser/owner connection — migrations + bootstrap only. Matches the postgres container creds below.
86
+ - MIGRATION_DATABASE_URL=${MIGRATION_DATABASE_URL}
87
+ # Runtime role — NON-superuser, NON-bypassrls (db-bootstrap creates it from this URL). RLS holds.
88
+ - DATABASE_URL=${DATABASE_URL}
89
+ # Checkpointer pool — MUST be the runtime (non-superuser) role, never the migration URL.
90
+ - LANGGRAPH_DATABASE_URL=${LANGGRAPH_DATABASE_URL}
91
+ # Persist uploaded branding + generated quote PDFs on the `storage` volume (defaults live on the
92
+ # ephemeral container FS and vanish on every redeploy).
93
+ - BRANDING_STORAGE_DIR=/app/storage/branding
94
+ - QUOTES_STORAGE_DIR=/app/storage/quotes
95
+ # No host port publish: Caddy is the only ingress and reaches the app over the compose network.
96
+ expose:
97
+ - '3000'
98
+ volumes:
99
+ - 'storage:/app/storage'
100
+ healthcheck:
101
+ test: ['CMD-SHELL', 'wget -qO- http://localhost:3000/api/health || exit 1']
102
+ interval: 20s
103
+ timeout: 10s
104
+ retries: 5
105
+ depends_on:
106
+ postgres:
107
+ condition: service_healthy
108
+
109
+ postgres:
110
+ # pgvector image (NOT plain postgres): the schema needs CREATE EXTENSION vector. pg17 matches the
111
+ # validated isolation spike. Not published to the host — only the app reaches it over the network.
112
+ image: 'pgvector/pgvector:pg17'
113
+ restart: always
114
+ environment:
115
+ - POSTGRES_DB=${DATABASE_NAME:-secretaria_v4_db}
116
+ - POSTGRES_USER=${POSTGRES_USER}
117
+ - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
118
+ volumes:
119
+ - 'postgres:/var/lib/postgresql/data'
120
+ healthcheck:
121
+ test:
122
+ - CMD-SHELL
123
+ - 'pg_isready -h localhost -p 5432 -U $${POSTGRES_USER} -d $${POSTGRES_DB}'
124
+ interval: 20s
125
+ timeout: 20s
126
+ retries: 10
127
+
128
+ volumes:
129
+ storage:
130
+ postgres:
131
+ caddy_data:
132
+ caddy_config:
@@ -0,0 +1,85 @@
1
+ # Platform-neutral production stack for fazer.ai agents (Portainer / EasyPanel / plain `docker compose`
2
+ # on a VM). Coolify has its own file (docker-compose.coolify.yml, magic-vars); THIS file reads plain
3
+ # env vars from a .env you generate with `bun scripts/gen-onboarding-env.ts` (writes every secret/URL
4
+ # below, consistently). TLS + reverse proxy are EXTERNAL (Caddy/Traefik/Nginx Proxy Manager): this
5
+ # stack only publishes the app's HTTP port on the host — terminate TLS in front of it.
6
+ #
7
+ # TWO database roles by design (docs/deploy.md, load-bearing): the SUPERUSER (POSTGRES_USER, the
8
+ # postgres container's owner) runs db-bootstrap + migrations; the NON-superuser runtime role (encoded
9
+ # in DATABASE_URL, created by db-bootstrap from that URL) is what the server runs as, so Postgres RLS
10
+ # isolation holds. The boot guard hard-crashes if the runtime role is privileged.
11
+ #
12
+ # SINGLE replica by construction (the scheduler / debounce / webhook / alert workers + realtime
13
+ # pub/sub are single-process). Do NOT scale `agents` to >1 here. To add web replicas, run the
14
+ # extras with SCHEDULER_WORKER_ENABLED/WEBHOOK_WORKER_ENABLED/DEBOUNCE_WORKER_ENABLED/
15
+ # ALERT_WORKER_ENABLED=false and keep exactly one leader with them on (docs/deploy.md §4).
16
+
17
+ services:
18
+ agents:
19
+ # Edition-driven: Free (default, public image) vs Pro (set AGENTS_IMAGE to the private
20
+ # Harbor image, project `agents`, after `docker login` — hub: create_registry_credential, per-user).
21
+ # PLACEHOLDER: the public Free image is not published yet — swap `agents` once it exists.
22
+ image: ${AGENTS_IMAGE:-ghcr.io/fazer-ai/agents:latest}
23
+ pull_policy: always
24
+ restart: always
25
+ # NOTE: no `command` here on purpose — the image's default entrypoint already runs, in order:
26
+ # db-bootstrap.ts (idempotent runtime-role provisioning) → prisma migrate deploy → serve
27
+ # interpreted (bun src/index.ts). Overriding risks drifting from that contract.
28
+ environment:
29
+ - NODE_ENV=production
30
+ - PORT=3000
31
+ - PUBLIC_URL=${PUBLIC_URL}
32
+ - CDN_URL=${PUBLIC_URL}
33
+ - CORS_ORIGIN=${CORS_ORIGIN:-*}
34
+ - LOG_LEVEL=${LOG_LEVEL:-info}
35
+ - JWT_SECRET=${JWT_SECRET}
36
+ - ENCRYPTION_KEY=${ENCRYPTION_KEY}
37
+ # Superuser/owner connection — migrations + bootstrap only. Its user/password MUST match the
38
+ # postgres container's POSTGRES_USER/POSTGRES_PASSWORD below.
39
+ - MIGRATION_DATABASE_URL=${MIGRATION_DATABASE_URL}
40
+ # Runtime role — NON-superuser, NON-bypassrls (db-bootstrap creates it from this URL). RLS holds.
41
+ - DATABASE_URL=${DATABASE_URL}
42
+ # Checkpointer pool — MUST be the runtime (non-superuser) role, never the migration URL.
43
+ - LANGGRAPH_DATABASE_URL=${LANGGRAPH_DATABASE_URL}
44
+ # Persist uploaded branding (logo/favicon) + generated quote PDFs on the `storage` volume
45
+ # (mounted below). The defaults (./data/*) live on the ephemeral container FS and vanish on
46
+ # every redeploy.
47
+ - BRANDING_STORAGE_DIR=/app/storage/branding
48
+ - QUOTES_STORAGE_DIR=/app/storage/quotes
49
+ ports:
50
+ # Host:container. Put a TLS-terminating reverse proxy in front; in untrusted networks bind to
51
+ # 127.0.0.1 (set APP_PORT=127.0.0.1:3000) or firewall the port.
52
+ - '${APP_PORT:-3000}:3000'
53
+ volumes:
54
+ - 'storage:/app/storage'
55
+ healthcheck:
56
+ test: ['CMD-SHELL', 'wget -qO- http://localhost:3000/api/health || exit 1']
57
+ interval: 20s
58
+ timeout: 10s
59
+ retries: 5
60
+ depends_on:
61
+ postgres:
62
+ condition: service_healthy
63
+
64
+ postgres:
65
+ # pgvector image (NOT plain postgres): the schema needs CREATE EXTENSION vector. pg17 matches the
66
+ # validated isolation spike. Not published to the host — only the app reaches it over the network.
67
+ image: 'pgvector/pgvector:pg17'
68
+ restart: always
69
+ environment:
70
+ - POSTGRES_DB=${DATABASE_NAME:-secretaria_v4_db}
71
+ - POSTGRES_USER=${POSTGRES_USER}
72
+ - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
73
+ volumes:
74
+ - 'postgres:/var/lib/postgresql/data'
75
+ healthcheck:
76
+ test:
77
+ - CMD-SHELL
78
+ - 'pg_isready -h localhost -p 5432 -U $${POSTGRES_USER} -d $${POSTGRES_DB}'
79
+ interval: 20s
80
+ timeout: 20s
81
+ retries: 10
82
+
83
+ volumes:
84
+ storage:
85
+ postgres:
@@ -0,0 +1,59 @@
1
+ # Langfuse v3 self-hosted: env for the GENERIC flavor (docker-compose.yml).
2
+ # Coolify users do NOT need this file (magic vars are auto-generated); see docker-compose.coolify.yml.
3
+ #
4
+ # cp .env.example .env && fill every CHANGE_ME && docker compose up -d
5
+ #
6
+ # Generate strong secrets, e.g.:
7
+ # openssl rand -base64 32 # SALT, NEXTAUTH_SECRET, passwords
8
+ # openssl rand -hex 32 # ENCRYPTION_KEY (MUST be 64 hex chars / 256-bit)
9
+ # openssl rand -hex 16 # MinIO user/password are arbitrary, just keep them secret
10
+
11
+ # ── public URL (where the browser/SDK reach Langfuse; include scheme) ──
12
+ LANGFUSE_PUBLIC_URL=https://langfuse.example.com
13
+ LANGFUSE_PORT=3000
14
+
15
+ # ── app secrets ──
16
+ LANGFUSE_SALT=CHANGE_ME_base64_32
17
+ LANGFUSE_ENCRYPTION_KEY=CHANGE_ME_hex_64 # exactly 64 hex chars
18
+ LANGFUSE_NEXTAUTH_SECRET=CHANGE_ME_base64_32
19
+
20
+ # ── postgres ──
21
+ POSTGRES_DB=langfuse-db
22
+ POSTGRES_USER=langfuse
23
+ POSTGRES_PASSWORD=CHANGE_ME
24
+
25
+ # ── clickhouse ──
26
+ CLICKHOUSE_DB=default
27
+ CLICKHOUSE_USER=clickhouse
28
+ CLICKHOUSE_PASSWORD=CHANGE_ME
29
+
30
+ # ── redis ──
31
+ REDIS_PASSWORD=CHANGE_ME
32
+
33
+ # ── MinIO (blob storage: MANDATORY; the compose points all S3 families here) ──
34
+ MINIO_ROOT_USER=langfuse-minio
35
+ MINIO_ROOT_PASSWORD=CHANGE_ME
36
+
37
+ # ── optional: SMTP for invitations / password resets (blank = disabled) ──
38
+ LANGFUSE_EMAIL_FROM=admin@example.com
39
+ LANGFUSE_SMTP_URL=
40
+
41
+ # ── headless seed (agent-driven; see references/05-langfuse.md), Langfuse's recommended pattern ──
42
+ # The agent provisions EVERYTHING on the first boot: a user (org OWNER) + org + project + key pair, with
43
+ # AUTH_DISABLE_SIGNUP=true from the start, so signup is never open (Langfuse has no "first admin then
44
+ # close" gate; open signup would let anyone register on the exposed instance). The operator SIGNS IN at
45
+ # /auth/sign-in with the seeded email + password (and changes it); the agent already holds the keys and
46
+ # wires them into fazer.ai agents. One deploy, no open signup window.
47
+ # The USER requires the ORG (seed them together); Langfuse upserts by id, so a redeploy does not duplicate.
48
+ # Password: include a non-alphanumeric char (Langfuse's policy for signup/change; seed + login accept
49
+ # without, but be safe). Leave every LANGFUSE_INIT_* blank for a plain deploy with no seed.
50
+ AUTH_DISABLE_SIGNUP=true
51
+ LANGFUSE_INIT_USER_EMAIL=
52
+ LANGFUSE_INIT_USER_NAME=
53
+ LANGFUSE_INIT_USER_PASSWORD=
54
+ LANGFUSE_INIT_ORG_ID=
55
+ LANGFUSE_INIT_ORG_NAME=
56
+ LANGFUSE_INIT_PROJECT_ID=
57
+ LANGFUSE_INIT_PROJECT_NAME=
58
+ LANGFUSE_INIT_PROJECT_PUBLIC_KEY=
59
+ LANGFUSE_INIT_PROJECT_SECRET_KEY=
@@ -0,0 +1,132 @@
1
+ # Langfuse (self-hosted) for fazer.ai
2
+
3
+ Optional companion service for **agent tracing**. Langfuse is wired into fazer.ai agents **per-tenant
4
+ via a `langfuse` vault credential** (never a global env var); see `docs/deploy.md`
5
+ and `docs/graph.md`. This folder vendors a deploy that actually works, in the
6
+ project's three flavors (Coolify primary; Portainer and a generic "Outros" path, per
7
+ `docs/deploy.md`).
8
+
9
+ ## Why this exists (the bug the one-click hides)
10
+
11
+ Langfuse v3 **requires S3-compatible blob storage for ingestion**: on every
12
+ `POST /api/public/ingestion`, the web container uploads the raw event JSON to S3 **before** queueing
13
+ it to ClickHouse. The official Langfuse one-click / Coolify template declares the `LANGFUSE_S3_*`
14
+ variable **names** but ships them **empty** and bundles **no** object-storage service.
15
+
16
+ Result, observed in production-like testing:
17
+
18
+ - `GET /api/public/projects` reads **Postgres only** → returns `200`. A naive "test connection"
19
+ (which is exactly what the Langfuse credential test does) **passes**, so the instance looks healthy.
20
+ - `POST /api/public/ingestion` tries to upload to S3 with empty creds → the AWS SDK throws
21
+ `Could not load credentials from any providers` → `Failed to upload events to blob storage,
22
+ aborting event processing` → **HTTP 500** (plain text, not JSON).
23
+ - The Langfuse client SDK then chokes parsing the non-JSON 500 body, and in its background flush
24
+ path the error is swallowed. Net effect: **traces silently never arrive**, with no client-side
25
+ error and nothing in the producing app's logs.
26
+
27
+ This is **not** a bug in fazer.ai agents (the trace handler builds, enqueues, and POSTs correctly) and
28
+ **not** a flush/runtime issue: it is a missing-storage deploy gap. These compose files bundle
29
+ **MinIO** and wire all three S3 families (`EVENT_UPLOAD`, `MEDIA_UPLOAD`, `BATCH_EXPORT`) to it.
30
+
31
+ ## Files
32
+
33
+ | File | Use |
34
+ | --- | --- |
35
+ | `docker-compose.coolify.yml` | **Coolify** (primary). Uses Coolify magic vars (`SERVICE_*`); secrets auto-generated. |
36
+ | `docker-compose.yml` | **Generic**: Portainer / EasyPanel / Dokploy / plain Docker. Secrets from `.env`. |
37
+ | `.env.example` | Template for the generic flavor. `cp .env.example .env`, fill every `CHANGE_ME`. |
38
+
39
+ Both composes are identical in topology, `langfuse` (web) + `langfuse-worker` + `postgres` +
40
+ `redis` + `clickhouse` + **`minio`**, and differ only in how secrets are supplied.
41
+
42
+ ## Deploy
43
+
44
+ ### Coolify (primary)
45
+
46
+ 1. New Resource → **Docker Compose** → paste `docker-compose.coolify.yml`.
47
+ 2. Set the service **Domain** to your Langfuse FQDN (Coolify fills `SERVICE_FQDN_LANGFUSE_3000` /
48
+ `SERVICE_URL_LANGFUSE`). Coolify generates `SERVICE_USER_MINIO`, `SERVICE_PASSWORD_MINIO`, and all
49
+ other `SERVICE_*` secrets automatically; **do not** set them by hand.
50
+ 3. Deploy. Confirm `minio` becomes healthy, then verify ingestion (below).
51
+
52
+ > The blob-storage creds are the same Coolify magic vars used by the `minio` service, so the web,
53
+ > the worker, and MinIO always agree without you copying a secret around. That is the point of using
54
+ > magic vars here instead of hardcoded `LANGFUSE_S3_*` values.
55
+
56
+ ### Portainer / EasyPanel / Dokploy / plain Docker (generic)
57
+
58
+ ```sh
59
+ cp .env.example .env # fill every CHANGE_ME (see the openssl hints in the file)
60
+ docker compose up -d
61
+ ```
62
+
63
+ Portainer: paste `docker-compose.yml` as a Stack and provide the same variables via the Stack env
64
+ editor instead of a `.env` file. Put your own TLS-terminating reverse proxy in front of port 3000.
65
+
66
+ ### Magic var ↔ generic env mapping
67
+
68
+ | Coolify magic var | Generic `.env` | Purpose |
69
+ | --- | --- | --- |
70
+ | `SERVICE_USER_MINIO` / `SERVICE_PASSWORD_MINIO` | `MINIO_ROOT_USER` / `MINIO_ROOT_PASSWORD` | MinIO creds = the S3 access key/secret |
71
+ | `SERVICE_USER_POSTGRES` / `SERVICE_PASSWORD_POSTGRES` | `POSTGRES_USER` / `POSTGRES_PASSWORD` | Postgres |
72
+ | `SERVICE_USER_CLICKHOUSE` / `SERVICE_PASSWORD_CLICKHOUSE` | `CLICKHOUSE_USER` / `CLICKHOUSE_PASSWORD` | ClickHouse |
73
+ | `SERVICE_PASSWORD_REDIS` | `REDIS_PASSWORD` | Redis |
74
+ | `SERVICE_PASSWORD_SALT` | `LANGFUSE_SALT` | Langfuse salt |
75
+ | `SERVICE_PASSWORD_64_LANGFUSE` | `LANGFUSE_ENCRYPTION_KEY` | 256-bit encryption key (64 hex) |
76
+ | `SERVICE_BASE64_NEXTAUTHSECRET` | `LANGFUSE_NEXTAUTH_SECRET` | NextAuth secret |
77
+ | `SERVICE_URL_LANGFUSE` | `LANGFUSE_PUBLIC_URL` | Public URL |
78
+
79
+ ## Seed everything headless (agent-driven, one deploy)
80
+
81
+ The agent does **not** ask the user to copy keys out of the UI, and does **not** open signup. It
82
+ provisions everything on the first boot (Langfuse's own recommended headless-init pattern):
83
+
84
+ 1. The agent generates a `pk-lf-…` / `sk-lf-…` key pair, an org id + name, a project id + name, and a
85
+ strong temporary password (include a non-alphanumeric char). The user email is the operator's, reused
86
+ from the Chatwoot admin so they have one login across tools.
87
+ 2. The agent sets, on the service env (Coolify) or `.env` (generic), and deploys **once** with
88
+ `AUTH_DISABLE_SIGNUP=true` (the template default):
89
+ - `LANGFUSE_INIT_USER_EMAIL` / `_NAME` / `_PASSWORD`
90
+ - `LANGFUSE_INIT_ORG_ID` / `_NAME`
91
+ - `LANGFUSE_INIT_PROJECT_ID` / `_NAME` / `_PUBLIC_KEY` / `_SECRET_KEY`
92
+
93
+ On boot Langfuse creates the **user (org OWNER) + org + project + keys**. The user requires the org
94
+ (seed them together); it upserts by id, so a redeploy never duplicates.
95
+ 3. The operator **signs in** at `/auth/sign-in` (never signs up) with the seeded email + the generated
96
+ password (the agent shows it) and changes it on first login. The seed is create-if-not-exists
97
+ (verified: the password change survives redeploys), so the operator's real password never passes
98
+ through the agent. The agent already holds the keys and wires them into fazer.ai agents (below).
99
+
100
+ Empirically validated with this template's compose: the `LANGFUSE_INIT_USER` becomes org `OWNER`, the
101
+ seeded user signs in (session shows `role: OWNER`), the seeded keys authenticate ingestion (`207`), and
102
+ signup returns `422 Sign up is disabled` from the start, with no open-signup window.
103
+
104
+ ## Verify ingestion actually works
105
+
106
+ A green health check is **not** enough (it only proves Postgres). Confirm a trace round-trips:
107
+
108
+ ```sh
109
+ # 1) use the seeded pk-lf-…/sk-lf-… pair (or create one in the UI), then:
110
+ PK=pk-lf-...; SK=sk-lf-...; BASE=https://langfuse.example.com
111
+
112
+ # 2) POST a trace event, expect HTTP 207/200 (NOT 500):
113
+ curl -s -u "$PK:$SK" -H 'Content-Type: application/json' -X POST "$BASE/api/public/ingestion" \
114
+ -d '{"batch":[{"id":"t1","type":"trace-create","timestamp":"2026-01-01T00:00:00.000Z","body":{"id":"verify-1","name":"verify"}}]}' \
115
+ -w '\n[http %{http_code}]\n'
116
+
117
+ # 3) read it back (give the worker a few seconds to flush to ClickHouse):
118
+ curl -s -u "$PK:$SK" "$BASE/api/public/traces?limit=5"
119
+ ```
120
+
121
+ A `500` here means blob storage is still misconfigured: check the `langfuse` (web) container logs
122
+ for `Failed to upload events to blob storage`.
123
+
124
+ ## Wire into fazer.ai agents
125
+
126
+ Tracing is per-tenant. The agent uses the MCP **`langfuse_connect`** tool: it takes the public key +
127
+ secret key + base URL inline, creates the `langfuse` vault credential, and turns tracing on in one call
128
+ (`docs/mcp.md`). The equivalent by hand is a `langfuse` vault credential (`POST /v1/vault` with
129
+ `kind:"langfuse"`, `value:{publicKey, secretKey}`, `baseUrl:"https://langfuse.example.com"`) then
130
+ `PUT /v1/tenant-settings/langfuse {enabled:true, credentialRef:"vault:<id>"}`. The connection test on
131
+ the credential checks reachability + auth (Postgres path); the **ingestion** verification above is what
132
+ proves traces will land. See `references/05-langfuse.md`.