@facturably/cli
fct — the Facturably CLI
Invoice like you deploy.
Facturably is invoicing for developers: track time, generate clean PDF invoices, email them, and get paid via Stripe — from a web portal, REST API, this CLI, or an MCP server for your AI agent.
npm install -g @facturably/cli
Requires Node ≥ 20. Also runs on Bun.
Quick start
# 1. Sign in with an API key (create one at app.facturably.com → Settings → API keys)
fct login
# 2. Track some time
fct time start acme/platform -m "auth refactor"
fct time stop
fct time log 2h acme/platform -m "code review" --date yesterday
# 3. Turn it into money
fct invoice create --client acme --from-time --send
Commands
| Command | What it does |
|---|---|
fct login / fct logout / fct whoami |
Authenticate with an API key; inspect your workspaces |
fct clients / fct clients add --name "Acme" --email billing@acme.com |
List and create clients (--email becomes the first billing contact) |
fct projects |
List projects |
fct time start <project> -m "note" |
Start the (single) running timer |
fct time stop |
Stop the timer and save the entry |
fct time status |
Show the running timer |
fct time log <duration> <project> |
Log time after the fact — accepts 1h30m, 1.5, 90m |
fct time week |
This week's timesheet with totals |
fct invoice |
List invoices with status and balances |
fct invoice create --client <id-or-name> [--from-time] [--from A --to B] [--send] |
Draft an invoice for a client (by id or unique name prefix), optionally from unbilled time (--from/--to must be used together), optionally send it |
fct invoice send <id> |
Render the PDF, email the client, lock the time entries |
fct invoice void <id> |
Void an invoice (unlocks its time) |
fct invoice clone <invoiceId> |
Clone any invoice into a fresh draft (time lines become fixed lines) |
fct invoice deliveries <id> |
Outbound email log for an invoice — the original send plus every reminder, with per-recipient delivery status |
fct invoice preview <id> [--theme <name>] [--open] |
Render the invoice to HTML on stdout; --theme previews another theme without changing the workspace default; --open writes a temp file and opens it in your browser |
fct estimate list [--client <id-or-name>] [--status <s>] |
List estimates (draft, sent, accepted, declined, expired) with totals and validity date |
fct estimate create --client <id-or-name> [--valid-until YYYY-MM-DD] [--currency <code>] [--line "Desc:<minor units>[:<qty>]"] [--send] |
Draft an estimate; --line is repeatable and adds fixed-price lines (price in minor units, quantity defaults to 1); currency defaults to the client's |
fct estimate show <id> |
Show an estimate with its lines, totals and validity date |
fct estimate send <id> |
Assign the number, render the PDF, email the client, print the hosted accept link |
fct estimate convert <id> [--due YYYY-MM-DD] [--net <days>] |
Convert an accepted estimate into a draft invoice (once) |
fct estimate deliveries <id> |
Outbound email log for an estimate, with per-recipient delivery status |
fct estimate preview <id> [--theme <name>] [--open] |
Render the estimate to HTML on stdout, or open it in your browser |
fct estimate open <id> |
Open the estimate in the portal |
fct recurring list |
List recurring schedules with status and next run |
fct recurring create --from-invoice <id> (--monthly --day <1-28> | --weekly --weekday <0-6>) [--net <days>] [--auto-send] |
Create a recurring schedule from an existing invoice — monthly on a day of month (1–28) or weekly on a weekday (0 = Sunday); --net sets net-N terms on generated invoices; --auto-send emails them automatically |
fct recurring pause <id> / fct recurring resume <id> |
Pause or resume a schedule |
fct payments |
Payment ledger |
fct settings show |
Show workspace settings — theme, document language, numbering patterns, tax mode, rounding, reminders |
fct settings set [--theme <name>] [--language en|es] [--page-size A4|Letter] [--tax-mode exclusive|inclusive] [--rounding <rule>] [--invoice-pattern <p>] [--credit-note-pattern <p>] [--payment-instructions <text>] [--business-name <name>] [--business-email <email>] [--reminders on|off] |
Change workspace settings; omitted flags are left unchanged. Themes are minimal, ledger, mono, slate, bold; rounding is none, nearest6/15/30, up6/15/30. Tax rates are managed separately (they are relational, not part of this patch) |
fct open |
Open the portal in your browser |
fct ai usage [--since <d>] [--until <d>] [--project <ref>] [--by model|project|day|provider] |
Report AI token usage from local Claude Code, Codex and GitHub Copilot CLI session logs — see AI usage |
fct ai link <path> <project> / fct ai links / fct ai unlink <path> |
Map a local repository path to a Facturably project (stored locally) |
fct ai rates / fct ai rates set <model> [--input <n>] [--output <n>] [--cache-read <n>] [--cache-write <n>] [--currency <code>] / fct ai rates rm <model> |
Your own rate card, in currency units per million tokens |
fct ai bill --project <ref> --since <d> --until <d> [--invoice <id>] [--confirm] |
Turn AI usage into expense invoice lines. Dry run unless you pass --confirm |
AI usage (optional)
Time-and-materials work increasingly includes AI spend as a material. fct ai
reads the session logs your AI coding CLI already writes to your own disk, and
turns them into ordinary expense lines on an invoice.
It is entirely optional and entirely local. There is no new dependency, no
required configuration, and no external service. If you do not use an AI CLI,
fct ai usage tells you where it looked and exits 0. Every other fct command
behaves exactly as it always did.
What it reads
| Tool | Location | What is taken from it |
|---|---|---|
| Claude Code | ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl |
Per-turn message.usage (input, output, cache-creation, cache-read) and message.model |
| Codex | ~/.codex/sessions/YYYY/MM/DD/*.jsonl |
total_token_usage / last_token_usage and the session's model. Totals are cumulative, so successive records are differenced |
| GitHub Copilot CLI | ~/.copilot/session-state/<id>/events.jsonl, %LOCALAPPDATA%\copilot\session-state\…, or $COPILOT_HOME/session-state |
Per-call assistant.usage events (inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, model), falling back to the session.shutdown modelMetrics aggregate only when a session has no per-call events |
Transcripts routinely exceed 30 MB, so they are streamed line by line and never read whole into memory. An unparseable line — usually a truncated tail from a session that is still running — is skipped and reported, never fatal.
The Claude Code reader is verified against real transcripts. The Codex and GitHub Copilot readers are written from their event schemas and could not be exercised against real sessions — neither tool had any logs on the machine they were written on. They are built to find nothing rather than to report a wrong number, but treat their first real run as something to sanity-check.
Copilot records no working directory in some sessions. That usage is reported as
(unknown working directory) and left unattributed rather than guessed at.
Nothing about tokens is ever sent to the Facturably API. fct ai bill
creates the same expense line any other API client could create.
Quantities come from the logs; prices come from you
Facturably ships no built-in price table, on purpose. A hardcoded table goes stale silently: the model you ran today is missing from it tomorrow, and the session quietly costs $0 on a client's invoice. So you configure the rate card:
# Rates are in currency units per MILLION tokens.
fct ai rates set claude-opus-4-8 --input 15 --output 75 --cache-read 1.5 --cache-write 18.75
fct ai rates set gpt-5 --input 15 --output 60 --cache-read 7.5 --currency USD
# A bare prefix matches every version under it; "*" is a catch-all.
fct ai rates set claude-sonnet --input 3 --output 15 --cache-read 0.3
Cache-read is priced separately because it dominates real usage by orders of magnitude — a single session can read over a billion cache-read tokens against a few thousand input tokens. Pricing them together makes the number meaningless.
Flat-rate tools. GitHub Copilot CLI is a subscription, not metered per
token, so there is no per-token provider charge to pass through — Facturably
ignores the cost field Copilot writes for exactly that reason. A common fair
approach for a flat fee is to allocate the monthly subscription across projects
by token share rather than pricing tokens directly. fct ai usage --by project
gives you the shares; the split itself is yours to make.
If a model has no rate, fct ai usage still counts its tokens but shows the
cost as unpriced, and fct ai bill refuses to bill it and names it. It is
never silently billed at zero.
Linking repositories to projects
Usage is attributed by the working directory a session ran in. Nothing is guessed — you map paths explicitly, and the mapping lives in your local config:
fct ai link ~/code/acme-platform acme/platform
fct ai links
fct ai usage lists paths that have usage but no link, so you can see what is
still unattributed. The most specific link wins, so linking a repository root
covers every subdirectory of it.
Reporting and billing
# What was spent, grouped however you need it
fct ai usage --since 2026-07-01 --until 2026-07-31 --by model
fct ai usage --since 2026-07-01 --project acme/platform --by day --json
# Dry run by default — shows exactly the lines it would create, writes nothing
fct ai bill --project acme/platform --since 2026-07-01 --until 2026-07-31
# Actually create them (one expense line per model, on a new draft invoice)
fct ai bill --project acme/platform --since 2026-07-01 --until 2026-07-31 --confirm
# ...or append to an existing draft
fct ai bill --project acme/platform --since 2026-07-01 --until 2026-07-31 --invoice inv_123 --confirm
Each line's description itemises what is being charged for, so the invoice reconstructs itself for the client:
AI coding assistance — claude-opus-4-8, 2026-07-01 to 2026-07-31: 5,237 input @ 15/M + 129,400 output @ 75/M + 1,284,300,000 cache-read @ 1.5/M (USD per million tokens)
Before you bill it: passing AI costs through to a client is a commercial question, not a technical one. Check that your contract or statement of work actually permits it, and that the rate card you configure is one you can stand behind.
Scripting
Every command supports --json for machine-readable output:
fct invoice ls --json | jq '.items[] | select(.status == "sent") | .number'
Environment variables override stored config: FCT_API_KEY, FCT_TENANT (workspace slug), FCT_API_URL.
Exit codes: 0 success · 1 API/runtime error · 2 usage error · 3 not authenticated.
Notes
- Amounts are always integer minor units on the wire (cents) — money is
{ amount, currency }, invoice balances come frominvoice.totals; no floats near your money. - Project arguments take the project code (e.g.
acme/platform); the CLI resolves it to the API's project id for you. - Config is stored at
~/.config/facturably/config.json(%APPDATA%\facturablyon Windows). - The MCP server (
@facturably/mcp) exposes the same operations to Claude and other AI agents.
Plural Solutions, Inc. All rights reserved. This package is proprietary software; see LICENSE terms at facturably.com/legal.