sizmo 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # sizmo
2
2
 
3
- **Unofficial read-only GoHighLevel CLI for coaches and consultants.**
3
+ **Unofficial read-only GoHighLevel CLI.** Your GoHighLevel CRM — leads, bookings, pipeline, receivables, payments, and a money-ranked to-do list — from the terminal, in one command.
4
4
 
5
5
  > Not affiliated with, endorsed by, or supported by HighLevel. This is an independent open-source tool.
6
6
 
@@ -10,7 +10,16 @@
10
10
 
11
11
  Requires Node.js 20+.
12
12
 
13
- **Option A — clone + install (puts `sizmo` on your PATH):**
13
+ **Option A — npm (recommended):**
14
+
15
+ ```sh
16
+ npx sizmo brief # run with no install
17
+ # or install globally:
18
+ npm install -g sizmo
19
+ sizmo brief
20
+ ```
21
+
22
+ **Option B — clone + install (puts `sizmo` on your PATH from source):**
14
23
 
15
24
  ```sh
16
25
  git clone https://github.com/csalamida07-cyber/sizmo-ghl-cli
@@ -20,12 +29,6 @@ bash install.sh
20
29
 
21
30
  `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).
22
31
 
23
- **Option B — run with no install, straight from GitHub:**
24
-
25
- ```sh
26
- npx github:csalamida07-cyber/sizmo-ghl-cli brief
27
- ```
28
-
29
32
  **Option C — clone + run directly:**
30
33
 
31
34
  ```sh
@@ -33,8 +36,6 @@ git clone https://github.com/csalamida07-cyber/sizmo-ghl-cli && cd sizmo-ghl-cli
33
36
  node bin/sizmo.mjs brief
34
37
  ```
35
38
 
36
- > `npx sizmo` (npm install) is coming once the package is published to npm.
37
-
38
39
  Then configure a profile:
39
40
 
40
41
  ```sh
@@ -43,6 +44,17 @@ echo "pit-yourtoken..." | sizmo config set --profile myclient --loc YOUR_LOCATIO
43
44
 
44
45
  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.
45
46
 
47
+ When creating the Private Integration, grant these scopes for the full `brief`:
48
+
49
+ ```
50
+ contacts.readonly · conversations.readonly · opportunities.readonly
51
+ calendars.readonly · invoices.readonly · payments/transactions.readonly
52
+ ```
53
+
54
+ 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.
55
+
56
+ **Auth: PIT vs MCP** — `sizmo` uses a Private Integration Token (PIT), not the GoHighLevel MCP server. See [`docs/how-to/auth-pit-vs-mcp.md`](docs/how-to/auth-pit-vs-mcp.md) for the comparison and when you'd want each.
57
+
46
58
  Verify auth:
47
59
 
48
60
  ```sh
@@ -149,4 +161,4 @@ MIT. See LICENSE.
149
161
 
150
162
  ---
151
163
 
152
- Built by Sizmo — productized GHL systems for coaches & consultants. Unofficial; not affiliated with HighLevel.
164
+ Built by [Sizmo](https://github.com/csalamida07-cyber/sizmo-ghl-cli) — GHL CRM systems & automation. Unofficial; not affiliated with HighLevel.
package/SKILL.md CHANGED
@@ -28,4 +28,4 @@ Read-only GoHighLevel ops. Every command takes `--json` (stable envelope: `{sche
28
28
  - No location resolved → exit 3. Pass `--profile` or set `GHL_LOCATION_ID`; there is no default location.
29
29
 
30
30
  ---
31
- Built by Sizmo — productized GHL systems for coaches & consultants. Unofficial; not affiliated with HighLevel.
31
+ Built by Sizmo — GHL CRM systems & automation. Unofficial; not affiliated with HighLevel.
@@ -0,0 +1,77 @@
1
+ # Auth: PIT vs GoHighLevel MCP
2
+
3
+ Two ways to authenticate with GoHighLevel from external tooling. `sizmo` uses one; here is why, and when you might want the other.
4
+
5
+ ---
6
+
7
+ ## What sizmo uses: PIT (Private Integration Token)
8
+
9
+ A Private Integration Token is a scoped API credential you create inside your GoHighLevel account under **Settings → Integrations → Private Integrations**. When you create one you choose exactly which API scopes it carries. The token is a long string starting with `pit-`.
10
+
11
+ Why `sizmo` uses it:
12
+
13
+ - **Precise scope control.** You decide which scopes are granted — `sizmo` requests read-only scopes only (`contacts.readonly`, `conversations.readonly`, etc.). The token cannot do anything beyond what you explicitly allowed.
14
+ - **Deterministic.** The token is stable and usable in any headless environment — scripts, cron jobs, CI, terminal aliases. No browser, no interactive login flow, no per-session refresh.
15
+ - **Least-privilege.** A read-only PIT cannot create contacts, send messages, charge invoices, or modify workflows. If the token is ever compromised, the blast radius is limited to read access.
16
+ - **No token server required.** The CLI resolves the token from a local profile file or an environment variable. There is no OAuth callback server to run.
17
+
18
+ **PIT = more control** over what the credential can do.
19
+
20
+ ---
21
+
22
+ ## GoHighLevel also offers an official MCP server
23
+
24
+ GoHighLevel provides an official MCP (Model Context Protocol) server — the LeadConnector MCP server — as a sanctioned, public feature you can enable inside your GoHighLevel account. Once enabled, MCP clients (such as AI assistants like Claude) can connect to it and perform CRM operations through the MCP protocol.
25
+
26
+ Key characteristics of GHL's MCP server:
27
+
28
+ - It is OAuth-connected. Authorization goes through GoHighLevel's standard OAuth flow.
29
+ - It is designed for AI agent and LLM use cases — giving an AI assistant the ability to read and write CRM data through natural language requests.
30
+ - It exposes a broader surface area of CRM operations compared to a scoped read-only PIT.
31
+
32
+ For information on enabling GoHighLevel's MCP server, refer to [GoHighLevel's official MCP documentation](https://help.gohighlevel.com) (search for "MCP" or "Model Context Protocol" in their help center).
33
+
34
+ ---
35
+
36
+ ## When to use which
37
+
38
+ | | PIT (what sizmo uses) | GHL MCP server |
39
+ |---|---|---|
40
+ | **Use case** | CLI tools, shell scripts, cron jobs, precise read-only reporting | AI assistants / LLM agents that need to read or write CRM data via natural language |
41
+ | **Auth mechanism** | Static token in local profile or env var | OAuth flow |
42
+ | **Scope control** | Explicit, per-scope — grant only what you need | Managed by the MCP server and OAuth grant |
43
+ | **Headless / scriptable** | Yes — no browser required | Requires initial OAuth browser flow |
44
+ | **Write operations** | Not applicable for sizmo (read-only tool) | Yes — MCP can expose write operations |
45
+ | **Setup** | Create PIT in GHL settings, save with `sizmo config set` | Enable MCP in GHL settings, connect an MCP-capable client |
46
+
47
+ **Use a PIT** when you want a CLI tool, shell automation, or any situation where you need deterministic, headless, read-only access with explicit scope control. That is what `sizmo auth check` validates.
48
+
49
+ **Use GHL's MCP server** when you are wiring GoHighLevel into an AI assistant or agent that communicates via the MCP protocol and you want the AI to be able to interact with your CRM through natural language.
50
+
51
+ The two are not mutually exclusive — you can use `sizmo` for terminal-based reporting alongside an MCP-connected AI assistant in the same GoHighLevel location.
52
+
53
+ ---
54
+
55
+ ## Checking which scopes your PIT has
56
+
57
+ After setting up a profile, run:
58
+
59
+ ```sh
60
+ sizmo auth check
61
+ ```
62
+
63
+ This probes all six read lanes and reports which scopes are present and which are missing:
64
+
65
+ ```
66
+ auth check: probing 6 GoHighLevel API scopes...
67
+ ✅ contacts
68
+ ✅ conversations
69
+ ✖ opportunities — add scope opportunities.readonly
70
+ ✅ calendars
71
+ ✅ invoices
72
+ ✅ payments
73
+
74
+ 5/6 lanes readable — `brief` will show ⚠ on opportunities until you add: opportunities.readonly
75
+ ```
76
+
77
+ Add the missing scope in your GoHighLevel Private Integration settings, then re-run `sizmo auth check` to confirm.
@@ -4,7 +4,20 @@ A profile stores a PIT (Private Integration Token) and Location ID under a name.
4
4
 
5
5
  ## Step 1 — get your PIT
6
6
 
7
- In GoHighLevel: Settings → Integrations → Private Integrations → Create. Grant read-only scopes. Copy the token (starts with `pit-`).
7
+ In GoHighLevel: Settings → Integrations → Private Integrations → Create. Copy the token (starts with `pit-`).
8
+
9
+ Grant the following scopes when creating the integration (minimum set for a full `brief`):
10
+
11
+ ```
12
+ contacts.readonly
13
+ conversations.readonly
14
+ opportunities.readonly
15
+ calendars.readonly
16
+ invoices.readonly
17
+ payments/transactions.readonly
18
+ ```
19
+
20
+ Grant all six for the complete `brief`. Granting fewer is fine — any missing scope shows as ⚠ in the affected metric rather than hard-failing. Run `sizmo auth check` after setup to see exactly which lanes are readable and which scopes are still needed.
8
21
 
9
22
  ## Step 2 — find your Location ID
10
23
 
package/lib/cli.mjs CHANGED
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
4
4
  import { registry } from './registry.mjs';
5
5
  import { resolve, loadProfiles, saveProfiles, validateToken, mask, pitAgeDays } from './config.mjs';
6
6
  import { makeHttp } from './http.mjs';
7
+ import { mapLimit } from './pool.mjs';
7
8
  import { buildCtx } from './context.mjs';
8
9
  import { buildSchema } from './schema.mjs';
9
10
  import { GhlError, EXIT } from './errors.mjs';
@@ -92,24 +93,77 @@ async function routerVerb(cmd, args, io) {
92
93
  // ── auth ────────────────────────────────────────────────────────────────────
93
94
  if (cmd === 'auth') {
94
95
  if (verb === 'check') {
95
- // auth check: verify PIT scopes by probing the API directly
96
+ // auth check: per-lane scope diagnostic probes all fleet read lanes concurrently.
97
+ // Rule: 401/403 = scope MISSING; 200 or 4xx param error (400/422) = scope PRESENT.
98
+ // Exit 0 if contacts lane is readable (tool is usable); per-lane ✖ lines guide the user.
96
99
  const creds = resolve(profile);
97
100
  if (!creds.pit) return die('no credentials found', EXIT.AUTH, 'set GHL_PIT, or: sizmo config set --profile <name> --pit-stdin');
98
101
  if (!creds.loc) return die('no location resolved', EXIT.AUTH, 'pass --profile <name>, or set GHL_LOCATION_ID');
99
- write('auth check: probing GoHighLevel API scopes...\n');
102
+
103
+ const loc = creds.loc;
104
+ const LANES = [
105
+ { name: 'contacts', scope: 'contacts.readonly', path: `/contacts/?locationId=${loc}&limit=1` },
106
+ { name: 'conversations', scope: 'conversations.readonly', path: `/conversations/search?locationId=${loc}&limit=1` },
107
+ { name: 'opportunities', scope: 'opportunities.readonly', path: `/opportunities/search?location_id=${loc}&limit=1` },
108
+ { name: 'calendars', scope: 'calendars.readonly', path: `/calendars/?locationId=${loc}` },
109
+ { name: 'invoices', scope: 'invoices.readonly', path: `/invoices/?altId=${loc}&altType=location&limit=1` },
110
+ { name: 'payments', scope: 'payments/transactions.readonly', path: `/payments/transactions?altId=${loc}&altType=location&limit=1` },
111
+ ];
112
+
113
+ if (!json) write(`auth check: probing ${LANES.length} GoHighLevel API scopes...\n`);
114
+
115
+ let lanes;
100
116
  try {
101
117
  const http = makeHttp({ pit: creds.pit });
102
- const result = await validateToken(http, creds.loc);
103
- if (!result.ok) {
104
- writeErr(`WARN: token check failed — ${result.reason}\n`);
105
- return EXIT.AUTH;
106
- }
107
- write(`auth check: PIT accepted for location ${creds.loc}\n`);
108
- return EXIT.OK;
118
+ lanes = await mapLimit(LANES, 5, async (lane) => {
119
+ try {
120
+ const r = await http.get(lane.path);
121
+ // 401/403 = scope blocked; 200 or param-error (400/422) = authorized
122
+ const ok = r.code !== 401 && r.code !== 403;
123
+ return { name: lane.name, scope: lane.scope, ok, code: r.code };
124
+ } catch (e) {
125
+ return { name: lane.name, scope: lane.scope, ok: false, code: 0, error: e?.message ?? 'error' };
126
+ }
127
+ });
109
128
  } catch (e) {
110
129
  writeErr(`auth check: could not reach GoHighLevel (${e?.message ?? 'error'})\n`);
111
130
  return EXIT.API;
112
131
  }
132
+
133
+ const okCount = lanes.filter(l => l.ok).length;
134
+ const total = lanes.length;
135
+
136
+ if (json) {
137
+ const contactsOk = lanes.find(l => l.name === 'contacts')?.ok ?? false;
138
+ write(JSON.stringify({
139
+ schemaVersion: 1,
140
+ location: loc,
141
+ lanes: lanes.map(l => ({ name: l.name, scope: l.scope, ok: l.ok, httpCode: l.code })),
142
+ summary: `${okCount}/${total} lanes readable`,
143
+ usable: contactsOk,
144
+ }) + '\n');
145
+ return contactsOk ? EXIT.OK : EXIT.AUTH;
146
+ }
147
+
148
+ // human output — per-lane lines then summary
149
+ for (const l of lanes) {
150
+ if (l.ok) {
151
+ write(` ✅ ${l.name}\n`);
152
+ } else {
153
+ writeErr(` ✖ ${l.name} — add scope ${l.scope}\n`);
154
+ }
155
+ }
156
+
157
+ const missing = lanes.filter(l => !l.ok).map(l => l.scope);
158
+ if (missing.length === 0) {
159
+ write(`\n${okCount}/${total} lanes readable — full brief available\n`);
160
+ } else {
161
+ const missingNames = lanes.filter(l => !l.ok).map(l => l.name).join(', ');
162
+ writeErr(`\n${okCount}/${total} lanes readable — \`brief\` will show ⚠ on ${missingNames} until you add: ${missing.join(', ')}\n`);
163
+ }
164
+
165
+ const contactsOk = lanes.find(l => l.name === 'contacts')?.ok ?? false;
166
+ return contactsOk ? EXIT.OK : EXIT.AUTH;
113
167
  }
114
168
  if (verb !== 'status') return die('usage: sizmo auth status|check', EXIT.USAGE);
115
169
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sizmo",
3
- "version": "0.4.0",
4
- "description": "Unofficial read-only GoHighLevel CLI for coaches and consultants. Not affiliated with HighLevel.",
3
+ "version": "0.4.1",
4
+ "description": "Unofficial read-only GoHighLevel CLI read your CRM (leads, bookings, pipeline, A/R, payments) from the terminal. Not affiliated with HighLevel.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "sizmo": "bin/sizmo.mjs"