Skip to content
Merged
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
65 changes: 65 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# AGENTS.md

Guidance for coding agents working in this repository. (To *use* the `fic` CLI/MCP as a
tool, read [`skills/fic/SKILL.md`](skills/fic/SKILL.md) instead — this file is about
developing fic.)

## Commands

```sh
uv sync --extra dev # install deps (includes mcp + test/lint tools)
uv run pytest -q # full test suite — no live credentials, no network
uv run ruff check fic tests
uv run mypy fic
```

CI runs exactly the pytest / ruff / mypy above. Keep all three green.

## Rules

- **Core purity is load-bearing.** `fic/core/` is pure: no Typer, no `print()`, no
`sys.exit()`, no other I/O framework — it accepts plain args, returns plain data
(dicts / SDK models), and raises typed `FicError`s. It is the seam the CLI *and* the MCP
server both wrap. `tests/test_core_purity.py` enforces this; the **MCP server lives in
`fic/mcp/`, never in `fic/core/`**.
- **The agent contract is public — a change is a contract change.** These are scripted
against by agents and pinned by tests; call out any change explicitly in the PR:
- exit codes (`0..7`) and the one-line JSON error envelope
(`{"error":{"code","message","hint"?,"fields"?,"totals"?}}`) — `fic/cli/_errors.py`;
- the `fic agent-docs` JSON shape (the documented self-description);
- the MCP tool set (13 tools) and the `confirm=true` write gate — `fic/mcp/server.py`.
- **One renderer, one exit.** Commands return typed results or raise `FicError`; they never
print errors. `@handle` (`cli/_errors.py`) renders the envelope/plaintext once and sets the
exit code; `main()` renders click *usage* errors (exit 2) the same way. Don't `print()` errors
in command bodies.
- **Writes are gated.** Destructive CLI commands require `--yes`; MCP write tools require
`confirm=true` (strict `is True`, checked before any network). New mutating operations must
carry the matching gate on both surfaces.
- **No network in tests.** Mock the SDK / inject a fake client (`tests/` patterns). Tests must
pass offline.
- **Conventional commits:** `feat:`, `fix:`, `docs:`, `chore:`, `ci:`, `test:`, `refactor:`.

## Layout

- `fic/core/` — pure business logic over the official `fattureincloud-python-sdk`.
`client.py` (FicClient: retries/backoff, pagination, error mapping), `auth.py`
(env > config > device flow), `registry.py` (clients/suppliers/products CRUD),
`documents.py` (issued/received docs, convert, pdf-url, pay, e-invoice),
`info.py` (reference data + user/company), `errors.py`, `serialize.py`.
- `fic/cli/` — thin Typer adapters. `main.py` (app wiring + the `main()` console entry point),
`_errors.py` (exit codes + JSON error envelope), `_output.py` (json/table), `agentdocs.py`
(`fic agent-docs`), `mcp_serve.py` (`fic mcp serve` glue), plus one module per domain.
- `fic/mcp/` — the embedded MCP server (`server.py`). The `mcp` SDK is an **optional** extra,
imported lazily inside `build_server` so the base CLI runs without it. Tools are thin
wrappers over `fic.core` via per-resource dispatch adapters.
- `tests/` — `core/` (SDK mocked), `cli/` (Typer CliRunner), `mcp/` (dispatch + gate + tool
inventory), and `test_core_purity.py`.
- `docs/superpowers/specs/` — design specs; `2026-06-16-fic-agent-readiness.md` covers this
agent layer (and its adversarial-review resolutions).

## Build order for new surfaces

Foundation (errors/contract) first; agent-facing surfaces (`agent-docs`, MCP) last, since they
walk the finished command/core tree. When adding a domain operation, add the core function
(pure), wire the CLI command, then expose it via an MCP dispatch adapter — and gate it if it
writes.
102 changes: 91 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ uv tool install .
pipx install .
```

**With the MCP server** (for AI agents — see [Use with an AI agent](#use-with-an-ai-agent)):

```sh
uv tool install 'fic[mcp]' # or: pipx install 'fic[mcp]' / uv tool install '.[mcp]'
```

---

## Authentication
Expand Down Expand Up @@ -352,6 +358,20 @@ Reference data lookups (not paginated).

---

### `fic agent-docs`

Print the machine-readable manual (every command, flag, exit code, and the error-envelope
shape) as JSON, or as markdown with `--output table`. See
[Use with an AI agent](#use-with-an-ai-agent).

### `fic mcp serve [--stdio]`

Run `fic` as an MCP server over stdio for AI agents (needs the `mcp` extra). On a terminal it
prints client setup instructions instead of starting; `--stdio` forces the server. See
[Use with an AI agent](#use-with-an-ai-agent).

---

## Output contract

- **Default output is JSON** on stdout — a single value, jq-pipeable, no decorative chrome.
Expand Down Expand Up @@ -380,6 +400,66 @@ fic docs get 123 | jq '.data.number'
| 6 | Not found | 404 response |
| 7 | Forbidden | 403 non-throttle (permission / scope error) |

On failure, JSON mode (the default) prints **exactly one error-envelope line on stderr**;
stdout stays empty:

```json
{"error":{"code":"not_found","message":"Resource not found (404).","hint":"Check the id and that --company-id is correct."}}
```

`code` is one of `error`, `usage`, `config`, `auth`, `validation`, `rate_limited`,
`not_found`, `forbidden`. A `validation` error adds `fields` (per-field messages) and, for a
totals mismatch, `totals` — enough for an agent to fix the input and retry. `--output table`
keeps the human `error: <msg>` form instead.

---

## Use with an AI agent

`fic` is built to be driven by coding agents — two surfaces, same data, same write-safety.

### Self-describing manual

```sh
fic agent-docs # JSON: every command, flag, exit code, and the error envelope
fic --output table agent-docs # the same as a markdown manual
```

Point an agent at `fic agent-docs` and it can discover the whole surface in one call.

### MCP server

Run `fic` as a [Model Context Protocol](https://modelcontextprotocol.io) server so an agent
calls fic operations as tools (needs the `mcp` extra — see Install):

```sh
# Claude Code
claude mcp add fic -- fic mcp serve
```

```jsonc
// Cursor — ~/.cursor/mcp.json
{ "mcpServers": { "fic": { "command": "fic", "args": ["mcp", "serve"] } } }
```

Run `fic mcp serve` in a terminal to print setup instructions for any stdio client (it does
not start the server on a TTY; pass `--stdio` to force it). It exposes ~13 tools:

- **Reads (free):** `fic_list`, `fic_get`, `fic_info`, `fic_account`, `fic_einvoice_status`,
`fic_einvoice_rejection`, `fic_docs_pdf_url`.
- **Writes (require `confirm=true`):** `fic_create`, `fic_update`, `fic_delete`,
`fic_docs_convert`, `fic_docs_pay`, `fic_einvoice_send`. Called without `confirm=true`
they refuse and do nothing — the MCP mirror of the CLI's `--yes`.

The server reads credentials exactly like the CLI (env / config / device login) and honors
`--company-id` on the `fic` command.

### Bundled skill

[`skills/fic/SKILL.md`](skills/fic/SKILL.md) teaches an agent the search→inspect→act loop,
the document `type` enums, the destructive-op gate, and the known traps. Drop it into a
Claude/Hermes skills directory (or your agent's skill loader) and it is auto-discovered.

---

## Gotchas
Expand Down Expand Up @@ -431,26 +511,26 @@ requires `--yes` to confirm. Concurrent modifications to the same document may c

---

## Design: `core/` adapter and future MCP server
## Design: the `core/` adapter

All business logic lives in `fic.core` — a pure Python package with **no Typer, no `print()`,
no `sys.exit()`**. It accepts plain arguments and returns plain data (dicts / SDK models), raising
typed exceptions (`AuthError`, `NotFound`, `ValidationError`, etc.).

The `fic.cli` package is a thin adapter layer: it parses CLI arguments, calls `fic.core` functions,
formats the output (JSON or Rich table), maps exceptions to exit codes, and writes diagnostics to
stderr. It is the only layer that does I/O and exit codes.

This boundary means a future MCP server can import and call `fic.core` directly — without touching
the CLI layer at all:
Both surfaces are thin adapters over it: `fic.cli` (Typer — parses args, formats json/table, maps
exceptions to exit codes, writes diagnostics to stderr) and `fic.mcp` (the MCP server — wraps the
same core functions through per-resource dispatch adapters, with the `confirm=true` write gate).
Neither duplicates business logic, so the two surfaces can never disagree. The `mcp` SDK
dependency is confined to `fic/mcp/` and imported lazily, so the base CLI never requires it.

```python
# A hypothetical MCP server
from fic.core import documents, registry
# fic.mcp and fic.cli both call core like this:
from fic.core import documents
from fic.core.client import FicClient

client = FicClient(access_token="...", company_id=12345)
invoices = documents.list_issued(client, type="invoice", all=True)
with FicClient(access_token="...", company_id=12345) as client:
invoices = documents.list_issued(client, type="invoice", all=True)
```

No Typer, no subprocess, no shell. The core functions are the stable, reusable API surface.
See [`AGENTS.md`](AGENTS.md) for the development contract.
Loading
Loading