# Install & setup for agents

A step-by-step guide for an AI agent setting up `@elnora-ai/merit-aktiva` for a user. If
you are a human, the [README](README.md) quickstart is shorter. Follow these steps in
order; do not skip the identity/safety gates.

`elnora-merit` writes to a **live accounting system**. Treat every write as consequential:
confirm intent, prefer `preview`/read-only commands first, and never pass `--yes` unless
the user explicitly asked to perform that specific destructive action.

---

## 0. Identity & safety gate

Before doing anything, confirm with the user:

1. **Which Merit company** these credentials belong to (there is no "test mode" — the API
   acts on the real company books).
2. That they have a **Merit Aktiva Pro or Premium** license (the API is unavailable on
   lower tiers; a non-Pro key returns `401 api-wronglicense`).
3. Whether they also use **Merit Palk** (payroll — separate product, separate keys) and/or
   want the **Stripe → Merit reconcile** connector. Only collect those credentials if so.
4. Whether they bank with **LHV** — if so, recommend connecting it (step 4c) so statements
   come from the bank instead of a manual export. It needs **no credentials**: never collect a
   bank token.

Never print a secret value back to the user or into logs. The CLI redacts credentials from
its own error output; you must not defeat that by echoing keys.

---

## 1. Verify the install

```bash
elnora-merit --version
elnora-merit --help
```

If `elnora-merit` is not found, install it: `npm install -g @elnora-ai/merit-aktiva`
(requires Node.js ≥ 20). The `--help` output lists every command group.

---

## 2. Collect & store credentials

The CLI resolves credentials in this order (first wins):

1. `process.env` (`MERIT_API_ID`, `MERIT_API_KEY`, `MERIT_LOCALIZATION`, `MERIT_API_VERSION`)
2. `~/.config/elnora-merit/.env`
3. `./.env` in the current directory (dev convenience)
4. An interactive prompt (only on a TTY)

**Where the user generates the keys:** in Merit Aktiva → **Settings → Company data → API
settings → "Koosta võti" (Generate key)**. This yields an **API ID** (a GUID) and an
**API Key** (a base64 secret used as the HMAC shared key).

**Offer to drive the browser.** If a Chrome DevTools MCP server (`chrome-devtools`) is
available, offer to open Merit and walk this click-path with the user — navigating to
**Settings → Company data → API settings** and driving the "Koosta võti" step so they can
copy the ID and Key without hunting for the page. Keep it an offer, not a requirement:
some users would rather click through it themselves. Either way it is the user who reads
off and pastes the values — never generate or guess them.

Write them to the per-user env file at mode `0600` (do **not** commit this anywhere):

```bash
mkdir -p ~/.config/elnora-merit
umask 077
cat > ~/.config/elnora-merit/.env <<'EOF'
MERIT_API_ID=<the API ID>
MERIT_API_KEY=<the API key>
MERIT_LOCALIZATION=ee   # ee (Estonia, default) or pl (Poland)
EOF
chmod 600 ~/.config/elnora-merit/.env
```

Substitute the real values for the `<...>` placeholders (ask the user to paste them; do not
invent them). Running `elnora-merit accounts list` once on a TTY with no credentials will
also prompt and save them at `0600` for you.

### Optional — Merit Palk (payroll)

A **separate product** with its own keys and host (`palk.merit.ee`, Estonia-only, requires
a Palk PRO license). Generate in **Merit Palk → Settings → API Settings**. Add to the same
env file:

```bash
MERIT_PALK_API_ID=<Palk API ID>
MERIT_PALK_API_KEY=<Palk API key>
```

### Optional — Stripe reconcile

If the user books Stripe payouts into Merit, add a Stripe **secret or restricted** key
(read-only is sufficient — the integration never writes to Stripe):

```bash
STRIPE_API_KEY=<live secret/restricted key for the Stripe account whose payouts you book>
```

---

## 3. Smoke test

The cheapest read-only call that proves auth works:

```bash
elnora-merit accounts list --output table --fields Code,Name
```

- Success → a table of the chart of accounts.
- Exit code `3` with a `suggestion` → credentials missing/invalid (revisit step 2).
- Exit code `6` with `status: 401` and body `api-wronglicense` → the account is not on a
  Pro/Premium plan.

---

## 4. Optional — configure Stripe reconcile

```bash
elnora-merit reconcile init        # writes ~/.config/elnora-merit/stripe-map.json (0600 placeholder)
```

`reconcile init` also prints candidate account codes and VAT codes from the live Merit
company. Edit the map to set: the GL account codes (`revenue`, `vatPayable`, `stripeFees`,
`platformFees`, `refunds`, `clearing`), `vat.code` (a Merit TaxId from `taxes list`),
`cutoffDate`, and optionally `vatTimezone` (default `Europe/Tallinn`; `Europe/Warsaw` for
PL) and `revenueMemo`. See [`stripe-map.example.json`](stripe-map.example.json). The map
holds **no secrets** — the Stripe key stays in the environment — but it is company-specific,
so it is gitignored and must never be committed.

Then always preview before writing:

```bash
elnora-merit reconcile preview --output table   # read-only
elnora-merit reconcile run --yes                # writes; idempotent via a local ledger
```

---

## 4b. Optional — configure document sync (missing-receipt audit)

Keep every transaction backed by its source document. Read-only to start:

```bash
elnora-merit documents list-missing              # invoices with no attached receipt/invoice
elnora-merit documents run                        # audit → search sources → match → digest (read-only)
```

Config lives in `~/.config/elnora-merit/docsync.json` (copy [`docsync.example.json`](docsync.example.json);
gitignored, may hold a webhook URL). With no config it scans `~/Downloads`.

**Recommended companion — install the [elnora-google-workspace](https://github.com/Elnora-AI/elnora-google-workspace)
plugin.** The two connect: the bundled adapter [`adapters/gmail-drive-gw.mjs`](adapters/gmail-drive-gw.mjs)
uses its `gw` CLI to pull receipt PDFs straight from Gmail and Drive, so most missing
documents are found and attached automatically. After installing that plugin and running
`gw auth`, add this source to `docsync.json`:

```jsonc
{ "type": "command", "label": "gmail-drive", "command": "node ./adapters/gmail-drive-gw.mjs" }
```

Then `documents run --apply` stages matched PDFs for a one-click Merit upload, and
`documents install-schedule` runs it unattended. See [docs/document-sync.md](docs/document-sync.md).

---

## 4c. Optional — connect LHV bank (recommended if the user banks with LHV)

Automates the other half of the books: the statement comes from the bank instead of a manual
export. Estonia-only, LHV customers only.

**Collect nothing.** This needs **no credentials** and nothing in `.env`. LHV's own read-only
MCP server is already bundled with this plugin (`.mcp.json`); the user authenticates directly
with LHV and the token stays in their MCP client. You must not ask for, handle, or store a
bank token — if the user offers one, decline and point them at `/mcp`.

Tell the user to run:

1. **`/mcp`** → **lhv** → **Authenticate**
2. Sign in with **Smart-ID / Mobiil-ID / ID-card / biometrics** (same as their internet bank)
3. Grant **both** `accounts:read` and `transactions:read` (statement import needs both)

Verify it connected:

```bash
# via the lhv MCP tools, not the CLI:
list_accounts            # → IBAN, currency, availableBalance per account
```

What it adds — four read-only tools:

| Tool | Scope | Returns |
|---|---|---|
| `list_accounts` | `accounts:read` | every account: IBAN, currency, available balance |
| `get_balances(iban)` | `accounts:read` | available + **settled** + **reserved** |
| `get_transactions(iban, dateFrom, dateTo)` | `transactions:read` | raw **camt.053** XML, max 31 days |
| `get_transactions_summary(iban, dateFrom, dateTo)` | `transactions:read` | totals, top counterparties |

`get_transactions` returns camt.053 — exactly what `payments import-statement` accepts, so the
bank feeds the books with no file in between.

**Load the `merit-lhv` skill before importing anything.** Two rules that are not optional:

- **Check the period isn't already booked first** (compare the bank account's GL balance to the
  statement's real closing balance; equal → already booked → stop). Merit's idempotency does
  **not** protect you — it cannot see payments posted via the API.
- **Never confirm a row with `Muud` when its invoice already exists** — that books the expense
  twice. Use `Võlgnevused` to clear an existing invoice.

LHV has **no write scope**, so this can never move money. Revoke: internet bank → Settings →
Active sessions.

---

## 5. Conventions you must follow

- **Output is compact JSON on stdout by default.** Parse it. `--output table` for humans,
  `--output csv` for spreadsheets, `--pretty` for indented JSON, `--fields a,b` to project.
- **Errors are JSON on stderr** as `{ "error", "suggestion", ... }`. Check the exit code:
  `0` ok · `1` general · `2` validation · `3` auth · `5` rate limited ·
  `6` API error.
- **Merit endpoints are POST with a JSON body, even queries/reports.** That is normal.
- **Reads** = `list` / `find` / `get` / reports. **Writes** = `create` / `send` / `update`
  / `delete`.
- **Complex documents** (invoices, GL batches, payments) take the documented Merit JSON
  body via `--data '<json>'` or `--file <path>`. Run the command's `--help` first — it
  lists the exact required fields and nested shapes. Field names are PascalCase; booleans
  like `NotTDCustomer` are the lowercase strings `"true"`/`"false"`.
- **Dates:** query fields accept `YYYY-MM-DD` or `YYYYMMDD`; some payload fields use
  `YYYYMMDDHHMMSS`. Palk uses `YYYY-MM-DD` dates and `YYYYMM` months.
- **Destructive commands require `--yes`.** Never add it unless the user asked to delete
  that specific record. Sales invoices cannot be updated — `delete` (with `--yes`) and
  re-create.
- **Limits:** ~100 requests/minute (the CLI auto-retries 429 and transient 5xx); invoice
  list queries span at most 3 months; at most 500 rows per document.

---

## 6. Company-specific bookkeeping

The bundled `merit-*` skills carry the **correct Merit method** but no company's account
numbers. Get the real codes from the **company profile** — a local snapshot of the
account's own chart of accounts, banks, VAT codes, and financial years:

```bash
elnora-merit profile sync                 # pull from the live account → company-profile.json
elnora-merit profile show --section taxes  # e.g. the VAT TaxId guids to use
```

`profile sync` writes `company-profile.json` into the references directory
(`MERIT_REFERENCES_DIR`, default `~/.config/elnora-merit`; gitignored, never committed).
Load it before posting so the right accounts are used; re-sync when the chart of accounts
changes. The profile holds the machine-readable codes — any prose conventions the user
keeps (which revenue account, KMD cadence, standing rules) live in their own notes in the
same references directory. If neither is available, look codes up live (`accounts list`,
`taxes list`) rather than guessing.

---

## 7. Wire up routing so the write agents fire

The plugin ships three agents that wrap the CLI with guardrails (customer resolution, per-rate
VAT totals, payload preview, approval gate):

| When the user asks to… | Agent |
|---|---|
| create / bill a **sales invoice** | `merit-aktiva-workspace:merit-invoice-creator` |
| **record a payment**, enter a **purchase invoice**, **reconcile** open items | `merit-aktiva-workspace:merit-bookkeeper` |
| **look up a company** (name / VAT / address / e-invoice capability) | `merit-aktiva-workspace:merit-company-lookup` |

These only fire if the host is told to reach for them. A host that just loads a `merit-*`
skill gets a recipe and hand-runs the CLI — skipping the guardrails. As the last setup step,
add one routing rule to the host's own instructions (its `CLAUDE.md`, `AGENTS.md`,
`.cursorrules`, or persistent memory) — do **not** put it in this repo:

> Merit **write/lookup** work (create invoice, record payment, enter purchase, reconcile,
> company lookup) → dispatch the `merit-aktiva-workspace` agent, don't hand-run the CLI.
> Merit **read/report/VAT** work (KMD, reports, Stripe/LHV import, reverse charge, payroll)
> has no agent → use the `merit-*` skills directly.

Agent-first only where an agent exists; everything else stays on the skills.
