@llcrm/crm-cli
v0.1.1
Published
CLI binary for the LigueLead CRM. Bundles @liguelead/crm-sdk for ops/dev/migration use cases.
Readme
@liguelead/crm-cli
A first-class CLI binary for the LigueLead CRM (NestJS API + Refine
frontend, Phase 61–62.2 codebase). Wraps the official @liguelead/crm-sdk
for ops, dev, and migration use cases. Distributed via npm with OIDC trusted
publishing + provenance attestation.
Killer use case: Bitrix migration. The CLI is the generic, productized version of the one-shot
apps/api/scripts/bitrix-migration/script that shipped Phase 62.2. See §Bitrix migration below.
Quickstart
npm install -g @liguelead/crm-cli
crm auth login # paste API key (ll_live_...)
crm contacts list --limit=10That's it. From there, every command supports --help, every error has a
distinct Unix exit code (see §Exit codes), and every list
command auto-detects TTY-vs-pipe (table for humans, JSON for jq).
Obtain an API key
API keys are issued from the Phase 65 portal (/settings/api-keys).
During the v8.0 dev period (until that ships) you can use the local helper
script apps/api/scripts/create-bitrix-migration-key.ts (same approach used
in Phase 62.2).
Once you have a key:
crm auth login # interactive — masks input via @inquirer/prompts
# OR
export LIGUELEAD_API_KEY='ll_live_xxxx...'
crm contacts list # picks up the env automaticallyAuth precedence
Resolution priority: env > keychain > file (D-03, hard-coded — no override flag):
LIGUELEAD_API_KEYenv var — wins for CI / scripted / ephemeral- OS keychain — macOS Keychain, Windows Credential Manager, Linux Secret
Service (via
@napi-rs/keyring) ~/.liguelead-crm/config.json— chmod 0600 fallback when keychain is unavailable (Linux without DBus, locked keychain, etc.)
Windows — the file fallback skips POSIX chmod (NTFS doesn't honor it). User-home directory ACL inheritance is the implicit defence. Documented partial limitation.
Commands
| Group | Commands |
| ------------ | ------------------------------------------------------------------- |
| contacts | list, get, search, create, update, delete |
| companies | list, get, search, create, update, delete |
| deals | list, get, search, create, update, delete |
| tickets | list, get, search, create, update, delete |
| activities | list, get, search, create, update, delete |
| auth | login, logout |
| config | list, revoke, create (stub), rotate (stub) |
| bulk | import, export, delete |
Use crm <group> <action> --help for full flag documentation. Examples:
crm contacts create [email protected] --firstName=Foo --lastName=Bar
crm tickets update <id> --status=closed
crm deals search "Acme"
crm activities list --entity-type=contact --entity-id=<uuid>TTY auto-detection
Output format is auto-detected (Pitfall #5 — process.stdout.isTTY alone is unreliable on Windows + various CI runners):
| Context | Output |
| ----------------------------- | ------- |
| Interactive TTY | table |
| Piped to another command | json |
| Redirected to file | json |
| Detected CI (30+ runners) | json |
| --json flag | json |
| --ndjson flag (lists only) | ndjson|
Status messages (Fetching..., Found N record(s).) emit on stderr
when format is table; silent on pipe to keep stdout clean for | jq.
Exit codes
| Code | Meaning | Trigger |
| ---: | ------------------------------- | -------------------------------------------- |
| 0 | Success | Caller's responsibility |
| 1 | Generic failure | Any unmapped Error or non-Error throw |
| 2 | Validation / usage error | CrmValidationError, malformed --filter, missing required flag |
| 3 | Auth / permission | CrmAuthError; missing API key |
| 4 | Rate limit | CrmRateLimitError |
| 5 | Server fault | CrmError with code starting 5 |
| 6 | Not found (404) | CrmNotFoundError — id unknown to server (I-1 lock) |
| 127 | Command not found | Shell-level — citty unrecognised subcommand |
Bulk import recipe (CLI-04)
# CSV must have a header row matching entity fields (D-05 — strict).
# external_id column REQUIRED for idempotent re-runs (D-06 + D-08).
echo "external_id,email,firstName,lastName" > contacts.csv
echo "cust-001,[email protected],Alice,Anderson" >> contacts.csv
crm bulk import contacts --csv=contacts.csv
# 1 ok, 0 failed
# Re-run is safe (D-08): same command produces no duplicates.Customise the idempotency column with --idempotency-key=cpf (or any other
column name). The value lands at customFields.<key> per CLAUDE.md regra 5
— never as a top-level column. Failed rows go to <input>.errors.csv
with an appended _error column (D-07 continue-on-error).
Bulk export recipe (CLI-05)
crm bulk export contacts --format=csv --output=dump.csv
# Output starts with UTF-8 BOM bytes (0xEF 0xBB 0xBF) for Excel pt-BR compat.
# Streams the full tenant via SDK autoPaginate (cursor-based, never offset).Customise columns via --columns=id,email,firstName to pick a subset.
Default columns differ per entity — see apps/cli/src/lib/columns.ts.
Bulk delete recipe + filter syntax (CLI-05 + I-3)
# Filter syntax (v1, locked): <field>=<value>
# Multiple --filter flags AND together.
# Malformed (no `=`, multiple `=`, empty key/value) → exit 2.
crm bulk delete tickets \
--filter status=closed \
--filter pipelineId=abc-123 \
--yes-i-know-what-im-doing \
--confirmFriction is intentional. The CLI:
- Parses each
--filterstrictly (rejects malformed, exit 2) - Fetches matches via
*ControllerSearch(max 1000; otherwise refuses) - Prints a 10-record preview to stderr in table form
- Refuses to proceed without
--yes-i-know-what-im-doing(exit 2) - Refuses to proceed without
--confirmafter the preview (exit 2) - Only THEN issues per-record DELETE calls
For complex filters (operators, JSON values, regex), use the SDK directly. v2 may extend the syntax — never break v1.
Killer use case — Bitrix migration
This CLI exists because LigueLead is migrating off Bitrix24 and the
ad-hoc one-shot script in apps/api/scripts/bitrix-migration/ (shipped
Phase 62.2) needed a productized, repeatable form.
The 410 Bitrix custom fields pre-triaged in
.bitrix-analysis/02-triage-custom-fields.md
include a "long tail" of low-frequency fields that are best handled by a
generic CSV bulk importer rather than bespoke ETL code:
# 1. Export from Bitrix to CSV with `external_id` named after Bitrix's deal id.
# 2. (Optional) preserve the original Bitrix field name with --idempotency-key:
crm bulk import deals \
--csv=bitrix-export.csv \
--idempotency-key=external_bitrix_idRe-running the same import is safe (D-08 — same external id resolves to UPDATE, not duplicate). External-id-based identity is canonical per CLAUDE.md regra 20 — UUIDs in this CRM are local to the environment that generated them.
The smoke test in apps/cli/src/__tests__/bitrix-smoke.spec.ts is the
machine-checkable form of this recipe — if you change the README example
above, please update the smoke spec accordingly.
Troubleshooting
- Linux without DBus — keychain is unavailable; the CLI silently falls
back to
~/.liguelead-crm/config.json(chmod 0600). Documented Pitfall #2. - Windows file fallback degraded — POSIX mode bits aren't honored on NTFS; user-home directory ACL inheritance is the implicit defence. Pitfall #4.
- CI runners with PTY — most CI envs auto-detect via
ci-info(30+ runners covered) and force JSON output. If a CI claims to be a TTY, setCI=trueor pass--json. Pitfall #5. - Large CSVs (>100k rows) — the in-memory idempotency index size is
bounded by the number of existing tenant entities, not the input CSV.
When the API later supports server-side
customFieldsfiltering (probeCUSTOM_FIELDS_SEARCH_SUPPORTED=trueflips at build time), per-row lookups become O(1) HTTP calls instead. --filtercomplex queries — v1 supports only<field>=<value>with AND across flags. ForOR/regex/JSON values, use the SDK directly.- Authentication failed — exit 3. Run
crm auth login(or setLIGUELEAD_API_KEY). The CLI never echoes the key value to stderr/stdout.
See also
@liguelead/crm-sdk— the underlying SDK every command wraps@liguelead/crm-mcp— sibling MCP server for AI agent integration (different bin, same SDK)LIGUELEAD_ECOSYSTEM_CONTRACT.md— cross-module integration patterns (event format, response envelope, external_client_id contract)
License
UNLICENSED — internal LigueLead tool.
