npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sizmo

v3.3.0

Published

Unofficial GoHighLevel CLI — read your CRM and make confirm-gated, scope-gated changes from the terminal (build, send, delete — only what your PIT allows). Not affiliated with HighLevel.

Readme

sizmo

CI npm zero deps

Unofficial GoHighLevel CLI — read your CRM and make confirm-gated, scope-gated changes from the terminal: only what your token allows. Not affiliated with HighLevel.

Not affiliated with, endorsed by, or supported by HighLevel. This is an independent open-source tool.

A full terminal interface to one GoHighLevel location — read it, build it, bill it, safely tear it down. Every write is confirm-gated (preview → --confirm → fire) and scope-gated (your PIT's scopes decide what's allowed). It never fabricates a number, and it can't pull money off a card — GoHighLevel exposes no such endpoint.

What it does

| | Commands | |---|---| | Ask (natural language — your own AI agent, or sizmo's opt-in resolver) | ask "who hasn't replied in 3 days" · ask "tag Ana as follow-up" — resolves to the exact command | | See (read-only) | brief · snapshot · triage · pipeline · receivables · reconcile · booked-not-paid · noshow · focus · segment · crm · list (12 entities) · forms · surveys · transactions | | Find (read-only) | contact find (name/email/phone → the id every write needs) · invoice list · appointment list (upcoming) | | Version (read-only) | export (location → one diffable file) · diff (file vs live, or file vs file — what changed?) | | Act | tag · note · opp (create/move/update) · appointment (book/cancel) · send (SMS/email) | | Build | contact create · contact upsert (de-dupe) · contact update · field create · field update · value create · value update · calendar create · business create · business update | | Delete (single-target, accident-proof) | contact delete · field delete · value delete · calendar delete · business delete | | Bill (scope-gated) | invoice draft · invoice send (pay-link — not a card charge) | | Operate | init · doctor · open · completions · api · multi-client profiles |

A human reads the pretty output; an agent consumes the stable --json / --ndjson underneath.

Why sizmo

sizmo is the CLI your AI agents drive GoHighLevel with — and the operator who supervises them. Every command emits stable, token-lean JSON, so an agent calls one command for one answer instead of loading a whole MCP toolshelf to read a single field. A human gets the exact same data as a clean card.

Three things it does that nothing else in the ecosystem does:

  • Diff a location. sizmo export turns a GoHighLevel location into one deterministic file; sizmo diff shows exactly what changed — or what a push would change. GHL snapshots are structurally incapable of this (the single loudest voted GHL fear is push-overwrite anxiety); a file is not. See the 30-second demo →
  • Answer the Monday questions. sizmo brief prints where money is leaking and who needs a reply today — and never invents a number to do it. A blocked data source is reported as unknown, never as zero.
  • Speak plain English to your CRM — two paths, pick what you already have. Most people driving sizmo already run an AI coding agent (Claude Code, Codex, Cursor). Point it at this repo — hand it SKILL.md — and it drives sizmo's documented flag commands directly (sizmo tag …, sizmo opp move …): zero extra AI key, zero extra cost, and it's the subscription you're already paying for. If you want the CLI itself to understand plain English with no agent in the loop, that's sizmo ask: it runs, not just resolves. sizmo ask "tag Ana as follow-up" runs a read immediately; a write previews then fires on a bare sizmo ask --confirm (no retyping). Chain steps in one sentence — sizmo ask "tag Ana as follow-up and book her Friday at 2pm" — and one --confirm fires the whole batch in order, resolving every name (fields, calendars, businesses, contacts) live against the account, never a stale local guess. Bare command names ("brief", "list forms") skip the AI call entirely. ask needs its own AI key (sizmo config set --ai-key); zero LLM calls without one, and your PIT/contacts/money never leave the machine — pronoun follow-ups ("her") resolve locally, the AI only ever sees a placeholder token, never the real name (see SECURITY.md). Full walkthrough with real examples, what fires directly vs. what only prints, and troubleshooting: docs/how-to/ask.md.

Why not the tools you already have:

  • vs the GoHighLevel web UI — the UI makes you click through six screens to assemble what sizmo brief prints in one. And it has no "money leaks" view — overdue receivables and booked-but-never-billed sessions are the numbers it won't surface for you.
  • vs the official GHL MCP server — that's an OAuth-app install, multi-tenant, and read-leaning. sizmo is PIT-simple (paste one token, go), does confirm-gated writes too, and is zero-dependency — the whole tool is auditable in an afternoon (see SECURITY.md).
  • vs Zapier / Make — those automate tasks on a trigger. sizmo answers questions for a human at a keyboard (and for the agent that human points at it). No per-task pricing, no data routed through a third party — it runs on your machine against your PIT.

It's also built to be piped: every command has a stable --json envelope (API-STABILITY.md), so a human reads the pretty output and their agent consumes the JSON underneath.

Install

Requires Node.js 22+ (current Active LTS).

Option A — npm (recommended):

npx sizmo brief            # run with no install
# or install globally:
npm install -g sizmo
sizmo brief

Option B — clone + install (puts sizmo on your PATH from source):

git clone https://github.com/csalamida/sizmo-ghl-cli
cd sizmo-ghl-cli
bash install.sh

install.sh symlinks bin/sizmo.mjs into ~/.local/bin/sizmo. Add ~/.local/bin to $PATH if not already present (the script will warn you if needed).

Option C — clone + run directly:

git clone https://github.com/csalamida/sizmo-ghl-cli && cd sizmo-ghl-cli
node bin/sizmo.mjs brief

Then set up a profile:

echo "pit-yourtoken..." | sizmo init --profile myclient --loc YOUR_LOCATION_ID

That writes the profile and runs sizmo doctor to confirm you're green, in one run. The token comes from stdin only, never argv, so it never lands in your shell history, ps, or a process list.

Don't have the token yet? Run sizmo init on its own first — it prints the exact GoHighLevel path and a copy-pasteable scope block, then tells you the command above to run. It deliberately does not prompt for the token interactively: stdin is reserved for the PIT, so it cannot both ask a question and read the secret from the same place. Bare sizmo init therefore exits non-zero with that instruction rather than completing setup — that is the intended two-step flow, not a failure.

sizmo init          # prints the GHL path + scopes, then the exact piped command to run

Manual alternative:

echo "pit-yourtoken..." | sizmo config set --profile myclient --loc YOUR_LOCATION_ID --pit-stdin

PIT = Private Integration Token. Find it under GoHighLevel Settings > Integrations > Private Integrations. Never pass it as a command-line argument — always pipe it via stdin.

When creating the Private Integration, grant these scopes for the full brief:

contacts.readonly · conversations.readonly · opportunities.readonly
calendars.readonly · invoices.readonly · payments/transactions.readonly

For write commands (tag, note, opp, appointment, send), also add:

contacts.write · opportunities.write · calendars.write · conversations/message.write

Granting fewer is fine — missing scopes show as ⚠ in affected metrics rather than failing the whole command. Run sizmo auth check after setup to see a per-lane scope report.

Auth: PIT vs MCPsizmo uses a Private Integration Token (PIT), not the GoHighLevel MCP server. See docs/how-to/auth-pit-vs-mcp.md for the comparison and when you'd want each.

Verify auth — or just run the one-shot health check:

sizmo doctor            # scopes + location + CRM model + version, one screen
sizmo auth status
sizmo auth check

sizmo doctor is the "is it me or the tool?" answer: it reports each scope (✓/⚠/✖ with the exact fix line for any blocked one), location reachability + latency, CRM-model freshness, and whether a newer sizmo is available — and it never reports green when a lane is blocked.

Commands

Command list generated from sizmo schema (authoritative — pulled directly from the code):

| Command | Summary | Key flags | |---------|---------|-----------| | sizmo brief | Morning brief — numbers + NEEDS YOU TODAY | --days N (default 7) | | sizmo snapshot | Monday card — 6 metrics, one screen | --days N (default 7) | | sizmo triage | Who is waiting on a reply, longest first | --top N (default 10), --days N (default 30) | | sizmo pipeline | Pipeline health — value by stage + stuck deal sweep | --stuck-days N (default 7), --top N (default 100) | | sizmo noshow | No-show recovery — who to re-book | --days N (default 30), --top N (default 15) | | sizmo receivables | A/R — who owes, how much, how old | --top N (default 20) | | sizmo reconcile | Money reconciliation — collected by source, flags, recurring | --days N (default 30), --top N (default 20) | | sizmo booked-not-paid | Sessions with no invoice or payment — the money leak | --days N (default 30), --top N (default 15) | | sizmo focus | One ranked to-do queue by money at stake | --top N (default 15), --stuck-days N (default 7) | | sizmo ack <contactId> | Snooze a contact so it stops surfacing in focus/brief. Local state only — never writes to GoHighLevel, so no --confirm. Acked items are hidden, not deleted, and the count is always shown in the footer; reveal with --show-acked | --for 7d\|48h\|30m (default 7d), --reason "...", --list, --clear <contactId> | | sizmo segment | Find contacts by criteria — tag, phone, age, etc. | --tag X, --without-tag X, --no-tags, --created-days N, --has-phone, --no-phone, --top N (default 20) | | sizmo crm | Query the local CRM model — counts, lists, staleness | --all (show all items) | | sizmo list [entity] | List any of 12 cached entities (pipelines, calendars, tags, fields, users, forms, surveys, products, links, businesses, objects) or an overview | [entity] (else 3-group overview) | | sizmo forms [formId] | List forms, or view a form's recent submissions | --top N (default 20, max 100) | | sizmo surveys [surveyId] | List surveys, or view a survey's recent submissions | --top N (default 20, max 100) | | sizmo transactions | Payment transaction history (read-only) | --top N, --type <entityType> | | sizmo ask "<intent>" | Run a plain-English read immediately; preview a write and fire it with a bare --confirm. Chains multiple steps in one sentence. | needs sizmo config set --ai-key <key> (or an exact command name — skips the AI call) | | sizmo sync | Refresh the local CRM model (all 12 entities) | [entity] (sync one) | | sizmo export | Dump the location's structure to one deterministic, diffable file | --out <file> (else stdout) | | sizmo diff | Compare an export against live, or two exports — what changed | sizmo diff <file> | sizmo diff <a> <b> |

Writes (confirm-gated)

These commands change data in GoHighLevel. Every write requires --confirm; without it the CLI prints the exact change + a rerun command and exits 5. Nothing fires silently. The PIT scope is the gate — a command only works if your token carries the scope; otherwise it fails with AUTH + the exact scope to add. sizmo cannot charge a card (GoHighLevel exposes no public endpoint for it); the money-side writes are draft/send an invoice.

| Command | Summary | Required flags | Scope needed | |---------|---------|----------------|--------------| | sizmo tag <contactId> --add <tag> | Add a tag to a contact | --add or --remove | contacts.write | | sizmo tag <contactId> --remove <tag> | Remove a tag from a contact | --add or --remove | contacts.write | | sizmo note <contactId> --text "..." | Add a note to a contact | --text | contacts.write | | sizmo opp create --name --pipeline --stage --contact | Create a pipeline opportunity | --name, --pipeline, --stage, --contact | opportunities.write | | sizmo opp move <oppId> --stage <name> | Move an opportunity to a stage | --stage | opportunities.write | | sizmo opp update <oppId> [--value --status] | Update value or status of an opportunity | --value or --status | opportunities.write | | sizmo opp delete <oppId> | Delete one opportunity by id | oppId positional | opportunities.write | | sizmo appointment book --calendar --contact --start | Book an appointment | --calendar, --contact, --start, --end, --title, --assigned-user, --address, --no-notify | calendars.write | | sizmo appointment cancel <apptId> | Cancel an appointment | apptId positional | calendars.write | | sizmo appointment note <apptId> --text "..." | Add a note to an appointment | --text | calendars.write | | sizmo send <contactId> --channel sms\|email --message "..." | Send an SMS or email now, or schedule it. Email bodies are HTML-escaped; subject defaults to the message's first line unless --subject is given | --channel, --message, --subject, --schedule | conversations/message.write | | sizmo send … --schedule 2026-08-01T09:00:00Z | Queue the message for a future ISO 8601 datetime instead of sending now. Must be in the future; cancel with sizmo send cancel | --schedule | conversations/message.write | | sizmo send cancel <messageId> --channel sms\|email | Cancel a scheduled SMS or email before it goes out | --channel | conversations/message.write |

Build / scaffold writes — stand up a location from the terminal instead of clicking. The PIT scope is the gate: if your token carries the write scope, the command works; if not, it fails with AUTH + the exact scope to add.

| Command | Summary | Scope needed | |---------|---------|--------------| | sizmo contact create [--email --phone --name --first --last --tag] | Create a contact | contacts.write | | sizmo contact upsert --email\|--phone … [--name --tag] | Create-or-update, de-duped on email/phone | contacts.write | | sizmo contact delete <contactId> | Delete one contact by id | contacts.write | | sizmo field create --name "..." [--type TEXT --model contact] | Create a custom field | locations/customFields.write | | sizmo field delete <fieldId> | Delete one custom field by id | locations/customFields.write | | sizmo value create --name "..." --value "..." | Create a custom value | locations/customValues.write | | sizmo value update <valueId> [--name] [--value] | Edit a custom value in place — the id is unchanged, so workflows and templates referencing it keep resolving. Reads the current value first, so changing one field never blanks the other. Prefer this over delete+create, which mints a new id | locations/customValues.write | | sizmo field update <fieldId> [--name] [--placeholder] … | Edit a custom field in place. --type is refused — the endpoint accepts no dataType, since stored values would no longer match. Prefer this over delete+create, which discards every value already stored on every contact | locations/customFields.write | | sizmo value delete <valueId> | Delete one custom value by id | locations/customValues.write | | sizmo calendar create --name "..." [--type --slot-min --team-member] | Create a calendar — --team-member uid1,uid2 required for round_robin/collective types | calendars.write | | sizmo calendar delete <calendarId> | Delete one calendar by id | calendars.write | | sizmo business create --name "..." [--email --phone --website] | Create a B2B company record | businesses.write | | sizmo business delete <id> | Delete one business by id | businesses.write | | sizmo link create --name "..." --redirect-to <url> | Create a trigger link | links.write | | sizmo link delete <linkId> | Delete one trigger link by id | links.write |

Deletion is single-target by design. delete takes exactly one id — there is no --all, no wildcard, no batch. Before it deletes, it fetches the resource and shows you its name in the confirm preview (a wrong/nonexistent id → NOTFOUND, nothing touched), then deletes that one resource by its id path. It is structurally incapable of the "I deleted one and it wiped them all" accident.

Money writes (scope-gated, since 2.0) — only what the public API + your scope allow:

| Command | Summary | Scope needed | |---------|---------|--------------| | sizmo invoice draft --contact <id> --item "Name:amount" | Create a draft invoice (a document — not sent, no charge) | invoices.write | | sizmo invoice send <invoiceId> | Send an invoice — delivers a pay-link / text-to-pay the customer acts on | invoices.write |

There is no card-charging command because GoHighLevel exposes no public endpoint for it. draft creates a document; send requests payment. Both are confirm-gated. (Prior to 2.0 sizmo excluded all money endpoints; 2.0 moved to "the PIT scope is the gate" — see SECURITY.md + CHANGELOG.md.)

How writes work:

# Step 1 — preview (no --confirm): prints change description + rerun command, exits 5
sizmo tag cid-001 --add VIP --json

# Step 2 — execute (with --confirm): fires the write, exits 0
sizmo tag cid-001 --add VIP --confirm

# --dry-run: shows change description without executing, exits 0
sizmo tag cid-001 --add VIP --dry-run

Pipeline/calendar names are resolved to IDs from the local CRM model. Run sizmo sync first if you've changed stages or calendars.

Utility commands

sizmo init              # guided setup: scopes → token (stdin) → profile → doctor
sizmo doctor            # one-shot health: scopes, location, model, version
sizmo help <command>    # per-command help with runnable examples (also: sizmo <command> --help)
sizmo open <contactId>  # open a contact in the GoHighLevel web app (--opp · --url to just print)
sizmo completions zsh   # print a tab-completion script (bash too) — eval "$(sizmo completions zsh)"
sizmo schema            # machine-readable command tree (JSON)
sizmo auth status       # show credential source, location, masked PIT, rotation age
sizmo auth check        # probe live API to verify PIT scopes
sizmo config list       # list all saved profiles
sizmo config use <name> # switch default profile
sizmo config set --profile <name> --loc <id> --pit-stdin
sizmo config set --ai-key "sk-…" --ai-provider anthropic  # enables `sizmo ask` (optional)
sizmo config rm <name>  # remove a profile
sizmo config cache-clear # delete the 60s read cache from disk (it holds contact data)
sizmo api /path         # raw GET escape hatch (--paginate --max-pages N)

Tab-completion — add eval "$(sizmo completions zsh)" to your ~/.zshrc (or bash~/.bashrc) and sizmo <TAB> completes commands; sizmo brief --<TAB> completes flags. Generated from the live command tree, so it stays correct across upgrades.

Act without retyping — the people-focused recipes (receivables, triage, noshow, booked-not-paid) print a ready-to-run → sizmo send … / → sizmo open … line under each row with the real contact id. Copy, run. (Sends still require --confirm; sizmo can't charge a card — no public endpoint.)

Global flags (work with every command)

--profile <name>     use a named credential profile (or set SIZMO_PROFILE)
--json               machine-readable output (stable JSON envelope)
--ndjson             machine-readable, streamed: one meta line + one JSON object per list item
--fields a,b,c       (with --json/--ndjson) keep only these keys on each list item — trims the payload
--concise            (with --json) leaner payload — currently trims `brief` only
--fresh              bypass 60-second read cache — re-fetches live data
--no-cache           alias for --fresh
--no-update-check    skip the once-a-day "newer version available" check for this run

Profile via env. SIZMO_PROFILE=<name> selects a saved profile without passing --profile on every call (precedence: an explicit --profile flag > SIZMO_PROFILE > the saved default). Mirrors AWS_PROFILE — handy for a per-client shell or a CI lane.

Token-lean for agents. --fields and --concise exist so an LLM driving sizmo pays for only the data it needs. sizmo receivables --json --fields name,due returns just those two keys per row instead of the full record — often an ~80–90% smaller payload. --fields projects the list in receivables, segment, triage, noshow, focus, crm, brief, and pipeline; --concise currently trims brief only. Both are read-from-the-shelf, pay-per-call ergonomics: an agent that shells out to sizmo … --json --fields … carries one command, not a whole tool's schema.

--ndjson for streaming/agents. Instead of one big JSON array, --ndjson emits a leading meta line (carrying command, location, degraded, warnings, count, and every non-list field under data) followed by one JSON object per list item — so an agent can process rows line-by-line without buffering thousands of records, and the "this source was blocked / unknown" signal is never dropped (it rides the meta line). A payload with no list (e.g. doctor) emits a single envelope line. Combine with --fields to trim each row.

sizmo receivables --ndjson --fields name,due
# {"_meta":true,"schemaVersion":1,"command":"receivables","location":"L1","listKey":"list","count":2,"degraded":false,"warnings":[],"data":{...}}
# {"name":"Acme Co","due":5000}
# {"name":"Beta LLC","due":3000}

JSON envelope

Every command supports --json. The envelope shape is stable:

{
  "schemaVersion": 1,
  "command": "brief",
  "location": "LOC_ID",
  "data": { ... },
  "degraded": false,
  "warnings": [],
  "cacheAgeMs": 0
}

degraded: true means at least one data source was blocked (scope or auth). Read warnings. A blocked source is not zero — treat it as unknown.

This holds across every command that can be blocked — receivables, pipeline, reconcile, triage, noshow, segment, booked-not-paid. A blocked lane returns null for its counts and totals plus a blocked: <httpStatus> marker naming the reason, so a denied read can never be mistaken for a settled account, an empty pipeline, a clean reconciliation, or an inbox with nobody waiting. reconcile nulls its flags too — "0 refunds · 0 failed · 0 orphans" is an equally fabricated all-clear on money that was never read.

// invoices unreadable — note null, not 0
{ "location": "…", "blocked": 401, "totalOwed": null, "outstanding": null, "list": [] }

Enforced by test/docs/blocked-is-not-zero.test.mjs, which scans every command and fails the build if any of them starts returning a hardcoded 0 from a blocked branch.

Router verbs differ. init, auth, and config are setup verbs, not data commands — their --json output is a purpose-specific object (e.g. auth check{ lanes, usable }, init{ profile, location, ok, doctor }), not the data/degraded/warnings envelope above. The data commands (brief, snapshot, doctor, …) all use the envelope.

Both contracts are frozen under semver — see API-STABILITY.md for exactly what you can depend on (exit codes, JSON shapes, schemaVersion policy, flag names) and what you can't (human output, stderr text, internal modules). The contract has held since 1.0 and is unchanged through 2.x — the 2.0 major bump was the money-policy guarantee, not the API.

Staying up to date

npx sizmo always runs the latest published version. If you installed globally (npm i -g sizmo), the CLI checks npm at most once a day and prints a one-line nudge to stderr when a newer version exists:

⚠ sizmo 2.1.0 available (you have 2.0.0) — update: npm i -g sizmo@latest

sizmo doctor also shows a CLI VERSION line. The check is privacy-clean: a single GET of the public npm registry, cached 24h, never blocking, nothing sent about you. Turn it off with --no-update-check (per run) or the NO_UPDATE_NOTIFIER / SIZMO_NO_UPDATE_CHECK env vars. It never runs under --json or when output is piped.

Your CRM model

sizmo caches the slow-changing structure of your CRM — pipelines + stages, calendars, tags, custom fields, users, location, plus forms, surveys, products, links, businesses, and custom objects — in a local file (~/.config/sizmo/model/<locationId>.json). Recipes read from this cache instead of re-fetching structure on every run.

What it stores: names + IDs only (pipeline/stage, calendars, tags, custom fields, forms, surveys, products, businesses, custom objects), plus user roster and location info (timezone, currency, country). Structure only — no contacts, no conversations, no payments.

Sync once, read fast. The model is synced automatically on first use. After that, recipes use the cached copy. Run sizmo sync after you change your pipeline stages or add calendars:

sizmo sync                # full refresh (all 12 entities)
sizmo sync tags           # refresh one entity only

Age is always shown. sizmo crm shows how old each entity is. Stale entries (past TTL: 24h for pipelines/calendars/users/location; 12h for tags/fields) show a warning. The CLI never silently serves stale structure as current.

Model never auto-syncs when stale. It serves the cached data with a loud age banner. This avoids surprise network calls mid-recipe. Use sizmo sync or --fresh to force a refresh.

sizmo crm                 # overview: counts + age per entity
sizmo crm pipelines       # list pipelines + stages
sizmo crm calendars       # list calendars
sizmo crm tags [--all]    # list tags (truncated at 20 by default)
sizmo crm fields          # list custom fields
sizmo crm users           # list users
sizmo crm location        # timezone / currency / country
sizmo crm pipelines --json  # machine output with _meta.source/syncedAt/ageMs/stale

The JSON _meta block in every crm response lets agents branch on staleness without parsing prose:

"_meta": { "source": "cache", "syncedAt": 1718000000000, "ageMs": 3600000, "stale": false, "offline": false }

Scope requirements for a full sync: opportunities.readonly, calendars.readonly, locations/tags.readonly, locations/customFields.readonly, users.readonly, locations.readonly. A 401/403 on one entity marks it blocked; the rest still store. sizmo crm shows ✖ needs <scope> for blocked entities.

Safety model

  • Reads are always free. Read commands never change anything in GoHighLevel.
  • Writes require explicit --confirm. Every write command — appointment, business, calendar, apply, contact, field, invoice, link, note, opp, send, tag, value — prints the exact change + a rerun command and exits 5 (confirmation-required) without --confirm. Nothing fires silently — safe for agent use. (This list is enforced against the source by test/docs/stability-claims.test.mjs; it named only five commands until 2026-07-27, which made seven gated commands look ungated.)
  • The PIT scope is the gate (since 2.0). sizmo exposes only what your token's scopes + GoHighLevel's public API allow — a missing scope fails with AUTH + the fix. There is no card-charging command (no public endpoint exists); the money-side writes are draft/send an invoice, both confirm-gated. Reads never change anything.
  • --dry-run available on all writes. Shows the change description without executing. Exits 0.
  • PIT never in argv. Credentials are passed via stdin (--pit-stdin) or env var (--pit-env VAR). Never logged, never echoed raw.
  • 60-second read cache. Repeated calls within 60s return cached data. cacheAgeMs in the envelope tells you how old. Use --fresh to bypass.
  • sizmo ask is opt-in and scoped. With no --ai-key set, sizmo makes zero LLM calls. With one set, only your typed request text and CRM structure (pipeline/calendar/tag/form/survey/business names + ids) reach your chosen provider — never your PIT, contacts, conversations, or money data. Pronoun follow-ups resolve from a local cache; the AI only ever sees a placeholder token, never a real name.
  • sizmo ask's confirm never re-asks the AI. The preview resolves every name to a real id once and caches that exact plan (~/.config/sizmo/ask-memory/, 10 min TTL); a bare sizmo ask --confirm replays it verbatim — it can't fire something different from what you previewed, and the plan is cleared before it runs so a stray second --confirm can't re-apply it. Typing the sentence and --confirm together resolves and fires in one call, with no preview step — you asked for both at once.
  • sizmo ask can't auto-fire everything. Invoicing, appointments, and opp update still only resolve-and-print (money and scheduling stay a deliberate manual step) — tag/note/send/contact/opp create/opp move/value create/field/calendar/business fire directly.

Every claim above is verifiable — see SECURITY.md for the threat model, the self-audit recipes, and how to report a vulnerability. Zero runtime dependencies: what you read is what runs.

Finding things

Every write command needs an id. These three turn what you know into what the tool needs.

sizmo contact find "ana cruz"        # name, email or phone -> contact id
sizmo contact find [email protected] --limit 25
sizmo invoice list --status draft    # find a draft you created earlier
sizmo invoice list --top 50
sizmo appointment list              # what is booked in the next 14 days
sizmo appointment list --days 30

All three are read-only and print the id on each row, so the next command is a copy away. Each also tells you when its answer is incomplete rather than looking clean: contact find reports GoHighLevel's real match count even when it returns fewer, invoice list marks a partial scan as a floor, and appointment list says plainly when a calendar could not be read instead of showing you an empty week.

Honest limitations

  • Rate-limit cap: 5 concurrent requests. The pool is capped at 5 to avoid hammering the GHL API.
  • Cache TTL: 60 seconds. Stale data possible within that window. Use --fresh when you need live.
  • No-show / booked-not-paid calendar truncation. GHL's /calendars/events endpoint has no pagination cursor. If a calendar returns >= 100 events the result may be silently truncated. A degraded: true warning is emitted in that case.
  • Pipeline currency. GHL opportunity monetary values carry no currency field — they inherit pipeline config. The CLI renders them as-is; cross-currency totals are never summed.
  • No workflow writes. This tool has no workflow-authoring capability. Workflow creation stays in GoHighLevel's UI.
  • sizmo ask's LLM step uses your synced CRM model as context (which pipelines/calendars/tags/forms/surveys/businesses it knows to suggest) — run sizmo sync after adding things so the AI is aware of them. Actual name→id resolution (contacts, opportunities, custom fields, calendars, businesses) is always a live fetch, not the cache, so a just-created entity resolves correctly even before the next sync. Accuracy also depends on the LLM provider you configure — a low-confidence resolution says so and asks you to rephrase rather than guessing at a command.
  • sizmo ask opportunity lookup ("move Ana's deal to Won") searches only that contact's OPEN opportunities — it can't find or move a won/lost/abandoned one by name.

Exit codes

| Code | Meaning | |------|---------| | 0 | OK | | 1 | API error | | 2 | Usage error (bad flag / unknown command) | | 3 | Auth error / no location resolved | | 4 | Not found (unknown pipeline/stage/calendar name) | | 5 | Confirmation required — rerun with --confirm to execute |

Multi-client

sizmo config set --profile clientA --loc LOC_A --pit-stdin
sizmo config set --profile clientB --loc LOC_B --pit-stdin
sizmo brief --profile clientA
sizmo brief --profile clientB

See docs/how-to/multi-client.md for full workflow.

Driving sizmo with an AI agent (recommended — no AI key needed)

SKILL.md in this repo is the full command reference, safety rules, and agent-use patterns in one file — no manual briefing needed. It's the fastest way to get natural language working against sizmo, because your agent (not sizmo) is the one doing the language understanding, using the subscription you already pay for.

Claude Code — ready-to-load skill:

cp SKILL.md ~/.claude/skills/sizmo-cli.md          # global, all projects
# or
cp SKILL.md /your-project/.claude/skills/sizmo-cli.md   # project-local

Once installed, load it in any Claude session:

/sizmo-cli

Codex, Cursor, or any other coding agent: AGENTS.md in this repo is the same command reference without Claude-specific frontmatter. Codex picks it up automatically from the repo root; Cursor users can point their project instructions at it. No manual briefing needed.

If you cloned the repo, it's already there. If you're running via npx sizmo, copy it once:

curl -fsSL https://raw.githubusercontent.com/csalamida/sizmo-ghl-cli/main/AGENTS.md > AGENTS.md

Either way, your agent will know every command, every flag, the confirm-gate pattern, how to read the JSON envelope, and when to stop and ask the human before firing a write. No extra prompting required.

If you update sizmo (new commands, changed flags), pull the repo — AGENTS.md and SKILL.md both track the CLI version.

License

MIT. See LICENSE.


Built by Sizmo — GHL CRM systems & automation. Unofficial; not affiliated with HighLevel.