{
  "markdown": "# gofact\n\n> 🇫🇷 [Version française](README-FR.md)\n\nSmall Go binary that turns a **print-ready HTML invoice** into\n**Factur-X**: a compliant PDF/A-3 with the CII EN 16931 XML embedded, and\noptionally submits it to a French e-invoicing platform (PDP —\n*plateforme de dématérialisation partenaire*).\n\nBuilt for the invoicing of a French freelancer or small business (the French\ne-invoicing reform), and also shipped as a **Claude Code plugin** with the\n`creer-facture` skill.\n\n## Pipeline\n\n```\nJSON  ──EN 16931 rules──▶  rejected if non-compliant (before producing anything)\n      ──gofact──────────▶  factur-x.xml (CII EN 16931)\nHTML  ──headless Chrome─▶  PDF          (identical to Ctrl+P output, backgrounds included)\nPDF + XML ──pure Go─────▶  Factur-X     (PDF/A-3 + VERBATIM embedding of the XML)\nFactur-X  ──self-check──▶  re-read      (structure + integrity of the embedded XML)\n```\n\n> **Verbatim embedding — important.** The XML is embedded **byte for byte**.\n> An assembler that re-serializes it through its own model **loses extended\n> fields**, in particular the PDP electronic routing addresses (BT-34/BT-49).\n> gofact never touches the XML it has produced.\n\n## System dependencies\n\n**A browser, and that's it.**\n\n- **Chrome, Edge, Brave or Chromium** — HTML → PDF rendering\n\nAuto-detected on Linux, macOS and Windows (where **Edge** is preinstalled, so\nnothing to do). Sandboxed packages — snap, flatpak — are skipped: they cannot\nread the temporary files gofact hands them. To point at a specific executable:\n`GOFACT_CHROME` or `-chrome`.\n\nThe PDF/A-3 assembly is done in Go (pdfcpu): no Ghostscript, no Java, no\nfirst-run download. The sRGB ICC profile is embedded in the binary.\n\n## Compliance\n\nTwo checks, at two different moments:\n\n- **EN 16931 business rules**, applied on the model **before** producing\n  anything. gofact refuses to issue an invoice it knows to be non-compliant,\n  reporting the rule identifier (`BR-50`, `BR-CO-15`…) and the field to fix.\n- **PDF self-check**, after writing: the file is re-read, the Factur-X\n  structures are verified (`/AF`, `/OutputIntents`, XMP, `/EmbeddedFiles`) and\n  the embedded XML is compared byte for byte with the one that was generated.\n\nDeep PDF/A-3b compliance is verified by **veraPDF**, via Mustang, in\ncontinuous integration — never on the user's machine:\n\n```sh\nGOFACT_ORACLE_HTML=facture.html go test -tags=ci ./internal/facturx\n```\n\n## Build\n\n```sh\ngo build -o gofact .\ngo test ./...\n```\n\n## Configuration\n\n**No identity and no secret is hard-coded.** The invoice issuer, the payment\nIBAN and the PDP credentials come from the environment.\n\n```sh\ncp .env.example .env    # then fill in your own values\n```\n\n`gofact` loads, in order and without ever overwriting an already-defined\nvariable: the file passed to `-env`, otherwise `./.env`, then\n`$XDG_CONFIG_HOME/gofact/.env` (`~/.config/gofact/.env` by default). The `.env`\nis git-ignored — never commit it.\n\n| Variable | Purpose |\n| --- | --- |\n| `GOFACT_SELLER_NAME` | Seller name. **Required** unless the JSON carries a `seller` block. |\n| `GOFACT_SELLER_SIRET` / `GOFACT_SELLER_SIREN` | French legal identifier (BT-30). The SIREN is derived from the SIRET. |\n| `GOFACT_SELLER_VAT_NUMBER` | Intra-community VAT number (BT-31). Empty under the 293 B franchise ⇒ BT-32 = SIREN. |\n| `GOFACT_SELLER_EMAIL` | Seller e-mail. |\n| `GOFACT_SELLER_ADDRESS` / `_POSTAL_CODE` / `_CITY` / `_COUNTRY` | Postal address (country defaults to `FR`). |\n| `GOFACT_SELLER_ELECTRONIC_ADDRESS` / `_SCHEME` | Peppol routing BT-34. Empty ⇒ SIREN + scheme `0225`. |\n| `GOFACT_PAYEE_IBAN` | Payment IBAN (BT-84). Empty ⇒ omitted from the invoice. |\n| `GOFACT_VAT_EXEMPTION_MENTION` / `_CODE` | Default exemption mention and code (293 B). |\n| `SUPERPDP_CLIENT_ID` / `SUPERPDP_CLIENT_SECRET` / `SUPERPDP_BASE` | PDP credentials (**secrets**), for `-send` only. |\n\nWith no seller configured, `gofact` **fails explicitly** rather than issuing an\nincomplete invoice. Every value can still be overridden per invoice via the JSON.\n\n## MCP server — talk to your AI to invoice\n\n`gofact mcp` exposes invoicing as a **local MCP server (stdio)**: from Claude\nDesktop, Claude Code, LM Studio or any MCP client, saying \"make me an invoice\nfor ACME, 2 days at €600\" is enough. The AI is the interface; gofact\nguarantees the numbering (continuous legal sequence, locked and transactional\nallocation), EN 16931 compliance and archiving — locally.\n\n```sh\n# Register in Claude Code, for example:\nclaude mcp add gofact -- /path/to/gofact mcp\n```\n\nTools: `list_organizations`, `get_organization`, `init_organization`,\n`update_organization`,\n`search_client`, `get_invoice_template`, `preview_next_number`, `list_invoices`,\n`create_invoice`, `send_invoice` (the only destructive tool — PDP submission,\nexplicit confirmation required), `get_invoice_status`. Prompt: `nouvelle-facture`.\n\nAn **organization** (issuing entity) = a self-contained directory: identity in\nits `.env`, `numerotation.json` registry, `journal.ndjson` audit log, invoice\ntemplate frozen at first issuance, and the invoices themselves. CLI\nmanagement: `gofact org list | init | show`. No secret ever leaves a tool\nresponse.\n\n## Usage (CLI)\n\n```sh\n# Simplest form: -data and -out derived from the HTML file name\ngofact -html \"2026011 - Client.html\"\n#   → reads \"2026011 - Client.json\", writes \"2026011 - Client.pdf\"\n\n# Explicit paths + XML dump for debugging\ngofact -html f.html -data f.json -out f.pdf -xml f.xml\n\n# Options\n-env <path>       # explicit .env file\n-validate=false   # skip the self-check of the produced PDF\n-chrome <path>    # force the Chrome executable\n-q                # quiet (errors only)\n```\n\nExit code `0` if the Factur-X is generated (and compliant when `-validate`).\n\n## Sending to a PDP (SuperPDP)\n\n`gofact` submits the Factur-X PDF to SuperPDP (OAuth2 client credentials).\n\n```sh\n# generate then submit in one command\ngofact -html f.html -send -poll\n# submit an already-generated PDF\ngofact send -pdf f.pdf -poll\n```\n\n> **The PDP requires that the invoice seller = the company of the authenticated\n> account** (legal identifier BT-30) and an **electronic routing address**\n> (BT-34/BT-49, scheme `0225`) for both the issuer and the recipient. See\n> `electronic_address` below. `-poll` shows the lifecycle (`fr:200` submitted →\n> `fr:201` issued → `fr:202` received).\n\n## JSON format (sidecar)\n\nAll amounts are in **cents** (integers). The seller, the IBAN, the VAT regime\nand the French legal mentions have **default values** (environment) — the JSON\nonly carries what varies.\n\n```json\n{\n  \"number\": \"2026011\",\n  \"type\": \"invoice\",\n  \"issue_date\": \"2026-06-29\",\n  \"buyer_reference\": \"D2026004\",\n  \"buyer\": {\n    \"name\": \"ACME SAS\",\n    \"siret\": \"55208131700015\",\n    \"email\": \"compta@acme.example\",\n    \"address\": \"1 rue de la Paix\",\n    \"postal_code\": \"75002\",\n    \"city\": \"Paris\",\n    \"country_code\": \"FR\"\n  },\n  \"lines\": [\n    { \"name\": \"Développement\", \"unit\": \"day\", \"quantity\": \"2.00\",\n      \"unit_price_ht_cents\": 60000, \"amount_ht_cents\": 120000 }\n  ]\n}\n```\n\nOptional fields: `due_date` (defaults to \"on receipt\" = issue date),\n`delivery_date` (defaults to issue date), `currency` (defaults to EUR), `vat`\n(defaults to 293 B exemption; set `{\"exempt\": false, \"rate_pct\": \"20.00\"}` for\nstandard VAT), `seller`, `iban`, `title` (the invoice subject, recorded in the\nregistry), `notes`.\n\n**Notes**: the three French legal mentions — late-payment penalties (`PMD`),\nrecovery fee (`PMT`), discount (`AAB`) — are always emitted (BR-FR-05). `notes`\n**adds** to them: a note without `subject_code` goes out as general information\n(`AAI`); a note carrying `PMD`, `PMT` or `AAB` replaces the default mention with\nthat code. No spec can drop them: generation refuses.\n\n**PDP routing**: `seller`/`buyer` accept `electronic_address` (BT-34/BT-49) and\n`electronic_address_scheme` (e.g. `\"0225\"`). A PDP routes on these addresses,\nnot on the postal address. Without a declared address, each party is routed on\nits **SIREN** (derived from the SIRET) with scheme `0225`; the e-mail (`EM`) is\nonly used when no legal identifier is known — it is not in the directory, and\n`send_invoice` then refuses the deposit. Example (seller override):\n\n```json\n\"seller\": {\n  \"name\": \"Studio Exemple\", \"siren\": \"123456789\",\n  \"electronic_address\": \"123456789\", \"electronic_address_scheme\": \"0225\"\n}\n```\n\n## Installation\n\n**Precompiled binary** (GitHub release, no toolchain required):\n\n```sh\ncurl -fsSL https://raw.githubusercontent.com/kolapsis/gofact/main/install.sh | sh\ngofact install -yes    # registers the MCP server in the detected clients\n```\n\n`gofact install` detects Claude Desktop, Claude Code, LM Studio and Cursor,\nshows what it intends to write (dry-run by default), backs up every file before\nmodifying it and never overwrites a diverging entry without `-force`. Windows:\n`install.ps1`.\n\n**Package managers** — the cask and the manifest live in this repository, hence\nthe explicit URLs (Homebrew's short `brew tap` form requires a `homebrew-*`\nrepository name; Homebrew core and the main Scoop bucket both gate on a\npopularity threshold gofact has not reached):\n\n```sh\nbrew tap kOlapsis/gofact https://github.com/kOlapsis/gofact\nbrew install kOlapsis/gofact/gofact\n```\n\n```powershell\nscoop bucket add gofact https://github.com/kOlapsis/gofact\nscoop install gofact\n```\n\n**Claude Code plugin** — the repository is also a plugin: `creer-facture` skill\n+ declared MCP server:\n\n```\n/plugin marketplace add kolapsis/gofact\n/plugin install gofact@gofact\n```\n\n**From source**: `go build -o gofact .`\n\nOrganizations live in **local directories** (identity, registry, invoices) —\nnever versioned here: they contain real data. `GOFACT_INVOICES_DIR` is still\nhonored for compatibility with the historical skill.\n\n## Known limitations\n\n- Rendering relies on a Blink browser; sandboxed packages (snap, flatpak) are\n  ignored because they cannot read outside `$HOME`.\n- Single VAT rate. Multi-rate is not supported.\n- Currency ≠ EUR: the VAT counter-value in EUR (BT-111) must be provided\n  upstream (`VATEur` field of the model); not exposed in the JSON yet.\n- Only one PDP implemented (SuperPDP).\n\n## License\n\n**GNU AGPL v3 or later** — see [LICENSE](LICENSE).\n\nCopyright (C) 2026 Benjamin Touchard.\n\ngofact is free software: you can redistribute it and/or modify it under the\nterms of the GNU Affero General Public License as published by the Free\nSoftware Foundation, either version 3 of the License, or any later version. It\nis distributed in the hope that it will be useful, but **WITHOUT ANY\nWARRANTY**; without even the implied warranty of merchantability or fitness\nfor a particular purpose.\n",
  "bytes": 10653,
  "sha": "d3a82957ccf9c9b3a6eb791b66729ce046b756442b3a002255eb66174591fab0",
  "repo_slug": "kolapsis/gofact",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_kolapsis_gofact_6b8433ab/readme"
}