# Changelog (/en/docs/b24/changelog)

Notable changes to `@wirecat/bitrix24-cli`. One section per version, newest first.

## 0.14.0 — 12.11.2026 [#0140--12112026]

### Added [#added]

- **`b24 guide` lists scenarios for common tasks**, each as commands in order with why: `first-sync` (the first sync
  and the pipeline board), `keep-in-sync` (a timer and search) and `safe-agent-writes` (an agent over MCP whose writes
  stay yours). `b24 guide <name> --json` gives one as data.
- **[MCP](/llms.mdx/docs/b24/mcp/content.md) and [Permissions](/llms.mdx/docs/b24/permissions/content.md) pages.** How to add `b24` to an agent, what the
  agent sees and what it is never offered; each level, the built-in levels, the keys one command checks, and the
  checks no level lifts.
- **Sign-in through a local application.** `b24 <profile> session start --portal <host> --client-id <id>` signs in
  through a local application you create in the portal, in the browser, and acts with the rights of the employee who
  signs in. The application key is asked without showing it, or read from stdin or `B24_BITRIX24_CLIENT_SECRET`. The
  sign-in returns through `https://wirecat.dev/webhooks/apps/b24-callback`, or the address you paste. Tokens are renewed
  by themselves. `session status` and `session list` show the kind of sign-in, and `session end` forgets either. The
  incoming webhook stays the quick way; see [sign in](/llms.mdx/docs/b24/usage/content.md#with-a-local-application).

### Fixed [#fixed]

- **The quick start copies deals.** The [README](https://github.com/WireCatLabs/bitrix24-cli/blob/v0.14.0/README.md) now chooses whose deals and leads to copy before the
  first `sync`, which copies nothing until then. It no longer says an administrator copies the whole CRM, says that
  over MCP a delete set to `ask` runs, and names `wcat-b24` for when another package's `b24` command is found first.
  [Troubleshooting](/llms.mdx/docs/b24/troubleshooting/content.md) has the "no CRM user is chosen" error.
- **Over MCP, `ask` does not stop a write.** The [README](https://github.com/WireCatLabs/bitrix24-cli/blob/v0.14.0/README.md), [security](/llms.mdx/docs/b24/security/content.md) and the first
  screen told owners to set `deals.move` to `ask` against the portal's robots, which stops a move only at a terminal:
  over MCP nobody answers, so the write goes ahead. They now say to set `readonly` or `deny`, or to turn on approval
  in the agent's client.
- **The agent skill covers what an agent needs.** `b24 skill show` now has exit codes and error reasons, MCP and the
  `ask` trap, the first setup from the webhook to the first sync, the boundaries of a write, and the traps.
- The examples use the profile `work` throughout, in place of `personal`.
- **`b24 commands` lists the CRM by section.** Deals, leads, contacts and companies split into Read and Write,
  pipelines, stages, fields, products, users and `api` are under Read, `sync` under Automate and `session` under
  Set up; before, most of `b24` was under Other.
- **The two local state files are written atomically and stay private.** The list of your own companies and the day's
  count of other people's clients shown now go through a temporary file and a rename, as the settings file does.
- **A daily `sync` no longer reads every client again.** A round of changes read every contact and company named on
  the chosen users' deals and leads, whether or not anything changed: on a portal with 50,000 deals that was about
  300 requests and close to three minutes for a sync with nothing new. It now reads only the clients of the deals and
  leads that changed, and lists the users' deals for their clients once a sync instead of once for contacts and once
  for companies.

## 0.13.0 — 11.11.2026 [#0130--11112026]

### Changed — may break callers [#changed--may-break-callers]

- **Only a Bitrix24 administrator's webhook deletes or clears in the CRM.** For any other webhook, `b24` refuses
  `deals delete`, `leads delete`, an `api call` to a method that deletes (its name ends in `.delete`,
  `.deletefieldvalue` or `.markdeleted`), an update or `.set` that sets a field to nothing (an empty value, `null`,
  `[]`, a `0` company, contact or lead link, or a phone or e-mail marked `DELETE: Y`), and `batch`. The refusal is a
  `permission_error` with reason `employee.delete`, `employee.clear` or `employee.batch`, and no permission level
  lifts it. A create may still leave fields empty.
- **Only a Bitrix24 administrator's webhook unlinks records or merges duplicates.** For any other webhook, `api call`
  refuses a method whose name ends in `.unbind`, such as `crm.timeline.bindings.unbind`, with reason
  `employee.unbind`, and a merge of duplicates, `crm.entity.mergeBatch`, which deletes all but one of the records,
  with reason `employee.merge`. No permission level lifts it, as for deletes. `crm.deal.contact.delete` and
  `crm.deal.contact.items.delete` already counted as deletes.
- **A portal's account is stored as work.** The first `sync`, write or read of a profile stores a new portal's account
  with scope `work`, so work views and meeting preparation read its copy and personal reads leave it out. A portal
  already in the store keeps the scope it has, which is `personal` in every store so far: switch it once with
  `memo groups update bitrix24:<portal> --scope work` (memo 0.131.81 or later). A sync never changes a scope back.

## 0.12.0 — 10.11.2026 [#0120--10112026]

### Added [#added-1]

- `store apply` removes, from a copy made under `scope mine`, the contacts that no deal or lead of the chosen users
  names and that belong to no kept company, and the people they leave empty. Without `--yes` it only counts. A sync
  that narrows the copy, or `sync --rescan`, does this itself, and its answer gives the counts as `contacts_removed`.
- **Built on cli-core 0.631.0** (was 0.630.0). The store moves to version 64 on first open: saving or removing a
  deal's link to a person no longer reads every such link. What changed is in
  [cli-core's changelog](https://github.com/WireCatLabs/cli-core/blob/main/CHANGELOG.md).

### Changed — may break callers [#changed--may-break-callers-1]

- A sync copies only the deals and leads of the users the owner chooses in `crm.bitrix24:<portal>.unassigned`, and
  their clients. No one is chosen by default, the webhook's own user included: until the owner chooses, a sync is
  refused (`configuration_error`, reason `bitrix.scope-users-unchosen`) and names the `config set` to run. Watch
  for: an existing copy fails its next sync until the owner chooses.
- No sync copies the whole CRM any more, an administrator's either: a sync under a stored `scope all` is refused
  (reason `bitrix.scope-all-refused`, was `bitrix.scope-all-admin-only`) and says to set `mine`. Once set, deals and
  leads of users not chosen are marked deleted in the local copy. Find any other client live on the portal with
  `people list` or `organizations list`.
- An employee's webhook may choose themselves, a dismissed user or a non-employee; an administrator's, anyone.

### Fixed [#fixed-1]

- A contact's name no longer carries the `null` or `null #<id>` a portal import left in its name fields
  ("Ярослав Орлов null #907" is now "Ярослав Орлов"). 101,876 of 114,165 contacts on one live portal had it. Watch
  for: a name stored before keeps the leftover until that contact is synced again.
- **Built on cli-core 0.630.0** (was 0.623.0). The store moves to version 63 on first open. A CRM contact no longer
  joins or renames your own person, and two contacts sharing a company's mailbox or phone stay two people. What changed
  in between is in [cli-core's changelog](https://github.com/WireCatLabs/cli-core/blob/main/CHANGELOG.md).

## 0.11.0 — 10.11.2026 [#0110--10112026]

### Changed — may break callers [#changed--may-break-callers-2]

- Under `scope mine` a sync copies only the user's clients: the contacts and companies assigned to them or to an
  unassigned user, and the ones named on the deals and leads it copies. Before, it copied the portal's whole contact and
  company base, 114 165 contacts on one live portal where the user held no deal. The daily deletion pass lists only
  those ids too. Watch for: a copy that already holds the whole base keeps its contacts; companies wait for
  `sync --rescan`.
- Only a Bitrix24 administrator may sync under `scope all`. A department head is refused like an employee
  (`bitrix.scope-all-admin-only`, was `bitrix.scope-all-manager-only`).
- Anyone but a Bitrix24 administrator sees phones and e-mails only of their own clients. `people list` and
  `organizations list` leave them out of the rest; `people show` and `organizations show` of one give them, at most
  100 such contacts and companies a day for the user and their agents together. `api call` refuses contact and
  company reads (`crm.contact.*`, `crm.company.*`, `crm.item.*` of type 3 or 4, requisites, addresses, duplicate
  searches) to anyone but an administrator, whatever the permissions say.
- Only a Bitrix24 administrator may name a working employee in `crm.bitrix24:<portal>.unassigned`; a department head
  is held to dismissed users and non-employees like everyone else.
- **Built on cli-core 0.623.0** (was 0.621.0). The store moves to version 61 on first open (people ties, additive).
  What changed in between is in [cli-core's changelog](https://github.com/WireCatLabs/cli-core/blob/main/CHANGELOG.md).

## 0.10.0 — 09.11.2026 [#0100--09112026]

### Added [#added-2]

- `session start` and `session status --check` answer whether the webhook's user is a `manager` (a Bitrix24
  administrator or the head of a department), beside `admin`.

### Changed — may break callers [#changed--may-break-callers-3]

- Only a manager copies every deal and lead: a sync under `crm.bitrix24:<portal>.scope all` is refused
  (`configuration_error`) unless the webhook's user is a Bitrix24 administrator or the head of a department, checked
  on every sync. Anyone else copies their own deals and leads and the unassigned ones; the refusal says to set
  `scope` to `mine`. Watch for: a webhook without the `department` scope counts a department head as an employee.
- For anyone but a manager, `crm.bitrix24:<portal>.unassigned` counts only users the portal shows as dismissed or as
  not employees (extranet, e-mail, bot), checked at every sync. A working employee in the list is left out with the
  reason in the sync's `issues` (`crm.unassigned-dropped`), and their deals and leads leave the local copy. A manager
  may still list anyone. Watch for: a webhook without the `user` scope counts no one as unassigned for an employee.

## 0.9.0 — 09.11.2026 [#090--09112026]

### Added [#added-3]

- `sync` stores who is who in the local store: a contact is a member of each of its companies with the role
  `contact`, its job title beside it; a deal's and a lead's contacts are on it with the main one marked, and the
  person responsible is its `owner`; employees are members of your own companies, read every sync, and report to
  their department's head; a converted lead points to its deal. These relationships are `reported` until you confirm
  them, end instead of being deleted when the portal stops saying them, and never change one you made. Watch for: the
  webhook needs the `user_basic` and `department` scopes for employees' e-mails and reporting lines; without them the
  sync reads the rest.
- `schedule` installs a timer that runs `sync`, or another `b24` command, on its own. MCP does not offer it.
- A sync copies only the deals and leads of the webhook's own user by default, and those of the users in
  `crm.bitrix24:<portal>.unassigned` (Bitrix24 always names a responsible person, so this is how a website form's or
  an integration's deals count as nobody's). `config set crm.bitrix24:<portal>.scope all` copies every one. The sync
  answer has `scope` (with the webhook's user as `self`), and each kind's `out_of_scope`: a deal given to another user
  leaves the local copy at the next sync. Contacts and companies are copied whole as before.
- Without a `scope` setting, a first `mine` that would mark more than half of the stored deals or leads deleted keeps
  them (`crm.narrowing-held`) until the owner sets `scope`. An agent cannot set `scope`, `self` or `unassigned`.
- One portal's copy belongs to one user: a sync through a webhook of another user than the first sync's is refused
  until `config unset crm.bitrix24:<portal>.self`.

### Changed — may break callers [#changed--may-break-callers-4]

- A deal's and a lead's `people` now include the employee responsible for it, with the role `owner`, beside its
  contacts. Watch for: code that took every entry in `people` for a client's contact.
- `store status` counts your own companies among the organizations and the portal's employees among the people.
- `api call department.get` and `crm.mycompany.basic.list` run without asking, as the other reads `b24` makes do;
  `department.get` is refused under `users.list: deny`, and `crm.mycompany.*` under `organizations.list: deny`.
- A webhook made with only the `crm` and `user` scopes now gets an entry in the sync's `issues` on every sync:
  Bitrix24 refuses it the departments. Add the `department` scope, or expect that entry.
- The first sync after the upgrade marks other users' deals and leads deleted in the local copy: they leave lists and
  search, and their earlier copy stays in the store file. Set `crm.bitrix24:<portal>.scope all` before it to keep them.
- A list answer without its items now fails that kind's sync (`invalid_response`) instead of ending the list there.

### Fixed [#fixed-2]

- `api show department.get` and `api call` named the method's scope `basic`; Bitrix24's is `department`.
- A method that Bitrix24 lets only an installed app call (`WRONG_AUTH_TYPE`) is refused as `permission_error` with
  the reason `webhook.app-only`. Before, `b24` said the webhook was refused and asked you to sign in again, which
  cannot help. Watch for: a script that branched on `authentication_error` for this refusal.
- `people` and `organizations` `create` and `update` declare their answers (`crm_record_created`,
  `crm_record_updated`), so `--json` takes their field names and `commands info` lists them.
- `session start` and troubleshooting name the same webhook scopes as the README: `crm`, `user_basic` and
  `department`. Troubleshooting also asked for `catalog`, which `b24` does not use.
- `api show`, `api list` and a refused call named the scope `basic` for every method outside five name prefixes, such
  as `im.message.add` and `tasks.task.add`. They now name the scope Bitrix24's reference gives (`im`, `task`,
  `calendar`), or `unknown`, and a refusal for an unknown one names no scope.

### Security [#security]

- `b24` sends requests only to the portal in your webhook address and refuses any other host before a byte leaves the
  machine. Its manifest says so (`network`), so WireCat Desktop shows where the data goes.

## 0.8.0 — 08.11.2026 [#080--08112026]

### Added [#added-4]

- The package ships `contracts/wirecat.json`, so WireCat Desktop can read what `b24` does, how to sign in and what
  `sync` copies before it installs it. `b24 --wcat-describe` now answers format 2 with that file's Desktop section
  and its SHA-256.

### Fixed [#fixed-3]

- The first sync of a large portal uses far less memory and takes a third less time: on synthetic data with 20,000
  deals, its peak falls from 3.7 GB to 0.55 GB.
- `session start` asks for the webhook again when nothing arrived. An empty Enter at the hidden prompt, as after a
  Ctrl+V that pasted nothing in a Linux terminal, failed at once with "no webhook address was given". It now says to
  paste with Ctrl+Shift+V (Cmd+V on macOS) and asks again, up to three times.
- A crash while `b24` starts, or one an awaited step throws, exits 1 with b24's crash error (`command.crashed` as
  JSON when stdout is not a terminal). It printed a raw Node stack.

## 0.7.0 — 08.11.2026 [#070--08112026]

### Added [#added-5]

- The package also installs the command as `wcat-b24`, so `wcat b24` runs it and `wcat --help` lists it.
  `b24 --wcat-describe` prints what the tool is, its version and its docs page.
- `config set owner_languages` names the languages the owner reads.

### Changed — may break callers [#changed--may-break-callers-5]

- A search with no letter or digit is refused (`validation_error`). `""`, a lone `?` or an emoji alone found nothing
  and said nothing. A lone `*` still reads as a wildcard.

### Fixed [#fixed-4]

- The first sync of a large portal is about twice as fast. It no longer slows down with every deal it saves: on
  synthetic data with 10 deal owners, 20,000 deals, 10,000 leads and 20,000 contacts sync in 30 s instead of 61 s.

## 0.6.0 — 08.11.2026 [#060--08112026]

### Added [#added-6]

- `people list --format csv` and `organizations list --format csv` print a page as a CSV table for a spreadsheet, as
  `deals list` and `leads list` now do too. stdout carries the table and nothing else; the way to the next page goes
  to stderr. The columns are the record's own fields, then every custom field of the portal by its label, a money
  field as `<label> amount` and `<label> currency`, a label two fields share told apart by its code. `--json=a,b`
  beside it picks the columns, a custom field as `provider_metadata.<code>`. A cell that would start a spreadsheet
  formula gets a leading `'`. `--jsonl`, a bare `--json` and `--compact` are refused with `output.conflict`, and
  agents do not get the option.
- `pipelines show` is the sales funnel board: each stage gives its deals or leads and their total in each currency,
  and at a terminal prints a table of the stages with the open total, the won, the lost and the win rate. The
  pipeline may be left out when the portal has only one.

### Changed — may break callers [#changed--may-break-callers-6]

- A custom field the portal does not have, named in a list's query or in `--json`, is refused with `param` set to
  `--query` or `--json`, where it said `--field`.
- A command on a profile with no webhook is refused with reason `session.missing`, where it said `webhook.missing`:
  the one "not signed in" every WireCat tool with an API token gives. The webhook is read and kept where it was,
  `B24_TOKEN`, then the keyring entry `token:<profile>` under `bitrix24-cli`, then a private file, now by cli-core's
  shared token store.

## 0.5.0 — 07.11.2026 [#050--07112026]

### Added [#added-7]

- `sync` copies the portal's pipelines, companies, contacts, deals and leads into the local WireCat store: what changed
  since the last sync, or everything with `--rescan`; once a day it also finds what the portal deleted. `store status`
  says how far each kind is. A sync stops before its deadline or `--max-pages`, and the next one goes on from there.
- `deals close <ref> won|lost` and `leads close`, to the first won or lost stage of the record's pipeline, and
  `deals delete` and `leads delete`, which ask first.
- `deals create` and `update` take `--pipeline`, `--stage`, `--amount` with `--currency`, `--close-date` and
  `--owner`; leads take the same but `--close-date`. A stage checks the move key, a won or lost one the close key too.
- `search all` finds stored deals and leads beside the other WireCat tools' messages, notes and events. Under
  `deals.list: deny` it leaves deals out and names them in `missing` and on stderr, and the same for leads; with every
  kind it was asked for denied, it is refused (`search.all-denied`).
- `b24 --help` and a bare `b24` open with what `b24` is for, what to try first, and where to start.

### Changed — may break callers [#changed--may-break-callers-7]

- `deals`, `leads`, `pipelines` and `stages` are cli-core's shared CRM commands, the same in every WireCat CRM tool.
  Their lists and `show` read the local store, so run `sync` first; `--offline` works for them now.
- A deal or lead is named by its ref, `deal:12` or `lead:7`, as a list prints it, not by its Bitrix24 id or title.
- A deal or lead answers `ref`, `pipeline` and `pipeline_name`, `stage` (its name), `status` (`open`, `won` or `lost`),
  `close_date`, `amount` as a decimal string in `currency`, both `null` with no amount, `assignee` as
  `<portal>/user/<id>`, and the portal's own fields in `provider_metadata` by code. `stage_id`, `pipeline_id`,
  `outcome`, `closes_on`, `assigned_to` and `person_ids` are gone from them; contacts are in `people`, the company in
  `organization`.
- Their list query takes `status`, `stage`, `pipeline` and `organization`; the free word, the time windows and the
  portal's own fields are gone from it. `--all` is gone from those lists.
- `deals move <ref> <stage>` takes the stage as an argument, not `--stage`.
- Deal and lead writes have no `--body`. `--field <label or code>=<value>` sets a custom field by the definitions the
  last `sync` stored: a list field takes its option's label or id, a multiple field one `--field` per value, and an
  empty value clears it. A code that names a standard field (`stageId`, `categoryId`, `assignedById`, a lead's
  `STATUS_ID`) is refused, and so is a `--field` with no key before its `=`.
- A custom field's label and a list option's label are matched as cli-core matches names, for every `--field` and
  query: `е` finds `ё`, and a letter typed as two code points finds the same letter typed as one. `people show` and
  `organizations show` compare a name with the portal's matches the same way. A label that matched one field may now
  match two, which is refused with both.
- `fields list <deals|leads|people|organizations>` is cli-core's: it reads the definitions `sync` stored, so run
  `sync` first, and each field names its `untrusted` parts.
- `api.crm.deal.update` (and the other deal and lead `api.*` keys) at `ask` now asks on `deals update`, `move` and
  `close` too, not only on `api call`. `--dry-run` of a deal or lead write names every key it may check: the
  `api.*` key, and the move, close and `crm.assign-others` keys with the option that would make each apply.
- `sync` reads the portal's field definitions of each kind, one more request per kind, so `--max-pages` reaches the
  records later. A webhook without the rights to read them still syncs; any other failure there fails the sync.
- Leads stand in one pipeline named `Leads`, since Bitrix24's leads have none.
- `deals.delete` and `leads.delete` default to `ask`; `api call` that sets a stage checks `deals.close` (or
  `leads.close`) as well as `deals.move`.
- `mcp` and `doctor` are cli-core's shared commands: `mcp` adds `config`, `add` and `check`, and `--profiles` and
  `--permission` on `serve`; `doctor` adds `--online`.
- A `--body` or `api call` method that could be read in more than one way is refused before anything is sent: a
  body that names one field twice in any spelling, an `entityTypeId` that is not a plain number, a `fields` that is
  not an object, a method with any suffix but one `.json`, and a `crm.item` method without `entityTypeId`. Send each
  field once, under one spelling.
- `--operation-id` is gone from every write. On a live portal a create repeated with the same id made a second
  record, so a repeat is never safe: after `outcome_unknown`, `sync` and `show` the record and write it again only if
  it is not there. A deal or lead write given `--operation-id` is refused before anything is sent.

### Fixed [#fixed-5]

- A `sync` after the first one works. It asked the portal for what changed with a time the portal refuses, so
  every kind failed and nothing new was copied. Times in a filter now go in whole seconds with an offset, which also
  fixes `updated_at:` and `created_at:` in `people list` and `organizations list`.
- A write answered with a server or gateway failure (500, 502, 504) is `outcome_unknown`, not `provider_unavailable`:
  the portal may have made it. A write a cancel cuts is `outcome_unknown` too, never `cancelled`.
- Stages are read page by page: a directory past REST 2's first 50 rows lost its later stages.
- An id or owner that is not a number is refused before anything is sent; it went out empty before.
- `--body` keeps integers past 2^53 as typed; `api call --body -` reads the body from stdin.
- A request that gets no answer is cut after 10 seconds, not 30, so a read can be tried again before a one-shot
  command's own 30 s run out.
- `show` for people and organizations fails when the portal's field labels fail for a reason other than missing
  rights, instead of showing the record without its labels.

### Security [#security-1]

- Permission checks on `api call` and on `people` and `organizations` writes read a body exactly as it is sent, so
  the key they check is the key the write needs.
- `api call` reads check the same keys as the commands that read those records, as its writes already did.

## 0.4.0 — 06.11.2026 [#040--06112026]

### Added [#added-8]

- `create` and `update` for deals, leads, people and organizations take `--field "<label or code>=<value>"`, repeated
  for more fields or for each value of a multiple field. The label is looked up on the portal; two fields with that
  label are refused with their codes. A list field takes its option's label or id. `--field` wins over `--body` for the
  same field, and `--title`, `--name`, `--first-name` and `--last-name` win over both. Other values go to the portal as
  typed. `--dry-run` does not look the names up.
- `list` for deals, leads, people and organizations filters by the portal's own fields: `<code or label>:<value>`,
  for the fields the portal can filter by; a list field takes its option's label or id. A label with spaces is
  written `lead_source:web` or `Lead\ source:web`.
- `list --json=…,provider_metadata.<code>` adds that custom field to each item, as `show` gives it;
  `--json=…,provider_metadata` adds every one. Only codes the portal lists are asked for.
- `fields list <deals|leads|people|organizations>` lists the portal's custom fields of that kind: code, label, type,
  whether it holds several values, whether a list can filter by it, and a list field's options with their ids.

### Changed — may break callers [#changed--may-break-callers-8]

- `show` gives a list field's value by its option's label, not its id; the id moves to `option_id` beside it. A field
  that holds several options gives both as arrays.
- `api call` works from an allow list. The CRM, catalog and user reads and the four CRM types' writes are allowed as
  before. Any other method asks, so it needs `--yes` without a terminal, and the families that message people, publish
  or send data out are denied: `imconnector`, `log.blogpost`, public file links and `event.bind` join `im`,
  `imopenlines` and the rest. `api show` names each method's level.

### Fixed [#fixed-6]

- A `deny` in a profile stops a read command again. `b24` handed cli-core only `resolveSettings`, so cli-core never
  saw b24's permission table: `deals.list: deny` did not stop `deals list`, over MCP too, and with any b24 key in the
  config every `--dry-run` failed with "names no command".
- `api.crm.<type>.add` and `api.crm.<type>.update` now hold on `deals`, `leads`, `people` and `organizations`
  `create`, `update` and `move`, as the command keys already held on `api call`. `--dry-run` names them beside the
  command's own key.
- `api call --dry-run` names every key the call checks with its level, such as `api.crm.deal.delete` at `ask`, instead
  of "no key, allowed".
- `api call crm.item.add|update|delete` with `entityTypeId` 1 to 4 checks the lead, deal, contact or company command
  key, as `crm.deal.update` already did: `deals.delete: deny` now stops `crm.item.delete` for a deal.
- Every method whose name ends in `delete` asks, not only the listed ones. A method name in capitals or with a `.json`
  suffix is read as its plain name, so it no longer gets round a key.
- A write that sets a stage checks `deals.move` or `leads.move`: `update` and `create` with `stageId` in `--body`, over
  MCP too, and `api call` with `STAGE_ID` or a lead's `STATUS_ID`. `ASSIGNED_BY_ID` checks `crm.assign-others` like
  `assignedById`.
- A write is sent once. A REST 3.0 write that got no answer was sent up to three more times with the same
  `Idempotency-Key`, which is not yet checked on a live portal; it is now `outcome_unknown` at once, and its message
  names `--operation-id` once.
- `pipeline_id:0`, the default pipeline, is accepted by `deals list` and `stages list`.
- The allow list keeps the denied families' reads refused, as before it: `crm.activity.list`, `im.*`, `tasks.*` and
  the rest are `deny`, and `api show` says so.
- The MCP server applies b24's permission table (WC-PERM-23): a command the profile denies is listed by tools search
  as unavailable, not offered as available.

## 0.3.0 — 06.11.2026 [#030--06112026]

### Changed — may break callers [#changed--may-break-callers-9]

- `show` for deals, leads, people and organizations gives each custom field its title on the portal:
  `provider_metadata` maps the field's code to `{ "label", "value" }`. `label` is `null` when the portal has no title
  for it or does not answer the request for titles; `show` still answers then.

## 0.2.0 — 06.11.2026 [#020--06112026]

### Fixed [#fixed-7]

- Commands that call the portal now run inside cli-core's run: each answer's `meta.run_id` names a run `runs show`
  finds, with the portal requests in it, `meta.duration_ms` measures the command, and `--timeout` stops a stuck
  command. An MCP agent's permission overrides now reach these commands too.
- `show` for deals, leads, people and organizations no longer lists the portal's custom fields that hold no value:
  `provider_metadata` leaves out `null`, `""` and `[]`, and is left out when nothing is left.

## 0.1.0 — 06.11.2026 [#010--06112026]

### Fixed [#fixed-8]

- An error reason that a newer cli-core also defines no longer makes every failing command crash instead of printing
  its error.

### Added [#added-9]

- Started from `@wirecat/cli-template` 0.14.0.
- The client for Bitrix24's REST API over an incoming webhook: REST 3.0 for deals, leads, contacts and companies,
  REST 2 for the rest; the portal's rate limits paced and waited out; a write repeated only with an
  `Idempotency-Key`; the webhook's secret kept out of every error and run record.
- `session start | status | end` per profile and `session list`: the webhook address read hidden or from stdin,
  checked against the portal, kept in the keyring under `bitrix24-cli`; `session status --check` says whose rights
  the webhook carries.
- `list` and `show` for `deals`, `leads`, `people` (contacts), `organizations` (companies), `pipelines`, `stages`
  (with each stage's outcome), `products` and `users`, live from the portal. A list takes a query that becomes the
  portal's own filter; a free word in it matches part of a title or name. A name two records share is refused with
  both as candidates.
- `create` and `update` for `deals`, `leads`, `people` and `organizations`, and `move` for deals and leads: each
  write answers its `operation_id`, which Bitrix24 receives as the `Idempotency-Key`, so `--operation-id` repeats a
  write safely; `--dry-run` previews; assigning a record to another employee is denied until allowed
  (`crm.assign-others`).
- `api list`, `api show <method>` and `api call <method> --body <json>`: any Bitrix24 method, on the REST version it
  lives on, checking `api.<method>` and the command key it does the same as; messaging, automation, tasks and
  `batch` writes are denied until allowed.
