Synaplan
开源 AI 软件平台,面向企业:自托管 AI 代理,符合 GDPR 的聊天机器人小部件,RAG 知识库,WhatsApp 与 Outlook AI,MCP,多模型路由。支持 Docker、Kubernetes 或在 Germ 托管。
AI 翻译
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Automated AI-powered contract analysis. Identify legal and business risks before you sign.
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗
Automated AI-powered contract analysis. Identify legal and business risks before you sign.
ContractGuardian was born while testing Tanyra — a B2B meeting intelligence platform. During analysis of real contract negotiation meetings, a recurring gap emerged: contracts were being discussed without any structured risk review.
ContractGuardian is the validated prototype that solved that problem. It's open source because validating problems publicly is more credible than claiming solutions privately.
Home — Upload your contract
Step 1 — AI extracts contract metadata automatically
Step 2 — Validate metadata before running the full analysis
Step 3 — Structured report with findings per actor, severity classification and legal norm citations
| Layer | Technology |
|---|---|
| Framework | Next.js 15 App Router |
| Language | TypeScript (strict) |
| Styling | TailwindCSS v4 |
| ORM | Drizzle ORM |
| Database | Supabase (PostgreSQL) |
| AI | OpenAI GPT-4o-mini |
| PDF generation | pdf-lib |
| OCR | Tesseract.js |
| i18n | next-intl |
| Deploy | Railway |
ContractGuardian is built with Supabase and Railway as reference providers, but is not locked to them.
DATABASE_URL accordingly.The only hard dependency is a PostgreSQL database and an OpenAI API key.
# 1. Clone the repository
git clone https://github.com/Tigierre/contractguardian.git
cd contractguardian
# 2. Copy the environment template
cp .env.example .env.local
# 3. Fill in the required variables (see .env.example for guidance)
# - DATABASE_URL → PostgreSQL connection string
# - OPENAI_API_KEY → from platform.openai.com
# 4. Install dependencies
npm install
# 5. Push the database schema
npm run db:push
# 6. Start the dev server
npm run dev
Open http://localhost:3000.
| Variable | Required | Description |
|---|---|---|
DATABASE_URL | Yes (runtime) | PostgreSQL connection string (any compatible provider) |
OPENAI_API_KEY | Yes (runtime) | OpenAI API key |
OPENAI_MAX_COMPLETION_TOKENS_ANALYSIS | No | Output-token cap per analysis call, reasoning included (default 16000) |
OPENAI_MAX_COMPLETION_TOKENS_PREANALYSIS | No | Output-token cap per pre-analysis call (default 6000) |
CG_ADMIN_GROUP | No | Group(s) granting admin rights (trash restore/purge), comma-separated |
AUTHENTIK_JWKS_URL | If CG_ADMIN_GROUP is set | JWKS used to verify the X-authentik-jwt token. Without it nobody is admin |
AUTHENTIK_JWT_ISSUER / AUTHENTIK_JWT_AUDIENCE | No | When set, the JWT must carry a matching iss / aud |
NEXT_PUBLIC_APP_URL | No | Base URL for the app (default: http://localhost:3000) |
NEXT_PUBLIC_SUPABASE_URL | No | Supabase project URL (only if using Supabase Auth — not used in v1) |
NEXT_PUBLIC_SUPABASE_ANON_KEY | No | Supabase anon key (only if using Supabase Auth — not used in v1) |
BRAND_THEME | No | One of the bundled themes (see Theming). Empty = the default look |
BRAND_MODE | No | chiaro (light) or scuro (dark), only for themes that ship both |
BRAND_ACCENT | No | Brand colour for primary actions, #rrggbb |
.env.example documents every variable, including performance tuning and trash retention.
Neither DATABASE_URL nor OPENAI_API_KEY is needed at build time: both clients are created on first use.
The look of an instance is chosen with environment variables read at runtime, with no code changes and no rebuild:
BRAND_THEME=acciaio # nova · aurora · neon · sabbia · foresta · officina · acciaio
BRAND_ACCENT=#0f766e # optional: your brand colour on primary actions
[tema] …) and ignored.lib/temi/ (temi.css: one block of colour tokens per theme;
temi.ts: contrast helpers and voltoDaEnv()). They are shared files: update them by
copying newer versions over, not by editing them here.lib/tema-di-casa.css maps the Tailwind palette used by the
components (white, slate, red/rose, amber, green/emerald, blue/sky) onto
the theme tokens (surface, border, text, danger, warning, success, info). The dark:
variant is switched off and the light/dark toggle is not shown: the theme (and BRAND_MODE
for themes that have both modes) decides the mode.BRAND_ACCENT replaces --primary; the text on top of it (--on-primary) and its hover
are computed for contrast, so a light brand colour gets dark text. It also works without
a theme.acciaio → Roboto, officina → Barlow). Fonts come from npm
packages (@fontsource/*) and are served by the app itself, never from a CDN; the browser
downloads them only when the active theme uses them. Roboto is bundled; a theme whose font
package is not installed falls back to the system font.force-dynamic in app/[locale]/layout.tsx) so that the
variables are read when the container runs, not when the image is built.ContractGuardian has no login of its own. It is meant to run behind a reverse proxy
with forward-auth to Authentik (e.g. Traefik + an Authentik
outpost), which forwards the user identity in X-authentik-* headers. Two deployment
requirements follow:
X-authentik-* header sent by the client;In production, API requests without an identity are rejected with 403; every user
only sees their own contracts and analyses. Admin rights are never derived from the
plain-text groups header: they come from the signed JWT, verified against
AUTHENTIK_JWKS_URL.
ContractGuardian is optimised for Railway with a standalone Next.js build.
The railway.json and railpack.json configuration files are already included in the repository.
Steps:
For production, use the Session pooling Supabase connection string (port 5432) in DATABASE_URL.
You can build and run ContractGuardian as a Docker container on any platform.
docker build --build-arg SOURCE_COMMIT=$(git rev-parse HEAD) -t contractguardian .
SOURCE_COMMIT is baked into the image and reported by GET /api/health as
versione (short SHA), so you can always tell which code a container runs. Without
it the probe reports "versione": "sconosciuta".
docker run -p 3000:3000 \
-e DATABASE_URL="postgresql://user:password@host:5432/postgres" \
-e OPENAI_API_KEY="sk-proj-xxx" \
contractguardian
Or pass all variables from a file:
docker run -p 3000:3000 --env-file .env.local contractguardian
The container runs as a non-root user (nextjs), listens on port 3000 by default, and reads PORT from the environment if you need a different port.
GET /api/health answers 200 with {"status":"healthy","versione":"<sha>","database":"connected"},
or 503 when the database does not answer within 3 seconds. The image declares a
Docker HEALTHCHECK that calls it from inside the container (Node's built-in fetch,
no extra packages), so orchestrators see a container with an unreachable database as
unhealthy.
The container entrypoint applies any pending Drizzle migrations from
./db/migrations before starting the server. This means you do not need
to run npm run db:migrate manually after a deploy — point the container at
an empty (or up-to-date) PostgreSQL database and it converges. Applied
migrations are tracked in the __drizzle_migrations table, so re-runs are
idempotent.
If you prefer to manage migrations out of band, leave DATABASE_URL set but
remove the ./db/migrations folder from the image; the entrypoint will skip
the step.
The current implementation is built on Supabase (PostgreSQL) and deployed to Railway. The Dockerfile and standalone build are provider-agnostic — they work anywhere Docker runs.
It has been tested in production on:
Migrating to a different infrastructure (e.g. Azure, AWS, GCP) requires adapting the database connection string and, potentially, the storage/auth configuration. The codebase uses standard PostgreSQL via Drizzle ORM, so switching database providers is straightforward as long as the target is PostgreSQL-compatible.
Contributions to improve portability are welcome — open an issue or PR.
Two issues you may hit when building the Docker image on your own infrastructure (anything not Railway). Both are fixed in the current Dockerfile — documented here so future contributors don't re-introduce them.
pdf-parse and Next.js standalone tracing[email protected] ships a debug autotest at the top of its index.js that
reads ./test/data/05-versions-space.pdf the first time the module is
required when module.parent is falsy. Next.js' standalone output traces
required modules but does not copy the test fixture, so at runtime the
package crashes with:
Error: ENOENT: no such file or directory, open './test/data/05-versions-space.pdf'
The crash bubbles up as an HTML error page from /api/upload, and the
client sees Unexpected token '<', "<!DOCTYPE "... is not valid JSON.
Fix: import the internal file directly, bypassing the autotest:
// lib/pdf/extractor.ts
const pdfParse = require('pdf-parse/lib/pdf-parse.js'); // ✓
// const pdfParse = require('pdf-parse'); // ✗ crashes
next start (or the standalone server.js) does not touch your database
schema. On a fresh deploy this leaves the contracts table missing and the
API throws relation "contracts" does not exist.
Fix: the Dockerfile uses a docker-entrypoint.sh that runs
scripts/migrate.mjs (a small standalone script using
drizzle-orm/postgres-js/migrator) before exec node server.js. The
migrations folder and the script are copied into the runner stage
explicitly — the standalone tracer does not include .sql files on its own.
The script also copies drizzle-orm and postgres from the deps stage:
Next.js standalone bundles them into server.js but does not preserve
them as resolvable packages in node_modules, so a plain Node.js script
cannot import them otherwise.
A Prisma-style db/migrations/*.sql exclusion in .gitignore is a
foot-gun with Drizzle. Drizzle's meta/_journal.json references each
.sql file by name and applies them at runtime; without the files in
the repository, every fresh container starts with an empty database and
never converges. Keep the SQL files committed — they are not "generated
artifacts" in the Prisma sense, they are the authoritative description
of the schema.
reasoning_effort values changed for GPT-5.xOlder GPT-5 nano accepted reasoning_effort: 'minimal'. The current
gpt-5.4-nano (the default for pre-analysis) only accepts
'none' | 'low' | 'medium' | 'high' | 'xhigh', and returns a 400
invalid_request_error for 'minimal'. ContractGuardian uses 'low'
for pre-analysis (a light triage) and 'medium' for the deep analysis
pass.
If you pin a different model via OPENAI_MODEL_PREANALYSIS /
OPENAI_MODEL_ANALYSIS, verify which reasoning_effort values it
accepts — OpenAI changes the supported scale between model generations.
Contributions and ideas welcome — open an issue to discuss.
See CONTRIBUTING.md.
MIT — see LICENSE.
Built by Francesco Rossberger · [email protected] | Spawned from Tanyra validation — tanyra.com