# CLI reference

> Install, configure, and use the haszno CLI: global flags, output formats, exit codes, and every command group with examples.

## Install

The `haszno` CLI is a Node.js program (Node.js 20 or later). The package is **not published on npm** (`"private": true` in `package.json`). Use one of the methods below.

### One-step installer (all three tools)

Installs the CLI, MCP server, and skill together. Requires Node.js 20+, `git`, and `npm`.

```bash
curl -fsSL https://raw.githubusercontent.com/haszno-labs/haszno-skill/main/install.sh | bash
```

See [Agents overview](agents.md) for what the installer does step by step.

### Manual install (CLI only)

```bash
npm install -g github:haszno-labs/haszno-cli
```

> [!NOTE]
> `npx haszno-install` and `npm install -g @haszno/cli` do not work — the package is not on npm. The correct install path is `github:haszno-labs/haszno-cli`.

### Run once without installing

```bash
npx -y github:haszno-labs/haszno-cli account ping
```

### Verify installation

```bash
haszno --version
# 0.1.0

haszno account ping
# API: OK elérhető
```

## Configuration

The CLI takes its API address and credentials from the `--api-url` / `--api-key` flags, from environment variables, or from the config file that `haszno login` writes. For an interactive machine, log in once:

```bash
haszno login --api-url https://haszno.com --api-key <your-api-key>
```

For scripts, CI and cron jobs, export the variables instead:

```bash
export HASZNO_API_URL=https://haszno.com
export HASZNO_API_KEY=<your-api-key>
```

> [!NOTE]
> Reading the config file requires a CLI build from 12 November 2026 or later. Older builds only read flags and environment variables; update with the installer or `npx -y github:haszno-labs/haszno-cli`.

### Environment variables

| Variable | Description |
|---|---|
| `HASZNO_API_URL` | API base URL. Default: `https://haszno.com` |
| `HASZNO_API_KEY` | API key, sent as the `x-api-key: <key>` request header |
| `HASZNO_BASIC` | `user:pass` for HTTP Basic auth, sent as `Authorization: Basic …`. Used only when no API key is set. |
| `ARFUTAR_API_URL` | Legacy alias for `HASZNO_API_URL` (silent fallback) |
| `ARFUTAR_API_KEY` | Legacy alias for `HASZNO_API_KEY` (silent fallback) |
| `ARFUTAR_BASIC` | Legacy alias for `HASZNO_BASIC` (silent fallback) |

### Precedence

Highest to lowest:

1. `--api-url` / `--api-key` flags
2. `HASZNO_API_URL`, `HASZNO_API_KEY`, `HASZNO_BASIC`
3. `ARFUTAR_API_URL`, `ARFUTAR_API_KEY`, `ARFUTAR_BASIC` (legacy)
4. `~/.config/haszno/config.json`
5. `~/.config/arfutar/config.json` (legacy, read only if the file above does not exist)
6. `https://haszno.com`, no credentials

An API key is sent as the `x-api-key` header. HTTP Basic credentials are used only when no API key is found; there is no flag for them.

Two rules keep credentials from reaching the wrong server:

- The config file is read only when neither a flag nor the environment supplies credentials. A key from the environment is never sent to a URL that only the config file names.
- Credentials from the config file are sent only to the URL they were saved for (`apiUrl` in the file, or `https://haszno.com` if the file has none). If `--api-url` or `HASZNO_API_URL` points to a different server, pass the key for that server explicitly.

If nothing is set, requests are sent without credentials. That is enough for the public demo endpoints.

### Config file

`haszno login` writes `~/.config/haszno/config.json` (file mode `600`). Both the CLI and the [MCP server](mcp.md#config-file) read it, so one login configures both.

```json
{
  "apiUrl": "https://haszno.com",
  "apiKey": "<your-api-key>"
}
```

A `basicAuth` value (`user:password`) in the file is also honoured. A file that is not valid JSON, or is not a JSON object, is ignored.

### Requests

Every request sends `Content-Type: application/json`, `Accept: application/json` and `User-Agent: haszno-cli/0.1.0`. HTTP `429` and `5xx` responses and network errors are retried, up to 3 attempts in total, with exponential back-off starting at 500 ms. Each attempt times out after 30 seconds.

## Global flags

These flags belong to the root `haszno` command. You can put them before or after the subcommand (`haszno --json products list` and `haszno products list --json` are the same).

| Flag | Description |
|---|---|
| `--json` | Emit machine-readable JSON output |
| `--api-url <url>` | Override `HASZNO_API_URL` for this invocation |
| `--api-key <key>` | Override `HASZNO_API_KEY` for this invocation |
| `-V`, `--version` | Print the CLI version |
| `-h`, `--help` | Show help for the command |

## Output formats

Without `--json`: human-readable text and tables. Labels and messages are in Hungarian.

With `--json`: one JSON document on stdout. List commands print a JSON array, except where a command section below names a wrapper object (`catalogue search`, `ideas list`, `blog latest`). Some commands pretty-print the JSON, others print it on one line; parse it with a JSON parser such as `jq`, not line by line.

Errors are written to stderr as plain text (usually `Hiba: <message>`), also in `--json` mode. The only exception is `haszno ideas`, which writes `{"error": "<message>"}` to stderr in `--json` mode.

## Exit codes

| Code | Meaning |
|---|---|
| `0` | Success. Also used when you cancel a confirmation prompt, and when a write command without `--yes` only prints a preview (exception: `peers invite`, see below). |
| `1` | Runtime error: the API returned an error status (including `401`), a network error persisted after 3 attempts, or the requested item was not found. Usage errors reported by the argument parser (unknown command or option, missing required option) also exit with `1`. |
| `2` | Invalid input detected by the command itself (for example an invalid `--status`, `--days` or ID value). Also: `account settings update` and `feeds import-*` without `--yes` in a non-interactive shell, and `haszno login` without a terminal. |

## Write commands and confirmation

Write commands behave in one of three ways when `--yes` is missing:

| Behaviour without `--yes` | Commands |
|---|---|
| **Preview and exit.** Print what would happen and exit without calling the write endpoint. Exit code `0`, except `peers invite`, which exits with `1`. | `proposals approve`, `proposals reject`, `proposals rollback`, `rules create`, `rules enable`, `rules disable`, `rules update`, `rules delete`, `rules run`, `rules run-all`, `rules trigger`, `rules automation pause`, `rules automation resume`, `import push`, `peers invite`, `peers accept` |
| **Interactive prompt.** Ask `[y/N]` (answer `y`) and proceed only on yes. | `account settings update`, `products update`, `products listing`, `products apply-prices`, `products describe`, `feeds import-supplier`, `feeds import-market`, `ideas fill-keywords` |
| **Interactive prompt `[i/n]`.** Answer `i` (igen) to proceed. Without a terminal the answer is treated as no. | `orders ship`, `orders demo` |

`haszno import add` writes immediately; it has no confirmation and no `--yes` flag.

In scripts and agents, always pass `--yes` to a write command once the user has confirmed the action. Without a terminal, `account settings update` and `feeds import-*` exit with code `2`, and `ideas fill-keywords --json` refuses to run.

---

## haszno login

Save the API URL and key to `~/.config/haszno/config.json` for the CLI and the MCP server. `haszno configure` does the same.

**Synopsis:** `haszno login [--api-url <url>] [--api-key <key>]`

With flags it saves the given values without prompting, which also works in scripts. Without flags it prompts for the API URL (press Enter to keep the default, `https://haszno.com`) and the API key; without flags and without a terminal it exits with code `2`. Existing values in the file are kept unless you replace them, and the file is written with mode `600`.

```bash
haszno login --api-url https://haszno.com --api-key <your-api-key>
# Konfigurációs fájl elmentve: /home/you/.config/haszno/config.json
```

> [!WARNING]
> A key passed as a flag can end up in your shell history. On a shared machine use the interactive prompt.

---

## haszno account

| Subcommand | Mode | Description |
|---|---|---|
| `ping` | **Read-only** | Check API connectivity (`GET /api/health`) |
| `whoami` | **Read-only** | API status plus the current pricing settings |
| `stats` | **Read-only** | Dashboard summary: products, losing, overMarket, toFix, orders |
| `impact [--days <n>]` | **Read-only** | Profit impact (Haszon-mérő) for the last N days (default 30) |
| `suppliers` | **Read-only** | List your own suppliers |
| `settings` | **Read-only** | Show the current pricing settings |
| `settings update [--undercut-pct <n>] [--min-margin-pct <n>] [--vat-pct <n>] [--yes]` | **Write** | Update pricing settings |
| `version` | **Read-only** | Print the CLI version and whether the API is reachable |

### haszno account ping

Check API connectivity. `/api/health` needs no credentials. Exits with `1` if the API does not answer `ok`.

**Synopsis:** `haszno account ping`

```bash
haszno account ping
```

### haszno account stats

Print the dashboard summary.

**Synopsis:** `haszno account stats [--json]`

```bash
haszno account stats --json
```

**JSON output:** `{ products, losing, overMarket, toFix, orders }`

**See also:** `haszno account impact` · MCP: `haszno_account_get_stats`

### haszno account impact

Print the profit impact for the last N days.

**Synopsis:** `haszno account impact [--days <n>] [--json]`

| Flag | Type | Default | Description |
|---|---|---|---|
| `--days` | int | 30 | Number of days to look back (positive integer) |

```bash
haszno account impact --days 7 --json
```

**JSON output:** includes `days`, `extraProfit`, `baseline`, `pctGain`, `orders`, `autoForwardedOrders`

### haszno account settings update

Update pricing settings. **Write** — prompts `[y/N]` unless you pass `--yes`. Give at least one of the three flags; the others keep their current values.

**Synopsis:** `haszno account settings update [--undercut-pct <n>] [--min-margin-pct <n>] [--vat-pct <n>] [--yes]`

| Flag | Type | Description |
|---|---|---|
| `--undercut-pct` | number | How many percent to price below the market minimum |
| `--min-margin-pct` | number | Minimum margin percentage |
| `--vat-pct` | number | VAT rate for gross/net conversion |
| `--yes` | bool | Skip the confirmation prompt |

```bash
haszno account settings update --undercut-pct 1 --min-margin-pct 10 --vat-pct 27 --yes
```

---

## haszno products

Your own shop's products and pricing.

| Subcommand | Mode | Description |
|---|---|---|
| `list [--all] [--losing] [--over-market] [--cats <names>]` | **Read-only** | List products with margin and price status |
| `show <id>` | **Read-only** | Show one product in full detail |
| `update <id> [--price <gross>] [--market-min <gross>] [--market-shop <name>] [--yes]` | **Write** | Update market data or set your own selling price |
| `listing <id> --on\|--off [--yes]` | **Write** | List or unlist a product |
| `apply-prices [--ids <id,...>] [--yes]` | **Write** | Apply the recommended prices |
| `describe <id> [--yes]` | **Write** | Generate an AI product description |
| `stats` | **Read-only** | Same dashboard summary as `account stats` |
| `impact [--days <n>]` | **Read-only** | Same profit impact as `account impact`; without `--days` the API default applies |

The write subcommands prompt `[y/N]` unless you pass `--yes`.

### haszno products list

Fetch your products and print them.

**Synopsis:** `haszno products list [--all] [--losing] [--over-market] [--cats <names>] [--json]`

| Flag | Type | Default | Description |
|---|---|---|---|
| `--all` | bool | false | Include unlisted products |
| `--losing` | bool | false | Only products with a negative current margin |
| `--over-market` | bool | false | Only products priced above the market minimum |
| `--cats` | string | (all) | Comma-separated **supplier name** fragments (case-insensitive), for example `auto,kert`. It filters on the supplier name, not on category keys. |

```bash
# Human-readable table
haszno products list

# JSON output — all fields
haszno products list --json

# Include unlisted, filter with jq
haszno products list --all --json | jq '.[] | select(.current.marginPct < 10)'
```

**JSON fields per product (selection):** `id`, `sku`, `name`, `supplierName`, `listed`, `priceGross`, `costNet`, `stock`, `current.marginPct`, `recommended.priceGross`, `marketMinGross`, `marketShop`

> [!NOTE]
> The MCP server does not have `haszno_products_*` tools yet (**Planned**). Use the CLI for product operations until they ship.

### haszno products update

**Synopsis:** `haszno products update <id> [--price <gross>] [--market-min <gross>] [--market-shop <name>] [--yes]`

| Flag | Type | Description |
|---|---|---|
| `--price` | int | Your own selling price (gross HUF) |
| `--market-min` | int | Market minimum price (gross HUF); `null` clears it |
| `--market-shop` | string | Name of the market source shop; `null` clears it |
| `--yes` | bool | Skip the confirmation prompt |

Give at least one of `--price`, `--market-min`, `--market-shop`; otherwise the command exits with `2`.

### haszno products apply-prices

Apply the engine's recommended prices. **Write** — prompts `[y/N]` unless you pass `--yes`.

**Synopsis:** `haszno products apply-prices [--ids <id,...>] [--yes] [--json]`

| Flag | Type | Default | Description |
|---|---|---|---|
| `--ids` | string | (all) | Comma-separated product IDs. Omit to apply to all products. |
| `--yes` | bool | false | Skip the confirmation prompt |

```bash
# Prompts before changing anything
haszno products apply-prices --ids 1,2,3

# After the user has confirmed
haszno products apply-prices --ids 1,2,3 --yes
```

**JSON output:** `{ changed, total }`

---

## haszno catalogue

Search the partner catalogue.

| Subcommand | Mode | Description |
|---|---|---|
| `search [flags]` | **Read-only** | Search the partner catalogue (`GET /api/catalogue/products`) |

> [!NOTE]
> CLI 0.1.0 has no `haszno catalog` command group. To list network suppliers or browse one supplier's catalogue, use the MCP tools `haszno_catalog_list_suppliers`, `haszno_catalog_supplier_products` and `haszno_catalog_supplier_view`, or `haszno catalogue search --partner <id>`.

### haszno catalogue search

**Synopsis:** `haszno catalogue search [--q <text>] [--cats <keys>] [--partner <id>] [--min-margin <pct>] [--max-price <ft>] [--min-price <ft>] [--in-stock] [--country <code>] [--sort <key>] [--page <n>] [--page-size <n>] [--json]`

| Flag | Type | Default | Description |
|---|---|---|---|
| `--q` | string | (all) | Free-text search (name or category) |
| `--cats` | string | (all) | Comma-separated category keys |
| `--partner` | int | (all) | Filter by partner (supplier) ID |
| `--min-margin` | number | — | Minimum margin % |
| `--max-price` | int | — | Maximum recommended retail price (gross HUF) |
| `--min-price` | int | — | Minimum recommended retail price (gross HUF) |
| `--in-stock` | bool | false | In-stock items only |
| `--country` | string | (all) | Supplier country code, for example `HU`, `DE` |
| `--sort` | string | `name_asc` | `margin_desc`, `price_asc`, `price_desc`, `name_asc` |
| `--page` | int | 1 | Page number (1-based) |
| `--page-size` | int | 24 | Results per page (the API caps it at 100) |

**JSON output:** `{ items, total, facets }`. Each item has `id` (the catalogue product ID that `haszno import add` expects), `name`, `partnerId`, `partnerName`, `categoryKey`, `wholesaleNet`, `rrpGross`, `marginPct`, `stock`, `inImportList`, `inShop`.

```bash
# Find in-stock garden items with at least 20% margin
haszno catalogue search --cats kert --min-margin 20 --in-stock --json \
  | jq '.items[] | {id, name, marginPct, stock}'
```

---

## haszno import

Import list: stage catalogue products, then create them in your shop or push them to a connected webshop.

| Subcommand | Mode | Description |
|---|---|---|
| `list [--status <s>] [--partner <id>] [--q <text>]` | **Read-only** | List import list items |
| `add <id> [<id>...]` | **Write** | Add catalogue products (catalogue product IDs) to the import list. Runs immediately, no confirmation. |
| `push --ids <ids> [--target <connectionId>] [--yes]` | **Write** | Push import list items (import list item IDs, required) |

**Status values for `--status`:** `varakozik`, `feltoltve`, `elvetve` These are the values the API filters on; the CLI's built-in help text lists `varakozik`, `kuldve`, `kesz`, `hiba`, which are per-target push states, not item states.

`push` without `--target` calls `POST /api/import-list/push`, which creates the products in your Haszno shop and returns `{ pushed }`. With `--target <connectionId>` (from `haszno connections list`) it calls `POST /api/connect/stores/<connectionId>/push`, which sends them to that connected webshop and returns `{ pushed, failed, errors }`.

> [!WARNING]
> `haszno import push` without `--yes` prints a preview only and does not push. Pass `--yes` to actually push.

```bash
# Stage two catalogue products (catalogue product IDs from `catalogue search`)
haszno import add 101 102

# Look up the import list item IDs
haszno import list --status varakozik --json

# Preview: what push would do
haszno import push --ids 5,6

# Create the items in your Haszno shop
haszno import push --ids 5,6 --yes

# Push to a specific connected webshop
haszno import push --ids 5,6 --target 1 --yes
```

---

## haszno connections

| Subcommand | Mode | Description |
|---|---|---|
| `list [--status <s>]` | **Read-only** | List connected webshops |

**Status values:** `uj`, `domain_ok`, `kapcsolva`, `hiba`

```bash
haszno connections list --status kapcsolva --json
```

---

## haszno proposals

| Subcommand | Mode | Description |
|---|---|---|
| `list [-s, --status <s>]` | **Read-only** | List proposals (default status: `pending`) |
| `show <id>` | **Read-only** | Show one proposal |
| `approve [--ids <id,...>] [--yes]` | **Write** | Approve proposals (all pending if no `--ids`). Returns a `batchId`. |
| `reject --ids <id,...> [--yes]` | **Write** | Reject specific proposals |
| `rollback --batch <batchId> [--yes]` | **Write** | Roll back an approved batch |

**Status values for `--status`:** `pending`, `approved`, `rejected`, `rolled_back`

Without `--yes`, `approve`, `reject` and `rollback` print a `[DRY-RUN]` line and exit with `0`.

> [!WARNING]
> Note the `batchId` that `approve` returns: `rollback` needs it. Approved proposals also carry it in `haszno proposals list --status approved --json`.

```bash
# List pending proposals
haszno proposals list --status pending --json

# Preview the approval
haszno proposals approve --ids 10,11,12

# Approve after the user has confirmed
haszno proposals approve --ids 10,11,12 --yes
# Jóváhagyva: 3 javaslat (batch: <batchId>)

# Roll back if needed
haszno proposals rollback --batch <batchId> --yes
```

---

## haszno rules

Automation rules. Every write subcommand prints a `[DRY-RUN]` preview and exits with `0` unless you pass `--yes`.

| Subcommand | Mode | Description |
|---|---|---|
| `templates` | **Read-only** | List built-in rule templates |
| `list [--enabled\|--disabled]` | **Read-only** | List rules |
| `runs [--limit <n>]` | **Read-only** | Rule execution log (default limit 50) |
| `automation` | **Read-only** | Show the kill switch state (`{ paused }`) |
| `create --template <key> --name <name> [--disabled] [--yes]` | **Write** | Create a rule from a template (enabled unless `--disabled`) |
| `enable <id> [--yes]` | **Write** | Enable a rule |
| `disable <id> [--yes]` | **Write** | Disable a rule |
| `update <id> [--name <name>] [--trigger <trigger>] [--enable\|--disable] [--yes]` | **Write** | Update a rule's name, trigger or state |
| `delete <id> [--yes]` | **Write** | Delete a rule permanently |
| `run <id> [--yes]` | **Write** | Run a single rule now |
| `run-all [--yes]` | **Write** | Run all manual-trigger rules |
| `trigger <name> [--ids <id,...>] [--yes]` | **Write** | Fire a named trigger, optionally only for the given product IDs |
| `automation pause [--yes]` | **Write** | Pause automation |
| `automation resume [--yes]` | **Write** | Resume automation |

**Template keys:** `keszlet_or`, `arjavaslat`, `felar_savok`, `veszteseg_riasztas`. **Built-in triggers:** `feed_import`, `market_price`, `manual`. The CLI can only create rules from templates; it has no flags for custom conditions or actions.

> [!WARNING]
> Check `haszno rules automation --json` before any bulk write. If `paused` is `true`, do not approve proposals or run rules.

```bash
# Check kill switch
haszno rules automation --json

# Browse templates
haszno rules templates --json

# View execution log
haszno rules runs --limit 10 --json

# Pause automation (without --yes this is only a preview)
haszno rules automation pause --yes
```

---

## haszno orders

| Subcommand | Mode | Description |
|---|---|---|
| `list [--status <s>]` | **Read-only** | List the 50 most recent orders with items and events |
| `show <id>` | **Read-only** | Show one order with items and event log |
| `ship <id> [--yes]` | **Write** | Mark an order as shipped |
| `demo [--yes]` | **Write** | Create a demo order (`POST /api/demo/order`) |

**Status values for `--status`:** `received`, `forwarded`, `shipped`, `error`

`ship` and `demo` prompt `[i/n]` unless you pass `--yes`.

```bash
# Find error-status orders
haszno orders list --status error --json

# Mark shipped
haszno orders ship 42 --yes
```

---

## haszno peers

P2P partner invites for sharing supplier access with a partner shop.

| Subcommand | Mode | Description |
|---|---|---|
| `list` | **Read-only** | List your own invites |
| `invite --supplier <id> --expires <datetime> [--yes]` | **Write** | Create an invite |
| `accept <code> --acceptor <shop> [--yes]` | **Write** | Accept a peer invite |

> [!NOTE]
> Without `--yes`, both `invite` and `accept` print a preview only and do not create or accept the invite. `invite` then exits with code `1`, `accept` with `0`.

```bash
# Preview (no --yes)
haszno peers invite --supplier 7 --expires 2026-12-01T00:00:00Z

# Create invite
haszno peers invite --supplier 7 --expires 2026-12-01T00:00:00Z --yes

# Accept a received invite code
haszno peers accept INVITE-XYZ --acceptor my-shop --yes
```

---

## haszno ideas

Product ideas from your products, ranked by trend score and margin (`GET /api/recommendations`).

| Subcommand | Mode | Description |
|---|---|---|
| `list [--min-score <n>] [--supplier <name>] [--limit <n>]` | **Read-only** | Ideas sorted by trend score, then margin |
| `fill-keywords [--yes]` | **Write** | Fill missing trend keywords with AI |

| Flag (`list`) | Type | Description |
|---|---|---|
| `--min-score` | number | Minimum trend score (0–100) |
| `--supplier` | string | Supplier name fragment (case-insensitive) |
| `--limit` | int | Maximum number of items |

**JSON output (`list`):** `{ anchor, geo, total, filtered, items }`. Each item has `rank`, `id`, `sku`, `name`, `supplier`, `stock`, `costNet`, `suggestedPriceGross`, `marginNet`, `marginPct`, `trendScore`.

```bash
haszno ideas list --limit 10 --json | jq '.items[] | {name, trendScore}'
```

---

## haszno feeds

Feed import and outbound feed URLs.

| Subcommand | Mode | Description |
|---|---|---|
| `import-supplier --supplier <id> --format <csv\|xml> [--file <path>] [--yes]` | **Write** | Import a supplier feed from a file or stdin |
| `import-market [--file <path>] [--yes]` | **Write** | Import market prices from a CSV file or stdin |
| `urls [--fetch]` | **Read-only** | Print the outbound Árukereső and Google Shopping feed URLs |

`--supplier` and `--format` are required for `import-supplier`. Without `--file` the commands read stdin; because stdin is then not a terminal, pass `--yes` as well.

`urls` prints `<base URL>/feed/arukereso.xml` and `<base URL>/feed/google.xml`. With `--fetch` it also downloads them, without sending credentials.

```bash
# Import a CSV supplier feed
haszno feeds import-supplier --supplier 3 --format csv --file ./supplier-feed.csv --yes
# Létrehozva   142
# Frissítve    7
# Összesen     149
```

**JSON output:** `import-supplier` → `{ created, updated, total }`; `import-market` → `{ matched, total }`

---

## haszno blog

| Subcommand | Mode | Description |
|---|---|---|
| `latest [--period <het\|honap>] [--limit <n>]` | **Read-only** | Latest market summary post(s); `--limit` defaults to 1 |

> [!NOTE]
> Blog posts contain aggregated data from 5 or more shops. Individual shop data is never exposed.

**JSON output:** `{ posts, total, … }`

```bash
haszno blog latest --period honap --limit 3 --json
```

---

## See also

- [Agents overview](agents.md) — decision table, one-step installer, safety model
- [MCP server](mcp.md) — full tool reference for Claude Code and MCP clients
- [Authentication](authentication.md) — how to get an API key beyond the demo
