nyrut
v0.1.0
Published
Nyrut from your terminal: invoices, expenses and Spanish tax estimates.
Maintainers
Readme
nyrut
Nyrut from your terminal. Invoices, clients, expenses and a quarterly tax estimate, without leaving the shell.
npm install -g nyrut
nyrut login
nyrut invoicesWhat it is
The same account, the same data, the same rules. There is no separate CLI backend and no separate CLI logic: this talks to the same database the web app talks to, authorises through the same row-level security, and imports the same modules that compute totals, invoice numbers and taxes. An invoice created here opens in the browser as an ordinary invoice, because it is one.
Commands
nyrut invoices list, most urgent first
--status overdue|unpaid|draft|paid
--client <name>
--limit <n> · --all
nyrut invoice <number> draw the invoice
nyrut invoice new create one (--client --issuer --date --due)
nyrut invoice send <number> email it (--to --yes)
nyrut invoice pay <number> mark it paid (--undo)
nyrut invoice link <number> public link to share
nyrut invoice open <number> open it in the browser
nyrut clients who owes what
nyrut issuers your issuing entities
nyrut expenses what you have recorded (--year)
nyrut summary VAT by quarter (--year --issuer)
nyrut login · logout · whoami--json works on every listing and is a separate code path: no colour, no
padding, no headers. You should never have to regex pretty output.
The invoice, drawn with characters
nyrut invoice 2026-014 draws the document rather than dumping its fields —
same regions, same order, in the issuer's own brand colour:
╭──────────────────────────────────────────────────────────────╮
│ ▄▄ │
│ │
│ FACTURA Nafs Signal SLU │
│ nafs-001 B88926027 │
│ │
│ FECHA DE EMISIÓN VENCIMIENTO │
│ 3 de agosto de 2026 18 de agosto de 2026 │
├──────────────────────────────────────────────────────────────┤
│ DE FACTURAR A │
│ Nafs Signal SLU Etéreo Experience, SL │
…It is a preview, and it says so. It exists to check at a glance that the
numbers, the parties and the dates are what you meant. The document people
archive is the A4 PDF, and it is made in the browser — nyrut invoice open is
one keystroke away.
Each document is drawn in its own language and currency format, because that is what the client receives. Lists are drawn in yours.
Signing in
nyrut login opens a loopback server on 127.0.0.1, sends you to the browser
you are already signed into, and the web app asks you — on screen, with the
port named — whether to hand the session to it.
- The port is loopback-only and chosen by the OS.
- A random
statetravels out and must come back. - The server answers one request and dies.
- The web side refuses any callback that is not literally
127.0.0.1,::1orlocalhost. Not "resolves to loopback":localtest.mepoints at 127.0.0.1 and somebody else controls its DNS. - Handing the session over needs a click. A link alone is never enough.
The refresh token is stored at ~/.config/nyrut/session.json with mode 0600.
A keychain would be better and is the obvious next step. nyrut logout removes
it.
Piping and scripting
- Colour never carries meaning. Every line reads correctly through
| cat, on a dumb terminal, and underNO_COLOR. - Animation stops the moment nobody is watching: not a TTY, piped,
CI,NO_COLOR, or--plain. Output that changes shape when piped is output you cannot script. - Progress spinners go to stderr.
nyrut invoice link X | pbcopycopies the URL and nothing else. - Unknown options are an error.
--stauts=paidsilently ignored would list every invoice and look like it worked.
Taxes
nyrut summary runs the app's own liquidacion303 / liquidacion130,
including the carry-forward of a negative quarter and both legs of reverse
charge. One issuer at a time, always: VAT is filed per tax ID, so adding two
issuers together produces a number matching no return anyone will ever file.
Modelo 130 appears only for individuals — a company files corporate income tax
instead.
Invoices in a currency other than the workspace's are excluded and counted, because converting them needs the ECB rate for each invoice's date and the app is what fetches it. Two answers to the same question would be worse than one answer with a footnote.
It is an estimate to check against, not a filing.
Configuration
The Supabase URL and publishable key are baked in at build time from the app's
own .env.local. Nothing secret is bundled — the publishable key is the one
already shipped to every browser, and every row it reaches is reached through
RLS with your session.
Environment overrides, for pointing at a staging deployment:
NYRUT_SITE default https://www.nyrut.com
NYRUT_SUPABASE_URL
NYRUT_SUPABASE_ANON_KEYBuilding
cd cli && npm install && npm run build # → cli/dist/nyrut.mjs
npx vitest run cli/ # renderer and parser testsThe renderer is pure — data in, string[] out — which is what makes it
testable. The tests check the thing that can break silently: that every line of
the card is exactly the same visible width, measured on text rather than bytes,
with accents, long names, four currencies and empty data.
