Commands
| Command | What it does |
|---|---|
ekwo init |
The whole installation, interactive or not: the socle's migrations, the reference seeds, then the modules', as ekwo migrate applies them. --no-company stops before the first company: see several countries. |
ekwo migrate |
Applies the migrations this release adds, after showing the gap — the socle's, then the modules'. Re-applies the reference seeds, which are idempotent. --no-modules leaves the modules alone. |
ekwo status |
Schema version installed against available, pending migrations, the instance, its administrators, the country packs it holds and, per company, the pack version it copied. On an installation that is not registered, a short invitation to register. Exits 1 when something is pending. |
ekwo doctor |
Every object this release defines and every privilege it grants, against what the database holds; row level security on every table, a policy on every protected table, no pending migration, no membership pointing at a deleted user, every company with a bank account, statements that tie to their lines, posted entries that balance. Exits 1 on a problem, 0 on warnings. |
ekwo register |
Opt in to security advisories and release notes. Optional: everything works the same without it. Also the retry when the announcement did not go through. |
ekwo unregister |
Opt back out. Clears the address and the date on the instance row. |
ekwo demo |
Loads the sample company. Fictional data, explicit request only. |
ekwo module |
What is installed beside the socle, applies a module's migrations and its country seeds, and turns one on or off for a company. |
ekwo company |
new creates a company in its own country, through create_company(), and list shows the companies held here. show reads the company in use as the person signed in — its financial years, lock dates, journals and accounts by role — the same document as the MCP tool get_company. One company leaves an installation with its books — export writes an archive anybody can read, as a member under row level security — and arrives in another one alive: import takes it in whole or not at all. |
ekwo pack |
Compiles a country pack into its seed, and refuses a seed that is no longer the output of its pack. Runs in a checkout of the repository only. |
ekwo login |
Signs in to an instance as yourself and keeps the session, in your own configuration directory. See acting as a person. |
ekwo logout |
Ends that session, here and on the instance. |
ekwo use <company> |
Picks the company the next commands run on. |
ekwo whoami |
Who you are on which instance, the companies you can see, and what you may do on the one in use. |
ekwo contact add / list |
A customer or a supplier, and finding one again. |
ekwo contact duplicates |
The contacts that are probably one party, strongest first, through suggest_duplicate_contacts(): one row per contact to absorb, the rows of one kept contact being one party, with a score, the evidence — the same VAT or registration number, the same bank account, the words of the name weighed by how few contacts carry them — and the simplest name the group carries, proposed for the kept contact. Writes nothing. |
ekwo contact merge <keep> <absorb> |
One party created twice — two spellings, two imports — joined through merge_contacts(): everything that names the second, posted lines and issued documents included, names the first; the first takes the fields it left empty; the second is retired, never deleted, and named by nothing again. By name or id. |
ekwo contact statement |
What went through the accounts of one contact over a period, the balance it runs to, and what is still open. |
ekwo doc new / doc line add |
A draft document — any kind, with --type — and one more line on it. A draft books nothing. ekwo invoice is the old name of ekwo doc, kept as an alias. |
ekwo doc approve <document> |
Approves a draft, as it stands, for an agent to post where agents post in review, through approve_document(). Signed in as yourself, with documents.post; refused to a machine key. A change to the draft afterwards needs another approval. |
ekwo company agent-posting [review|autopilot|review_only] |
The setting agent_posting: how an agent — a machine key, the MCP server — books a document of the company in use. review, the default, once a person approved it, unless the agent's session was started with --autopilot; autopilot, directly, within its rights, each posting on the audit trail; review_only, review with autopilot refused to every session. Without a value, says which. Signed in as yourself, with company.write; an agent may only make it stricter. |
ekwo key list [--all] / ekwo key create <name> --capability <code>… [--expires <timestamp>] [--autopilot] |
The machine keys of the company in use, with what each may do and how it posts; and one more, through create_api_key(), its secret shown once. A key posts as the company's agent_posting says; --autopilot issues one that posts without approvals — with company.write, never in a review_only company — and the mode is fixed for good: a key wanted in another mode is issued again. Needs members.manage. |
ekwo mcp [--autopilot|--review] |
The MCP server, over stdio, for an agent: the same server as ekwo-mcp of @ekwo-ai/mcp, with its flags and its environment (ekwo mcp --help). The @ekwo-ai/mcp installed beside this CLI, or the one of its release through npx — this CLI keeps its one outside dependency. In .mcp.json: "command": "ekwo", "args": ["mcp"]. |
ekwo post <document> |
Books it, through post_document(). --dry-run shows the entry the database would write and writes nothing. Signed in as yourself, you post in any mode; with a machine key, the company's agent_posting applies, or the key's own autopilot. |
ekwo cancel <document> |
Undoes a posted invoice, and says how. Back to draft, through unpost_document(), where its country allows it and nothing about it has left; otherwise through cancel_document(): the credit note that names it, posted and matched against it, and the invoice cancelled. --date books the credit note on another day than the invoice's, which is how a locked period is stepped over; --credit asks for the note where a draft was possible. |
ekwo reverse <entry> |
Undoes a posted entry keyed by hand, through reverse_entry(): its mirror, posted and matched against it. By id or by number; --date as for cancel. |
ekwo line contacts suggest [--line <line>]… / ekwo line contact set <line> <contact> |
The open receivable and payable lines that name nobody — open items of books imported line by line — with the contacts each could be, best first: a learned motif, the number of a document of the company quoted in the label, the description or the reference of the entry, the words of a contact's name, and how many contacts the same evidence reached. set names one, once, through set_line_contact(), and the label is learned for the next time. A contact by name or id. |
ekwo line unnamed leave <line> --reason <text> / ekwo line unnamed undo <line> |
Leaves such a line without a contact on purpose, with the reason, through leave_line_unnamed(): it is no longer suggested a contact and stays an open item. undo takes it back. |
ekwo line combinations suggest [--contact <contact>] / ekwo line match <line> <line>… [--difference <reason>] / ekwo line differences list |
Which open receivable and payable lines of one counterparty settle each other — an exact pair, several lines to the cent, or within the company's difference tolerance — through suggest_item_combinations(); match matches the lines named in one gesture under one letter (reconcile_many()), or with --difference books the difference on the account the pack gives for the reason and matches everything (reconcile_with_difference()); differences list gives the reasons, their accounts and the tolerance. |
ekwo payment record |
Money in or out, booked and matched. With --doc, against that document. |
ekwo match <transaction> <document>… [--amount <amount>]… [--extra "account=<code>,amount=<amount>,tax=<code>"]… |
A bank statement line pays documents, through settle_from_statement(): --amount, once per document, says how much settles each; --extra books the fee or the charges the line is net of in the same entry, and alone settles a bank charge. The line balances to the cent or is refused. |
ekwo analytic axes, ekwo analytic report --axis <code> --from <date> --to <date> |
The analytic axes of the company and their values; and the income, expense, result and fixed-asset movement of each value of one axis over a period, read by analytic_report() from the posted ledger, a parent with the subtotal of the values below it, then the part no share of the axis reaches and the total of the period. |
ekwo balance --from <date> [--to <date>] |
The trial balance: opening balance, debit, credit and closing balance of every account over the period, from posted entries only, read by trial_balance(), and the debit and credit totals. |
ekwo ledger [<account>] --from <date> [--to <date>] |
Every posted line of one account by its code, or of every account, with the balance carried forward to the first day and a running balance, read by general_ledger(). |
ekwo journal [<journal>] --from <date> [--to <date>] |
The book of one journal by its code, or of every journal: the totals of each, then every line of every posted entry in the order a book is read, from journal_totals() and journal_book(). |
ekwo aged [--receivable | --payable] [--at <date>] |
What each customer still owes, or what is still owed to each supplier, at a day, by days overdue, read by aged_balance() from the lines no matching has settled. --to and --at are today when they are not given, and the output says so. |
ekwo vat --from <date> --to <date> [--form <code>] |
The VAT return of the period, read by vat_return(): every box with its name and amount, in the order the form of the country pack prints them, the boxes the form derives from the others marked computed. --form names the declaration where the country files several. A period the company does not file that form on is refused by the database, by name. It prepares figures; it files nothing. |
ekwo ecsl --from <date> --to <date> [--form <code>] |
The recapitulative statement of supplies to customers in other Member States, read by ec_sales_list(): one line per customer VAT number and nature, credit notes deducted, and a warning for each line that cannot be filed as it stands. With --form, the period is checked against the cadence the company files that statement on. |
ekwo statement [<code> --from <date> --to <date>] |
Without a code, the balance sheets and income statements the company can be presented on, read by available_statements() — the schemes of its country pack, the ones its chart reports on marked default, and the generic framework by account type. With a code, that statement for the period, read by financial_statement(): every line, nil included, in the order the scheme prints it, totals in bold, and every account no line catches, from unmapped_accounts(), so a statement that does not tie out says so. It prepares figures; it files nothing. |
ekwo import <source> <file>… |
Books kept elsewhere — trial-balance, fec, journal-items, journal-report, transaction-journal, xaf — whole or not at all, through import_books(), with a correspondence of accounts and journals you save and give back; or a bank statement — camt.053, coda, cfonb120 — as pending lines. --dry-run rehearses. See taking over books. |
ekwo doc list / show |
What exists, and with --unpaid what is posted and still owed. See keeping books. |
ekwo doc pdf <document> |
The PDF of a sale invoice or credit note, rendered from the books by @ekwo-ai/invoice-pdf, with the logo the company names by URL (fetched by this command, from a public http(s) address only). Written to --out, or under its own name in the current directory. --factur-x embeds the CII of a posted sale and makes it a Factur-X PDF/A-3 (the rules it breaks end on exit 1); --page-size Letter; the words of the layout are in the document's language where a set exists for it (de, en, es, fr, nl), in English otherwise, and --labels <file.json> puts other words over them. ekwo document is the whole word of ekwo doc. Records nothing. |
ekwo proof stamp / upgrade / verify |
The sha256 of a filed file — a declaration, the annual accounts of a financial year, a document — committed to Bitcoin through the public OpenTimestamps calendars; only the hash leaves. upgrade completes the pending proofs of the company and is made to run on a schedule; verify checks a file against its proof and the block it names, and --out writes the .ots file any verifier reads. See docs/filing-proofs.md. |
ekwo einvoice validate / issue / status / list |
The electronic invoice of a posted sale, in the format its country pack declares, through the einvoicing module: validate writes it and names the rules it breaks (exit 1 if any), issue keeps it and with --send --to <directory> writes it to that folder, status --refresh follows it. See modules/einvoicing. |
ekwo einvoice fiscalise / fiscalisation |
The fiscal record a country's fiscalisation regime requires for every invoice — VERI*FACTU is one — written by the brick its pack names and chained after the last record of the invoicing system described by --system <file> (or EKWO_FISCAL_SYSTEM); --dry-run writes it and chains nothing, a record that breaks a rule is never chained (exit 1). fiscalisation walks each chain, and names the records still to send and the posted sales without a record. |
ekwo einvoice clearance <document> submitted|validated|rejected|cancelled |
Under a clearance regime — the authority validates every invoice before it is issued — records what happened to a posted sale: its submission (--reference, --issue), the authority's validation (--identifier, --validated-at, --verification-url — or, where the pack builds the link, --detail and --environment), a rejection by the authority or the buyer (--reason, --by), its cancellation (--reason), each --at a moment, --detail <file> the answer as JSON. It dials nobody; the database refuses a transition that cannot have happened, and a window of the pack that has closed. fiscalisation then counts the sales validated, waiting, rejected and cancelled. |
ekwo peppol validate <file> / lookup <participant> |
The Peppol network, with no installation and no sign-in. validate reads a UBL invoice or credit note as it arrived and names the rules it breaks — the arithmetic of EN 16931 and the electronic addresses of both parties — with exit 1 if any; nothing is booked. lookup checks a participant identifier (0208:0999999922, iso6523-actorid-upis::… or a symbolic scheme) and asks the DNS of the Service Metadata Locator whether it is registered, and with which Service Metadata Publisher; --test asks the acceptance network. Exit 0 only for a registered participant. See @ekwo-ai/peppol-ubl. |
There is no eject, because there is nothing to eject from. The schema is in
your database, the migrations are in the repository under AGPL-3.0, and
supabase db push applies them without this CLI ever running again.
What a command answers: --json and the exit codes
Every command prints for a person by default, and takes --json for a
program. Under --json the standard output is one JSON document and nothing
else, with the same shape whatever happened:
{
"ok": false,
"command": "module enable",
"exitCode": 3,
"warnings": [],
"error": {
"kind": "refusal",
"name": "not_allowed",
"message": "not_allowed: enabling a module on this company needs company.write",
"sqlstate": "42501"
}
}
data carries what the command has to say; its shape per command is
published in schema/output.1.json. Amounts are
decimal strings, dates are ISO 8601. ekwo help --json answers the whole
reference of commands and flags in one document, for an agent.
| Exit code | Means |
|---|---|
0 |
Done. |
1 |
It failed for a reason that is not the books — the network, a bug — or a check found something (doctor, status, pack check). |
2 |
The command was called wrong, or needed an answer with no terminal to ask it on. |
3 |
The database refused: a locked period, a capability you do not hold, a constraint. The call was well formed; the accounting said no. |
A refusal is printed as the database wrote it — period_locked: … — and never
rephrased or worked around. No command waits on a question when there is
nobody to answer: off a terminal, under --json or with --yes, a missing
answer is exit code 2 with the flag to pass.
Acting as a person: login, use, whoami
init, migrate, status and doctor connect as the owner of the database.
Anything that keeps books acts as a person, through the instance's API,
under row level security — the same route and the same functions as the MCP
server.
ekwo login --supabase-url https://<ref>.supabase.co --anon-key <publishable key> --email you@example.com
ekwo whoami
ekwo use "Example One"
- The session is kept in
$EKWO_CONFIG_DIR, else$XDG_CONFIG_HOME/ekwo, else~/.config/ekwo, readable by you alone, renewed on its own, and refused inside a repository. The password is sent to the instance once and written nowhere.ekwo logoutends it. - Profiles (
--profile,EKWO_PROFILE) keep one instance, one person and one company in use each: a demo, production, one client of a firm. - For a CI job,
SUPABASE_URL,SUPABASE_ANON_KEYand eitherEKWO_ACCESS_TOKENorEKWO_EMAILwithEKWO_PASSWORDsign in for one command and write nothing; pass--company. - The company in use is set with
ekwo use, or--companyfor one command. Under--json, every answer carries acontextnaming the profile, the instance and the company it was rendered for. - Never a
service_rolekey: it is refused by name, at every door it could arrive by.ekwo initremains the one command that takes it.
Keeping books
ekwo contact add "Client Example" --country <cc> --ref crm-42
ekwo doc new --contact client --date 2026-06-15 --ref job-7 \
--line "name=Audit,price=1500.00,account=<account code>,tax=<tax code>"
ekwo doc line add job-7 --name Travel --price 250.00 --account <account code>
ekwo post job-7 --dry-run # the entry the database would write; nothing is written
ekwo post job-7
ekwo cancel job-7 # back to draft where the country allows it, else the credit note
ekwo payment record --doc job-7 --amount 1750.00 --date 2026-06-30 --bank-account <id> --ref bank-1
ekwo match <bank transaction id> job-9
ekwo match <payout id> job-9 --extra "account=<fee account>,amount=3.00,tax=<tax code>"
ekwo doc list --unpaid --since 2026-06-01 --json
- Each verb is one function of
@ekwo-ai/core, the same the MCP server calls, and the rules underneath are the schema's. This CLI computes no amount: what you type goes in as text, what is printed is what came back. - A tax and an account are named by their code, never by a rate: several taxes share one. The codes come from the company's country pack.
--ref, so that nothing is created twice. The same reference a second time returns what the first call created and writes nothing. A<document>is its id, its number, or the--refit was created under.--dry-runonpostruns the posting for real and rolls it back, so the entry shown is the one that would be written, and a refusal is the real one.--stdintakes one JSON object with the fields of the matching MCP tool, for a program.
Taking over books: ekwo import
To try Ekwo on your own books, bring them. One reader per source, and nothing is posted while an account has no answer:
ekwo import fec FEC20251231.txt --dry-run --open-years --save-mapping map.json
$EDITOR map.json # answer what is null, check what is suggested
ekwo import fec FEC20251231.txt --mapping map.json --open-years
ekwo import camt.053 statement.xml # or coda, cfonb120: pending lines for `ekwo match`
| Source | What it reads |
|---|---|
trial-balance |
A trial balance as CSV, which becomes the opening entry of the year --opening-date starts |
fec |
A French fichier des écritures comptables |
journal-items |
The lines of every entry exported as CSV from a ledger kept as a table of lines, with its chart of accounts and partners |
journal-report |
A journal report or general ledger detail saved as CSV, with its chart and contacts |
transaction-journal |
A transaction journal saved as CSV, with its list of accounts |
xaf |
An XML Audit File Financial, version 3.2 or 4.0 |
camt.053, coda, cfonb120 |
A bank statement: its lines, pending, ready for ekwo match |
ekwo import --help also lists the exports of other ledgers by the software
they come from, and docs/compatibility.md
gives each one's export and official page.
- The correspondence is yours. Every account and journal of the old books
needs an answer in the company's chart. Ekwo proposes one for each —
exactonly for the same code, otherwisesuggestedwith its reason — and--save-mappingwrites them as JSON for you to check.--mappinggives it back;--accept-suggestionstakes every suggestion once you have read them. --dry-runis the real import, taken back, so the numbers shown are the ones the entries would take and a refusal is the real one.- Whole or not at all. The import is one transaction: one refusal and nothing stays. The same files twice are refused, saying when they were imported and what that import wrote.
- No tax on history. An imported line feeds the ledger, the balances and the statements, not the boxes of a declaration: a period kept elsewhere was declared from where it was kept.
The MCP server offers the same as import_books, and import_bank_statement
for a statement. docs/import.md is the long form.
ekwo module
A module is a Postgres schema beside the core — fixed assets, budgets,
corporate income tax, electronic invoicing. Its migrations travel with this
package, and ekwo migrate applies them by default.
ekwo module list # what this release carries, and what the database holds
ekwo module migrate [<code>] # the migrations, and the country seeds they need
ekwo module enable assets --company "…" # turn it on for one company
ekwo module disable assets --company "…" # turn it off; nothing it wrote is deleted
PostgREST serves a schema other than public only once the project lists it
under its exposed schemas — a setting of the API that no database connection
can change — so ekwo module enable prints the line to add (Supabase
dashboard → Project Settings → API). Before supabase db push, run
ekwo migrate --no-modules.
ekwo company
ekwo company new "My Company" --country <cc>
ekwo company list # name, country, currency, language, chart, pack, members
ekwo company show --json # the company in use: years, lock dates, journals, accounts by role
ekwo company export "My Company" --out ./my-company # manifest.json + one JSON Lines file per table
ekwo company import ./my-company --owner <user id> # whole, or not at all
A company leaves with its books. export runs as a member holding
company.export, under row level security, and writes an archive anybody can
read. import takes it into another installation in one transaction, or
refuses; members do not travel, --owner names the first one. Attached files
live in storage and are listed in the manifest to copy. The format is in
docs/company-archive.md.
ekwo doctor
What a healthy installation is true of. It reads and reports; it never repairs.
ekwo doctor --db-url "$URL" # readable
ekwo doctor --db-url "$URL" --json # the whole report
It compares the database with an inventory of everything this release defines — tables, columns, views, functions, policies, triggers, types, and the privileges each role holds — shipped inside this package and generated from the migrations. A missing object, a missing or extra policy, a changed column type and a privilege the anonymous role should not hold are problems; your own extra tables are reported as information. It also checks row level security on every table, pending migrations, memberships of deleted users, companies without a bank account, and posted entries that do not balance.
Two checks say where the socle lives — its own schema ekwo since 0.23.0, public
on a database that has not migrated yet (decision 0090):
socle_schema (ekwo or public, found by the marker ekwo_schema_version())
and ekwo_exposed (whether the Data API serves ekwo, read from
pgrst.db_schemas where the database carries it; otherwise the line to set).
ekwo migrate prints the same line: Project Settings → Data API → Exposed
schemas, add ekwo.
Exit code 0 when there is no problem, 1 otherwise — usable as a deployment
gate.
ekwo pack, in a checkout
A country is data under packs/<cc>/; the compiler turns it into the seed
ekwo init applies. These commands run in a clone of the repository:
ekwo pack list # the packs this checkout carries, and their review status
ekwo pack describe <cc> # everything one pack says; --json for the whole object
ekwo pack build <cc> # write its seed, and the lists of packs in the docs
ekwo pack check --all # exit 1 if a committed seed or list is not the output of the packs
pack describe --json is the same object the site builds each country's page
from. The format and every rule pack check applies are in
docs/packs.md.
Two commands read an installation instead, and need no clone:
ekwo pack status --db-url "$EKWO_DB_URL" # which pack version each company copied
ekwo pack upgrade "My Company" --db-url "…" # move it to the version this installation holds
upgrade applies additions on its own, lists everything else for a person to
read, never removes anything from a company's books, and takes --apply to
accept what it listed.
Flags
Every command takes the connection flags:
| Flag | Meaning |
|---|---|
--db-url <url> |
Postgres connection string. The reliable way. |
--project-ref <ref> |
With --db-password and --db-region, the session pooler host. |
--db-password <pw> |
Database password. Prompted, masked, when omitted. |
--db-region <region> |
With --project-ref, the session pooler in that region. Both generation prefixes are tried and the one that answers is kept. |
--supabase-url <url> |
https://<ref>.supabase.co. Needed only to create a user. |
--service-role-key <key> |
Needed only to create a user. Either form Supabase issues: a secret key, sb_secret_…, sent on the apikey header alone because it is not a JWT, or the legacy service_role JWT, sent on apikey and as a Bearer as before. |
--yes, -y |
Never ask a question. Everything must come from flags or the environment. |
Without --db-url or --db-region, nothing is derived and the CLI asks for
the connection string: copy it from the dashboard, Connect → Session pooler.
ekwo init adds:
| Flag | Meaning |
|---|---|
--country <cc> |
Which country pack: its chart of accounts, its journals, its taxes and its declaration. One of the packs the database holds — ekwo pack list names them, and there is no default. |
--chart <code> |
Which chart of accounts, where the country publishes several. Required outside a terminal when it does. |
--org <name> |
Your organisation, written on the instance row. |
--company <name> |
The first company. Defaults to --org. |
--admin-email <address> |
The first administrator, created in your Supabase Auth. |
--admin-password <pw> |
Their password. Omitted, an invite link is generated and printed. |
--admin-user-id <uuid> |
Use an account that already exists, instead of creating one. |
--fiscal-year <year> |
Calendar year of the first financial year. Defaults to this year. |
--fiscal-year-start <date> |
The day that year opens, as YYYY-MM-DD. Needed only where the pack names no usual opening month (defaults.fiscal_year_default). |
--currency <code> |
Currency of the company. Defaults to the pack's defaults.currency. |
--language <xx> |
Language of the books, two letters. Defaults to country_defaults.language_default, which the pack fills. It decides which label of the pack lands on each account; the others are kept in name_i18n. |
--bank-identifier <value> |
Creates the main bank account, wired to the bank journal and its ledger account. What it is written in is the country's: an IBAN, an ABA routing number and an account number ("021000021 123456789"), a sort code and an account number, a BSB, an IFSC, a CLABE… ekwo pack describe <cc> names it, and it is checked. A country whose pack declares none is asked for an account number as the bank wrote it. --iban is the old spelling and is refused where the banks do not use IBANs. Omitted, no bank account is created and ekwo doctor says so. |
--bic <bic> |
Optional, on that account, for a bank that has one. |
--bank-name <name> |
Optional. It also names the account in the books. |
--demo |
Also load the sample company. |
--register |
Register without being asked. --register-email sets the address. |
--registry-url <url> |
Where the registration is announced. |
--no-modules |
Leave the modules out. By default init installs them, as ekwo migrate does — empty schemas until a company enables one. |
--no-company |
Install without a company: schema, every pack, the modules, the first administrator, and no country on the instance row. The flags that only describe a company are refused with it. Then ekwo company new, once per company. |
ekwo company new <name> takes --country, --chart, --language,
--currency, --fiscal-year and --fiscal-year-start as init does, and
--as-user <uuid>: the administrator of the installation it is created as,
who becomes its owner — the only one, when there is exactly one.
Every command takes --json; see what a command answers.
ekwo migrate takes --skip-seeds.
Environment variables
| Variable | Same as |
|---|---|
EKWO_DB_URL |
--db-url. SUPABASE_DB_URL also works. |
EKWO_DB_PASSWORD |
--db-password |
SUPABASE_URL |
--supabase-url |
SUPABASE_SERVICE_ROLE_KEY |
--service-role-key. ekwo init only. |
SUPABASE_ANON_KEY |
--anon-key |
EKWO_EMAIL, EKWO_PASSWORD |
Sign in for one command, writing nothing. ekwo login reads them too. |
EKWO_ACCESS_TOKEN |
The same, with a session token already in hand. It is not renewed. |
EKWO_PROFILE |
--profile |
EKWO_EINVOICE_DIRECTORY |
--to, for ekwo einvoice. The MCP server reads it too. |
EKWO_FISCAL_SYSTEM |
--system, as the JSON itself, for ekwo einvoice fiscalise. The MCP server reads it too. |
EKWO_CONFIG_DIR |
Where profiles and sessions are kept. |
EKWO_REGISTRY_URL |
--registry-url. Default https://api.ekwo.ai/v1/registrations. |
EKWO_NO_REGISTER_INVITE |
Set to 1 to hide the invitation to register that ekwo init and ekwo status show on an installation that is not registered. The MCP server reads it too. |
EKWO_NO_CLOUD_OFFER |
Set to 1 to hide the one line offering a free Ekwo Cloud account. The MCP server reads it too. See An Ekwo Cloud account. |
NO_COLOR |
Plain output. |
See .env.example at the root of the repository.
Secrets
The CLI never writes a password or a key to disk. The database password and
the secret key are read from a flag, an environment variable or a masked
prompt, used, and forgotten; ekwo.json holds the project URL and the schema
version.
The one thing kept is the session of the person who signed in with
ekwo login, in their own configuration directory, readable by them alone,
and refused anywhere inside a repository. ekwo logout removes it.
Outside this repository the CLI has one runtime dependency, the Postgres driver: everything it is handed is a secret, and every dependency is one more thing that could read it.
Registering with Ekwo
At the end of an interactive ekwo init, you are asked:
Register this installation with Ekwo, for security advisories that concern your version and release notes? Everything works the same without it.
The default answer is no, and no is supported forever. Where nobody is asked
(--yes, --json), the end of ekwo init and ekwo status show a short
invitation instead, with the one command:
npx -y ekwo-os@latest register --email <your address>
EKWO_NO_REGISTER_INVITE=1 hides the invitation. Community works
unregistered: nothing reads the registration to decide what you may do.
Registering writes the address and a date on your instance row, and sends exactly six fields:
{
"instance_id": "…",
"organization": "My Organisation",
"country": "<cc>",
"edition": "community",
"schema_version": "0.24.0",
"contact_email": "you@example.com"
}
No ledger data, no user list, no connection string. instance_id is generated
by your own database and is not a licence key. A failed announcement is a soft
message, not a failed install: ekwo register retries it. ekwo unregister
clears the local fields.
An Ekwo Cloud account (optional)
Everything above works without an account, and always will. A free account on
Ekwo Cloud — an address and a
six-digit code, no password to invent — adds the web application on the same
books, a nightly backup of every company you link, and one connector URL,
https://mcp.ekwo.ai/mcp, for your AI agent, approved in your browser, instead
of a key and a password in a file.
The command line says so in one line, at two moments: at the end of an
ekwo init that succeeded, and after a first ekwo company export. Each
moment once for you: it is remembered in settings.json, in your
configuration directory beside your profiles, and nothing is sent to Ekwo to
show it or to remember it. The line is never shown under --json, when the
output is not a terminal, or in CI (CI set). EKWO_NO_CLOUD_OFFER=1 hides
it, and so does "cloud_offer": false in that settings.json, for good. The
MCP server carries the same words in its instructions; see
its README.
Testing it against a real project
The test suite runs against Postgres compiled to WebAssembly. To exercise the network, PostgREST and Supabase Auth as well, run the end-to-end script from a clone, against an empty project you can throw away:
npm run e2e:supabase
It installs, migrates, signs in, books, files a declaration and closes the
year, and prints a pass/fail table with how long each step took.
docs/releasing.md lists what it reads.
Licence
AGPL-3.0-only © Ekwo AI.