# mercadona-cli, two Go dependencies, no licence, and a default warehouse in Madrid

> This is a command line client for a supermarket's website, built on endpoints the company does not document, and the readme is unusually candid about that. It has two direct Go dependencies. One is a TOML parser. The other is a library for performing TLS handshakes that imitate other implementations, which is how a Go program makes a website believe it is talking to a browser. The install is a curl pipe to a script on the default branch, the module path does not match the repository, and there is no licence file.

**ivorpad/mercadona-cli** — Unofficial, agent-friendly Mercadona shopping CLI (Go) — search products, read prices, build a cart and prepare checkout from the terminal. BYO credentials.

- Repository: https://github.com/ivorpad/mercadona-cli
- Website: https://www.npmjs.com/package/@ivorpad/mercadona
- Stars: 324 · Forks: 30
- Language: Go
- License: not declared
- Published: 2026-09-18 · Updated: 2026-09-18 · Language: en
- Canonical page: https://hysenlabs.com/projects/ivorpad-mercadona-cli

## The module path does not match the repository and the readme says so

The install is a shell script fetched over a pipe:

```bash
curl -fsSL https://raw.githubusercontent.com/ivorpad/mercadona-cli/main/install.sh | sh
```

The module file declares one path and the repository lives at another. The readme notices, and the note is unusually blunt: the standard Go install command is not wired up, and the reason is that the module path does not match the repository URL.

So a tool written in Go, with a module file, cannot be installed with the Go toolchain. The four paths that remain are an npm package that downloads a prebuilt binary for your platform during install, that script, a manual download from the releases page, and building from source.

Three of those four avoid the Go ecosystem entirely, and the one that uses it requires a clone. For a project whose pitch is a single static binary with no runtime dependencies, the install surface is wider than the artefact.

The mismatch is also a supply-chain consideration rather than a cosmetic one. A module path that does not resolve to the repository URL means the two cannot be cross-checked by any tool that assumes they agree, and it is the reason the obvious install command is absent. Note also that the script is fetched from the default branch with no checksum shown, and the version override the readme demonstrates is seven minor versions behind the newest release.

## One of the two dependencies exists to make the handshake look like a browser

There are two direct dependencies. One is a TOML parser, which the readme's configuration example requires.

The other is a library whose purpose is to perform TLS handshakes that imitate the fingerprint of other implementations. That is a deliberate circumvention technique: it makes a Go program present itself to a server as though it were a browser, a particular browser, or a particular operating system's networking stack.

The readme does not mention this library at all. What it says is that the store has no public API, that the tool talks to the same HTTP endpoints the website does, and that you should bring your own credentials.

All of that is true. It is also incomplete in a way that matters. A program that merely called the website's endpoints with a Go HTTP client would be blocked or flagged; the second dependency is what stops that from happening. So the tool is not a plain API client against an undocumented interface, it is a client that impersonates a browser to reach an interface the operator has chosen not to publish.

That is a categorically different thing from wrapping a documented API, and the readme's framing does not distinguish the two.

## There is no licence file and the tool asks for a shopping account

The repository root has eleven entries: a hidden directory for an assistant's configuration, a workflow directory, a gitignore, a release configuration, the readme, a command directory, the module file, a checksum file, an install script, an internal package directory, and an npm packaging directory.

There is no licence file, and no licence is detected.

That is worth weighing against what the tool does. It reads product catalogues anonymously, which is harmless. It also reads a durable credential for a shopping account, writes it to a configuration file in your home directory, and renews it indefinitely without a browser.

The readme is careful about the credential's storage: the file is created with owner-only permissions, the customer identifier is read from a claim inside the token so you never pass it, and secrets are read from the environment or from files and never accepted as command line flags, which is the right choice because flags end up in your shell history and in the process list.

So the tool handles a long-lived account credential carefully and ships with no licence at all. The absence is the more surprising of the two facts, given that the tool is described as unofficial and the company involved is a listed retailer.

## The recommended login reads a session archive and the obvious login is documented as broken

There are four ways to authenticate, and the readme's ordering is a ranking.

The preferred one is a browser session archive. You sign in normally, export the archive from developer tools, and the tool extracts the refresh token from the authentication response inside it. The readme makes a point of the privacy boundary: it reads only authentication responses and the two kinds of header that carry credentials, and the password in the request body is never touched.

The second is a copy of a request as a command line, from which it takes the same three values, with the disadvantage that it yields no refresh token and so cannot renew itself.

The third is writing the token by hand, either with a command or by editing the configuration file, both of which are shown.

The fourth is a login command that takes a username and a password. The readme says it exists and that it will fail without a captcha token that only a browser can produce, and that it does nothing at all for accounts that sign in with a Google identity. The reason is given earlier: password sign-in is protected by a captcha, and accounts using a Google identity have no password to send.

So the command that a reader would try first is documented as non-functional, and the readme says so in a note rather than hiding it. That is good practice. What the readme also lists, immediately afterwards, is a pair of environment variables for a username and a password, which is the credential the archive path exists to avoid handling.

## The credential renews itself indefinitely and the evidence is one word

The authentication scheme is a bearer token, described as a signed token format rather than an opaque string. Two endpoints matter: one for password and captcha sign-in, and one for accounts using a Google identity, which takes the identity token and a postal code instead.

Both return the same three values, and the third is the important one. It is a refresh token, and the readme says it renews the session without a browser, without a captcha, indefinitely, by calling one endpoint with it and receiving a rotated token in return. The sentence ends with a single word asserting that this was verified.

That is the load-bearing claim in the document. Everything about the tool's convenience rests on a credential that never expires, obtained by exporting a browser session, and the evidence offered is one word.

The readme also discloses the shape of that credential's failure: the access token itself lasts about six weeks, and when a request comes back with a specific error the tool refreshes and retries automatically. So the access token is short-lived and the refresh token is indefinite, which is the standard arrangement and is the reason the tool needs a durable secret at all.

One more detail is worth having: a literal alias for the current customer is rejected by the server, which is why the tool reads the identifier from a claim inside the token.

## The default configuration is one specific warehouse in one city

Prices and product identifiers are per warehouse, and the readme says checkout requires the cart's warehouse to match the delivery address. So the warehouse is not a display preference; it changes what you are quoted and whether the basket can be delivered.

There is a command that takes a postal code and resolves it to a warehouse, and then saves that as the default. It needs no login.

The precedence chain is three levels: a command line flag, then the configuration file's defaults section, then a built-in value. That built-in value is a named warehouse in Madrid, and the readme's own example is a Madrid postal code resolving to it. It also notes that warehouses vary within a single city, giving a second postal code that resolves to a different one.

So out of the box the tool quotes you prices from one store in Madrid, and the first thing a user outside that city must do is set a postal code. That is a reasonable default for the project's home market and a poor default for everyone else, and the readme does not flag it as something you need to change yourself.

The example output makes the shape of a result concrete: three search terms resolved to three products, each with an identifier, a brand, a price, and a price per unit of weight or volume.

## The cart cap is enforced before the write and the last command is cut off

There is a spending cap on cart operations, and how it is enforced is the best engineering decision described in the readme.

Adding to the cart takes a product identifier, a quantity, and a maximum spend. Setting a whole basket takes many lines of identifier and quantity and applies them in a single write. Both price the basket first, and the readme says explicitly that the cap refuses before writing, rather than writing and then checking.

That is the right order. A cart is a server-side mutation with a real cost attached when you check out, and a cap that is checked after the write has already changed state. Doing the pricing first means a refused operation leaves nothing behind.

It is also why the many-lines variant exists. One write instead of many is fewer requests against an interface nobody has promised will stay up, and the batch command for search follows the same principle, resolving roughly a hundred terms in a single call.

The weakness in the section is cosmetic. The last command in the checkout sequence creates a checkout and its trailing comment stops after two words, so the one instruction a user reaches at the end of the authentication walkthrough is truncated. The command itself is complete; the sentence explaining what it opens is not.

## Batching everywhere and a rate limit that is never named

The readme opens with an instruction to use a sane request rate, and does not say what that is.

Everything else in the document is organised around reducing the number of requests. Search takes many terms in one call rather than one call per term. Building a basket takes many lines in one write. The batch command's help text states the ceiling directly, roughly a hundred items per call. Adding to a cart accumulates onto the existing quantity, and setting an absolute quantity of zero removes the item, so a corrected basket does not need a clear followed by a rebuild.

Those are all the right moves against an interface with no published limits, and together they suggest the author has a clear sense of what the server tolerates.

Which makes the missing number conspicuous. A user scripting against this tool has no way to know what a sane rate is, whether the hundred-item ceiling was measured or chosen, or whether the two are the same number. The readme's output contract is precise by contrast: data on standard output, logs and errors on standard error, exit code one on failure, and flags accepted anywhere after the subcommand rather than only up front. That last detail is exactly the kind of thing a person writing for other programs and agents would bother to state, which makes the absent rate limit look like an oversight rather than a decision.

## Conclusion

Use this if you shop at that supermarket and want to script price checks or build a cart from a terminal, and understand that you are running a client against endpoints the company has not published. Four things to know before you install it. That there is no licence file, so nobody has granted you permission to do anything with it. That the recommended authentication is exporting a browser session archive and letting the tool pull a durable credential out of it, and that credential renews itself indefinitely. That the default configuration points at one specific warehouse, so prices are wrong for you until you set a postal code. And that the install script is fetched from the default branch with no checksum, so pin a tag if the integrity of what you run matters.

## FAQ

### What is mercadona-cli and what can it do?

It is an unofficial command line client for a Spanish supermarket's website. It searches the catalogue, reads product details including price and nutrition where available, walks the category tree, resolves a postal code to the warehouse that serves it, builds a cart with a spending cap enforced before any write, and creates a checkout. It is a single static binary with no runtime dependencies and offers structured output for programmatic use.

### How do I install mercadona-cli?

Four ways: an npm package that downloads a prebuilt binary for your platform during install, a shell script fetched over a pipe, a manual download from the releases page, or building from source. The standard Go install command does not work, and the readme explains why: the module path does not match the repository URL. The script can be told which version to fetch and where to install it, defaulting to a system binary directory and falling back to one under the home directory.

### How does mercadona-cli authenticate, and what credential does it store?

The recommended method is to sign in normally in a browser, export the session archive from developer tools, and let the tool pull a refresh token out of the authentication response. The readme states it reads only authentication responses and credential headers and never touches the password in the request body. The token is written to a configuration file with owner-only permissions, renews itself without a browser indefinitely, and triggers an automatic refresh and retry when the shorter-lived access token is rejected.

### Is mercadona-cli licensed?

The repository has no licence file and no licence is detected. The root holds eleven entries, none of which is a licence. The tool is described as unofficial and works against endpoints the company does not publish, and it asks for a durable credential for a real shopping account, which makes the absence of a licence the most surprising thing about the repository.

## Sources

- [Issues](https://github.com/ivorpad/mercadona-cli/issues)
- [ivorpad/mercadona-cli on GitHub](https://github.com/ivorpad/mercadona-cli)
- [Project website](https://www.npmjs.com/package/@ivorpad/mercadona)
- [README](https://github.com/ivorpad/mercadona-cli/blob/main/README.md)
- [Releases](https://github.com/ivorpad/mercadona-cli/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ivorpad-mercadona-cli
