@elitedcs/ghl-mcp 3.84.4 → 3.84.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.84.6 — your money on the daily brief
4
+
5
+ The daily brief's Stripe section is now Money: monthly recurring revenue by product (add-ons such as extra seats roll into the product they extend), money received in the last 7 and 30 days before Stripe fees, subscription invoices paid against everything else received, and failed payment attempts. The top of the brief shows recurring revenue and the last 30 days. A Stripe key needs Read on Charges and Products for these figures; a key without them keeps the rest of the section and says which permission to add. Amounts in different currencies are never added together.
6
+
7
+ The Gmail and Google Drive panels are off the cockpit brief. Your Claude reads mail, files and calendar in its own morning brief, and the page ends by pointing there. The chat brief no longer asks Claude to save those findings.
8
+
9
+ ## 3.84.5 — renewals counted again, clearer connection cards
10
+
11
+ Stripe renewals are counted again. Stripe's current API reports when a subscription renews on each of its items rather than on the subscription, so the daily brief showed no renewals due and synced records had no renewal date. Both now read it from the items.
12
+
13
+ A connected card's Instructions now open with what the credential is, so a Slack card still says what a bot token is after it is connected. When Google sign-in is not set up on the machine, the Google Calendar card shows only why and hides steps for a Connect button it cannot offer. The sentences to say to Claude are labelled as an alternative to the screen, not a second set of steps.
14
+
3
15
  ## 3.84.4 — Connections and the daily brief
4
16
 
5
17
  The daily brief page is redesigned: a headline with stat tiles, coloured status chips for every source, a section bar that stays in view, a tick-off list for what needs you today with a progress bar, money in tables, and long lists folded to a count. The cockpit's top bar now wraps on narrow screens instead of running off the edge.
package/README.md CHANGED
@@ -897,7 +897,7 @@ Built by **[Elite DCs, LLC](https://elitedcs.com)** — a digital marketing and
897
897
 
898
898
  **Tech stack:** TypeScript, Node.js, esbuild, MCP SDK, Zod, GHL API v2, Firebase Auth
899
899
 
900
- **Version:** 3.84.4
900
+ **Version:** 3.84.6
901
901
 
902
902
  ---
903
903
 
package/dist/index.js CHANGED
@@ -448,16 +448,16 @@ function isWriteMethod(method) {
448
448
  }
449
449
  function locationIdsInRequest(path42, opts = {}) {
450
450
  const found = /* @__PURE__ */ new Set();
451
- const add = (v) => {
451
+ const add2 = (v) => {
452
452
  if (typeof v === "string" && v.trim()) found.add(v.trim());
453
- else if (Array.isArray(v)) v.forEach(add);
453
+ else if (Array.isArray(v)) v.forEach(add2);
454
454
  };
455
455
  const seg = /\/locations\/([^/?#]+)/i.exec(path42);
456
- if (seg) add(decodeURIComponent(seg[1]));
456
+ if (seg) add2(decodeURIComponent(seg[1]));
457
457
  for (const src of [opts.params, opts.body]) {
458
458
  if (!src || typeof src !== "object") continue;
459
459
  for (const key of ["locationId", "location_id", "locationIds", "location_ids", "targetLocationId"]) {
460
- add(src[key]);
460
+ add2(src[key]);
461
461
  }
462
462
  }
463
463
  return [...found];
@@ -20068,7 +20068,7 @@ var init_registry = __esm({
20068
20068
  CONNECTOR_INSTRUCTION_INTROS = {
20069
20069
  needs: "Have these ready before you connect.",
20070
20070
  connect: "Follow these steps to give this machine access.",
20071
- say: "Say one of these sentences to Claude to use this connection.",
20071
+ say: "In a Claude chat you can say one of these to connect or use it.",
20072
20072
  revoke: "Remove the saved connection or take back the provider's permission."
20073
20073
  };
20074
20074
  CONNECTOR_REGISTRY = [
@@ -20079,13 +20079,13 @@ var init_registry = __esm({
20079
20079
  builtState: "built",
20080
20080
  whatItIs: "A restricted key is a password Stripe issues that lets this machine read only the parts of your account you allow.",
20081
20081
  checkedOn: null,
20082
- gives: "Adds who owes you money, what each client pays and when they renew to your brief.",
20083
- needs: ["Access to your Stripe account's API keys.", "A restricted key with Read access to Customers, Invoices and Subscriptions."],
20082
+ gives: "Adds your monthly recurring revenue, money received, failed payments, who owes you and when clients renew to your brief.",
20083
+ needs: ["Access to your Stripe account's API keys.", "A restricted key with Read access to Customers, Invoices and Subscriptions, plus Charges and Products for money received and revenue by product."],
20084
20084
  steps: [
20085
20085
  "[UNVERIFIED] Sign in to your Stripe Dashboard and choose the account you want to read.",
20086
20086
  "[UNVERIFIED] Open Developers, then API keys. If your dashboard shows Workbench, open its API keys page.",
20087
20087
  "[UNVERIFIED] Under Restricted keys, choose Create restricted key and give it a name you recognise.",
20088
- "[UNVERIFIED] Set Customers, Invoices and Subscriptions to Read. Leave other permissions at None, then create the key.",
20088
+ "[UNVERIFIED] Set Customers, Invoices, Subscriptions, Charges and Products to Read. Leave other permissions at None, then create the key.",
20089
20089
  "[UNVERIFIED] Copy the key into the password field on the Stripe card on the Connections screen, add an account label if useful, and choose Connect. Use a live-mode key for real income."
20090
20090
  ],
20091
20091
  say: ["Connect my Stripe.", "Sync my Stripe.", "Run my morning brief."],
@@ -20129,22 +20129,20 @@ var init_registry = __esm({
20129
20129
  tools: { connect: "connect_google_calendar", disconnect: "connect_google_calendar", read: "read_my_calendar" }
20130
20130
  },
20131
20131
  {
20132
- panel: (deps) => deps.chat("gmail", "Gmail"),
20133
20132
  id: "gmail",
20134
20133
  name: "Gmail",
20135
20134
  builtState: "via-claude",
20136
20135
  whatItIs: "Your Claude's Gmail connection lets Claude read the mail you permit in your conversation.",
20137
20136
  checkedOn: null,
20138
20137
  viaClaude: "Answered by your Claude's Gmail connector; nothing to connect here",
20139
- gives: "Adds client email waiting on you through your Claude's Gmail connector.",
20138
+ gives: "Your Claude reads client email waiting on you in its own morning brief. The cockpit does not show mail.",
20140
20139
  needs: ["Gmail connected in your Claude."],
20141
20140
  steps: [],
20142
- say: ["Run my morning brief and check client email waiting on me through Gmail.", "After checking Gmail, call record_brief_answers to put the results on my brief dashboard."],
20141
+ say: ["Run my morning brief.", "What client email is waiting on me?"],
20143
20142
  revoke: "Disconnect Gmail in your Claude's connector settings.",
20144
20143
  tools: {}
20145
20144
  },
20146
20145
  {
20147
- panel: (deps) => deps.chat("google-drive", "Google Drive"),
20148
20146
  id: "google-drive",
20149
20147
  name: "Google Drive",
20150
20148
  builtState: "via-claude",
@@ -20154,7 +20152,7 @@ var init_registry = __esm({
20154
20152
  gives: "Finds a client's agreement, onboarding form and brand files on request through your Claude's Drive connector.",
20155
20153
  needs: ["Google Drive connected in your Claude."],
20156
20154
  steps: [],
20157
- say: ["Find this client's agreement, onboarding form and brand files in Google Drive, using their name and domain.", "After checking Drive on request, call record_brief_answers to put the results on my brief dashboard."],
20155
+ say: ["Find this client's agreement, onboarding form and brand files in Google Drive, using their name and domain."],
20158
20156
  revoke: "Disconnect Google Drive in your Claude's connector settings.",
20159
20157
  tools: {}
20160
20158
  },
@@ -20491,6 +20489,11 @@ var init_slack = __esm({
20491
20489
  });
20492
20490
 
20493
20491
  // src/command-os/connectors/stripe.ts
20492
+ function periodEnd(sub) {
20493
+ if (typeof sub.current_period_end === "number" && isFinite(sub.current_period_end)) return sub.current_period_end;
20494
+ const ends = (sub.items?.data ?? []).map((i) => i.current_period_end).filter((n) => typeof n === "number" && isFinite(n));
20495
+ return ends.length ? Math.min(...ends) : void 0;
20496
+ }
20494
20497
  function identity(kind, externalId) {
20495
20498
  return { system: SYSTEM, kind, externalId, tenantId: null };
20496
20499
  }
@@ -20541,7 +20544,7 @@ function mapSubscription(sub, observedAt) {
20541
20544
  if (priced.amount !== null) o.push(observe(id, "pays_per_period", priced.amount, observedAt, sub));
20542
20545
  if (priced.cannotState) o.push(observe(id, "price_cannot_be_stated", priced.cannotState, observedAt, sub));
20543
20546
  if (priced.interval) o.push(observe(id, "billing_interval", priced.interval, observedAt, sub));
20544
- const renews = iso(sub.current_period_end);
20547
+ const renews = iso(periodEnd(sub));
20545
20548
  if (renews) o.push(observe(id, "renews_at", renews, observedAt, sub));
20546
20549
  o.push(observe(id, "cancelling_at_period_end", sub.cancel_at_period_end === true, observedAt, sub));
20547
20550
  return { identities: [id], observations: o };
@@ -20617,7 +20620,7 @@ function renewalsWithStale(subs, withinDays = 30, now = /* @__PURE__ */ new Date
20617
20620
  const stale = [];
20618
20621
  for (const s of subs) {
20619
20622
  if (s.status !== "active" && s.status !== "trialing" && s.status !== "past_due") continue;
20620
- const renewsAt = iso(s.current_period_end);
20623
+ const renewsAt = iso(periodEnd(s));
20621
20624
  if (!renewsAt) continue;
20622
20625
  const days = Math.ceil((new Date(renewsAt).getTime() - now.getTime()) / 864e5);
20623
20626
  if (days > withinDays) continue;
@@ -20656,7 +20659,83 @@ async function health(fetcher, now = () => /* @__PURE__ */ new Date()) {
20656
20659
  return { system: SYSTEM, state: "BROKEN", detail: `Stripe read failed: ${msg3.slice(0, 120)}`, checkedAt };
20657
20660
  }
20658
20661
  }
20659
- var SYSTEM, iso, ZERO_DECIMAL, THREE_DECIMAL, fromMinor, upper, stillOwed;
20662
+ function productGroup(name, all) {
20663
+ const parents = all.filter((p) => p !== name && name.startsWith(p) && /^\s*[\-—–:(+]/.test(name.slice(p.length)));
20664
+ return parents.sort((a, b) => b.length - a.length)[0] ?? name;
20665
+ }
20666
+ function moneySummary(subs, invoices, charges, products, now = /* @__PURE__ */ new Date()) {
20667
+ const names = new Map(products.map((p) => [p.id, p.name]));
20668
+ const allNames = [...new Set(products.map((p) => p.name))];
20669
+ const mrr = /* @__PURE__ */ new Map();
20670
+ const groups = /* @__PURE__ */ new Map();
20671
+ let mrrUnpriced = 0;
20672
+ for (const sub of subs) {
20673
+ if (sub.status !== "active" && sub.status !== "past_due") continue;
20674
+ for (const item of sub.items?.data ?? []) {
20675
+ const price = item.price;
20676
+ const currency = upper(price?.currency) ?? upper(sub.currency);
20677
+ const per = PER_MONTH[price?.recurring?.interval ?? ""];
20678
+ const count2 = price?.recurring?.interval_count ?? 1;
20679
+ const unit = fromMinor(price?.unit_amount, currency);
20680
+ if (!currency || per === void 0 || unit === null || !(count2 > 0)) {
20681
+ mrrUnpriced++;
20682
+ continue;
20683
+ }
20684
+ const monthly = unit * (item.quantity ?? 1) * per / count2;
20685
+ add(mrr, currency, monthly);
20686
+ const product = price?.product ? names.get(price.product) ?? "A product no longer in Stripe" : "Unnamed product";
20687
+ const group = productGroup(product, allNames);
20688
+ const key = `${group}\0${currency}`;
20689
+ const g = groups.get(key) ?? { subs: /* @__PURE__ */ new Set(), currency, monthly: 0 };
20690
+ g.subs.add(sub.id);
20691
+ g.monthly += monthly;
20692
+ groups.set(key, g);
20693
+ }
20694
+ }
20695
+ const nowS = now.getTime() / 1e3;
20696
+ const within = (t, days) => t <= nowS && nowS - t <= days * 86400;
20697
+ const r7 = /* @__PURE__ */ new Map(), r30 = /* @__PURE__ */ new Map();
20698
+ let c7 = 0, c30 = 0, failed30 = 0;
20699
+ for (const c of charges) {
20700
+ if (!within(c.created, 30)) continue;
20701
+ if (c.status === "failed") {
20702
+ failed30++;
20703
+ continue;
20704
+ }
20705
+ if (c.status !== "succeeded") continue;
20706
+ const currency = upper(c.currency);
20707
+ const net = fromMinor(c.amount - (c.amount_refunded ?? 0), currency) ?? 0;
20708
+ if (net <= 0) continue;
20709
+ add(r30, currency, net);
20710
+ c30++;
20711
+ if (within(c.created, 7)) {
20712
+ add(r7, currency, net);
20713
+ c7++;
20714
+ }
20715
+ }
20716
+ const recurring = /* @__PURE__ */ new Map();
20717
+ for (const i of invoices) {
20718
+ const paidAt = i.status_transitions?.paid_at;
20719
+ const subId = i.parent?.subscription_details?.subscription ?? i.subscription;
20720
+ if (i.status !== "paid" || !paidAt || !within(paidAt, 30) || !subId) continue;
20721
+ const currency = upper(i.currency);
20722
+ const paid = fromMinor(i.amount_paid, currency);
20723
+ if (currency && paid) add(recurring, currency, paid);
20724
+ }
20725
+ const oneOff = /* @__PURE__ */ new Map();
20726
+ for (const [currency, amount] of r30) oneOff.set(currency, Math.max(0, amount - (recurring.get(currency) ?? 0)));
20727
+ return {
20728
+ mrr: totals(mrr),
20729
+ mrrUnpriced,
20730
+ byProduct: [...groups].map(([key, g]) => ({ name: key.split("\0")[0], subscribers: g.subs.size, currency: g.currency, monthly: Math.round(g.monthly * 100) / 100 })).sort((a, b) => b.monthly - a.monthly),
20731
+ received7: { count: c7, totals: totals(r7) },
20732
+ received30: { count: c30, totals: totals(r30) },
20733
+ recurring30: totals(recurring),
20734
+ oneOff30: totals(oneOff),
20735
+ failed30
20736
+ };
20737
+ }
20738
+ var SYSTEM, iso, ZERO_DECIMAL, THREE_DECIMAL, fromMinor, upper, stillOwed, PER_MONTH, add, totals;
20660
20739
  var init_stripe = __esm({
20661
20740
  "src/command-os/connectors/stripe.ts"() {
20662
20741
  "use strict";
@@ -20676,6 +20755,9 @@ var init_stripe = __esm({
20676
20755
  return t ? t : null;
20677
20756
  };
20678
20757
  stillOwed = (i) => (fromMinor(i.amount_remaining ?? i.amount_due ?? null, upper(i.currency)) ?? 0) > 0;
20758
+ PER_MONTH = { day: 365 / 12, week: 52 / 12, month: 1, year: 1 / 12 };
20759
+ add = (m, currency, amount) => m.set(currency, (m.get(currency) ?? 0) + amount);
20760
+ totals = (m) => [...m].map(([currency, amount]) => ({ currency, amount: Math.round(amount * 100) / 100 }));
20679
20761
  }
20680
20762
  });
20681
20763
 
@@ -21174,7 +21256,7 @@ async function connectSystem({ system, key, label }) {
21174
21256
  system,
21175
21257
  because: h.detail,
21176
21258
  storedAt: null,
21177
- note: "Nothing was saved, so nothing now claims to be connected. Fix the key and run this again \u2014 a Stripe RESTRICTED key needs read access to subscriptions, invoices and customers."
21259
+ note: "Nothing was saved, so nothing now claims to be connected. Fix the key and run this again \u2014 a Stripe RESTRICTED key needs read access to subscriptions, invoices and customers (and charges and products for the money figures)."
21178
21260
  };
21179
21261
  }
21180
21262
  const s = connect(system, key, label, void 0, h.state === "WORKING");
@@ -21239,7 +21321,15 @@ async function readStripe(f) {
21239
21321
  ]);
21240
21322
  return { subs, invoices, customers };
21241
21323
  }
21242
- var import_zod53, Customer, Subscription, Invoice;
21324
+ async function readStripeMoney(f, now) {
21325
+ const since = String(Math.floor(now.getTime() / 1e3) - 30 * 86400);
21326
+ const [charges, products] = await Promise.all([
21327
+ listAll(f, "/v1/charges", Charge, { "created[gte]": since }),
21328
+ listAll(f, "/v1/products", Product)
21329
+ ]);
21330
+ return { charges, products };
21331
+ }
21332
+ var import_zod53, Customer, Subscription, Invoice, Charge, Product;
21243
21333
  var init_stripe_read = __esm({
21244
21334
  "src/command-os/connectors/stripe-read.ts"() {
21245
21335
  "use strict";
@@ -21254,10 +21344,12 @@ var init_stripe_read = __esm({
21254
21344
  cancel_at_period_end: import_zod53.z.boolean().optional(),
21255
21345
  items: import_zod53.z.object({ data: import_zod53.z.array(import_zod53.z.object({
21256
21346
  quantity: import_zod53.z.number().nullable().optional(),
21347
+ current_period_end: import_zod53.z.number().optional(),
21257
21348
  price: import_zod53.z.object({
21349
+ product: import_zod53.z.string().nullable().optional(),
21258
21350
  unit_amount: import_zod53.z.number().nullable().optional(),
21259
21351
  currency: import_zod53.z.string().nullable().optional(),
21260
- recurring: import_zod53.z.object({ interval: import_zod53.z.string().optional() }).nullable().optional()
21352
+ recurring: import_zod53.z.object({ interval: import_zod53.z.string().optional(), interval_count: import_zod53.z.number().optional() }).nullable().optional()
21261
21353
  }).nullable().optional()
21262
21354
  })).optional() }).optional()
21263
21355
  });
@@ -21270,8 +21362,21 @@ var init_stripe_read = __esm({
21270
21362
  amount_remaining: import_zod53.z.number().optional(),
21271
21363
  due_date: import_zod53.z.number().nullable().optional(),
21272
21364
  number: import_zod53.z.string().nullable().optional(),
21273
- hosted_invoice_url: import_zod53.z.string().nullable().optional()
21365
+ hosted_invoice_url: import_zod53.z.string().nullable().optional(),
21366
+ amount_paid: import_zod53.z.number().optional(),
21367
+ subscription: import_zod53.z.string().nullable().optional(),
21368
+ parent: import_zod53.z.object({ subscription_details: import_zod53.z.object({ subscription: import_zod53.z.string().nullable().optional() }).nullable().optional() }).nullable().optional(),
21369
+ status_transitions: import_zod53.z.object({ paid_at: import_zod53.z.number().nullable().optional() }).nullable().optional()
21274
21370
  });
21371
+ Charge = import_zod53.z.object({
21372
+ id: import_zod53.z.string(),
21373
+ amount: import_zod53.z.number(),
21374
+ amount_refunded: import_zod53.z.number().optional(),
21375
+ currency: import_zod53.z.string(),
21376
+ status: import_zod53.z.string(),
21377
+ created: import_zod53.z.number()
21378
+ });
21379
+ Product = import_zod53.z.object({ id: import_zod53.z.string(), name: import_zod53.z.string() });
21275
21380
  }
21276
21381
  });
21277
21382
 
@@ -22367,26 +22472,26 @@ function isHandoffAction(a, nurtureTriggerTags) {
22367
22472
  function collectDefinedRefs(plan) {
22368
22473
  const refs = /* @__PURE__ */ new Map();
22369
22474
  const duplicates = [];
22370
- const add = (ref) => {
22475
+ const add2 = (ref) => {
22371
22476
  if (refs.has(ref)) duplicates.push(ref);
22372
22477
  else refs.set(ref, refNamespace(ref));
22373
22478
  };
22374
- for (const u of plan.users ?? []) add(u.ref);
22479
+ for (const u of plan.users ?? []) add2(u.ref);
22375
22480
  for (const p of plan.pipelines ?? []) {
22376
- add(p.ref);
22377
- for (const s of p.stages) add(s.ref);
22378
- }
22379
- for (const f of plan.customFields ?? []) add(f.ref);
22380
- for (const t of plan.tags ?? []) add(t.ref);
22381
- for (const cv of plan.customValues ?? []) add(cv.ref);
22382
- for (const c of plan.calendars ?? []) add(c.ref);
22383
- for (const fm of plan.forms ?? []) add(fm.ref);
22384
- for (const e of plan.emails ?? []) add(e.ref);
22385
- for (const s of plan.sms ?? []) add(s.ref);
22386
- for (const t of plan.templates?.emails ?? []) add(t.ref);
22387
- for (const t of plan.templates?.sms ?? []) add(t.ref);
22388
- for (const w of plan.workflows ?? []) add(w.ref);
22389
- for (const h of plan.handoffs ?? []) add(h.ref);
22481
+ add2(p.ref);
22482
+ for (const s of p.stages) add2(s.ref);
22483
+ }
22484
+ for (const f of plan.customFields ?? []) add2(f.ref);
22485
+ for (const t of plan.tags ?? []) add2(t.ref);
22486
+ for (const cv of plan.customValues ?? []) add2(cv.ref);
22487
+ for (const c of plan.calendars ?? []) add2(c.ref);
22488
+ for (const fm of plan.forms ?? []) add2(fm.ref);
22489
+ for (const e of plan.emails ?? []) add2(e.ref);
22490
+ for (const s of plan.sms ?? []) add2(s.ref);
22491
+ for (const t of plan.templates?.emails ?? []) add2(t.ref);
22492
+ for (const t of plan.templates?.sms ?? []) add2(t.ref);
22493
+ for (const w of plan.workflows ?? []) add2(w.ref);
22494
+ for (const h of plan.handoffs ?? []) add2(h.ref);
22390
22495
  return { refs, duplicates };
22391
22496
  }
22392
22497
  function checkRefIntegrity(plan, defined) {
@@ -24522,11 +24627,11 @@ var init_build_progress = __esm({
24522
24627
  current;
24523
24628
  updatedAt;
24524
24629
  constructor(plan) {
24525
- const totals = planProgressTotals(plan);
24630
+ const totals2 = planProgressTotals(plan);
24526
24631
  this.tallies = PROGRESS_GROUPS.map(({ key, label }) => ({
24527
24632
  key,
24528
24633
  label,
24529
- total: totals[key],
24634
+ total: totals2[key],
24530
24635
  done: 0,
24531
24636
  deferred: 0,
24532
24637
  failed: 0,
@@ -28406,7 +28511,7 @@ async function readDailyBrief(registry2, options = {}) {
28406
28511
  function registerDailyBriefTools(server2, registry2) {
28407
28512
  const recordTool = server2.tool(
28408
28513
  "record_brief_answers",
28409
- "Save up to 50 rows from your Gmail or Google Drive chat brief on this machine, one source per call. Replaces that source's previous rows; makes no provider calls. Record at most 50 rows per source.",
28514
+ "Save up to 50 Gmail or Google Drive findings from a chat brief as a private file on this machine, one source per call; replaces that source's previous rows and makes no provider calls. The cockpit's Daily brief page does not display them. Call it only when the user asks to save them.",
28410
28515
  BriefAnswersInput.shape,
28411
28516
  async (input) => {
28412
28517
  try {
@@ -28428,7 +28533,7 @@ function registerDailyBriefTools(server2, registry2) {
28428
28533
  },
28429
28534
  async ({ live, hoursBack, quietAfterDays }) => {
28430
28535
  const { brief } = await readDailyBrief(registry2, { live, hoursBack, quietAfterDays });
28431
- return { ...brief, afterBeyondGoHighLevel: "then call record_brief_answers with what you found, one call per source" };
28536
+ return brief;
28432
28537
  }
28433
28538
  );
28434
28539
  }
@@ -28514,16 +28619,16 @@ function livePanel(id, name, read2) {
28514
28619
  };
28515
28620
  }
28516
28621
  function money2(rows2) {
28517
- const totals = /* @__PURE__ */ new Map();
28622
+ const totals2 = /* @__PURE__ */ new Map();
28518
28623
  let unpriced = 0;
28519
28624
  for (const row of rows2) {
28520
28625
  if (row.amount === null || !row.currency) {
28521
28626
  unpriced++;
28522
28627
  continue;
28523
28628
  }
28524
- totals.set(row.currency, (totals.get(row.currency) ?? 0) + row.amount);
28629
+ totals2.set(row.currency, (totals2.get(row.currency) ?? 0) + row.amount);
28525
28630
  }
28526
- return { count: rows2.length, totals: [...totals].map(([currency, amount]) => ({ currency, amount })), unpriced };
28631
+ return { count: rows2.length, totals: [...totals2].map(([currency, amount]) => ({ currency, amount })), unpriced };
28527
28632
  }
28528
28633
  function chatPanel(id, name) {
28529
28634
  return async (_deps) => {
@@ -28547,7 +28652,7 @@ function chatPanel(id, name) {
28547
28652
  async function readBriefPanels(deps) {
28548
28653
  return Promise.all(CONNECTOR_REGISTRY.flatMap((entry) => entry.panel ? [entry.panel(briefPanelDeps(deps))] : []));
28549
28654
  }
28550
- var fs22, path20, import_node_crypto7, import_zod57, MoneySchema, BriefPanelSchema, TimesSchema, timesPath, NotConnected, safeLink, amountText, stripePanel, slackPanel, calendarPanel;
28655
+ var fs22, path20, import_node_crypto7, import_zod57, MoneySchema, TotalsSchema, SummarySchema, BriefPanelSchema, TimesSchema, timesPath, NotConnected, safeLink, amountText, stripePanel, slackPanel, calendarPanel;
28551
28656
  var init_brief_panels = __esm({
28552
28657
  "src/command-os/connectors/brief-panels.ts"() {
28553
28658
  "use strict";
@@ -28567,6 +28672,17 @@ var init_brief_panels = __esm({
28567
28672
  init_daily_brief2();
28568
28673
  init_agency_profile();
28569
28674
  MoneySchema = import_zod57.z.object({ count: import_zod57.z.number().int().nonnegative(), totals: import_zod57.z.array(import_zod57.z.object({ currency: import_zod57.z.string(), amount: import_zod57.z.number() })), unpriced: import_zod57.z.number().int().nonnegative() });
28675
+ TotalsSchema = import_zod57.z.array(import_zod57.z.object({ currency: import_zod57.z.string(), amount: import_zod57.z.number() }));
28676
+ SummarySchema = import_zod57.z.object({
28677
+ mrr: TotalsSchema,
28678
+ mrrUnpriced: import_zod57.z.number().int().nonnegative(),
28679
+ byProduct: import_zod57.z.array(import_zod57.z.object({ name: import_zod57.z.string(), subscribers: import_zod57.z.number().int().nonnegative(), currency: import_zod57.z.string(), monthly: import_zod57.z.number() })),
28680
+ received7: import_zod57.z.object({ count: import_zod57.z.number().int().nonnegative(), totals: TotalsSchema }),
28681
+ received30: import_zod57.z.object({ count: import_zod57.z.number().int().nonnegative(), totals: TotalsSchema }),
28682
+ recurring30: TotalsSchema,
28683
+ oneOff30: TotalsSchema,
28684
+ failed30: import_zod57.z.number().int().nonnegative()
28685
+ });
28570
28686
  BriefPanelSchema = import_zod57.z.object({
28571
28687
  id: import_zod57.z.string(),
28572
28688
  name: import_zod57.z.string(),
@@ -28578,7 +28694,7 @@ var init_brief_panels = __esm({
28578
28694
  why: import_zod57.z.string().optional(),
28579
28695
  rows: import_zod57.z.array(BriefRowSchema),
28580
28696
  needsYou: import_zod57.z.array(BriefRowSchema),
28581
- money: import_zod57.z.object({ owed: MoneySchema, renewals: MoneySchema }).optional()
28697
+ money: import_zod57.z.object({ owed: MoneySchema, renewals: MoneySchema, summary: SummarySchema.optional() }).optional()
28582
28698
  });
28583
28699
  TimesSchema = import_zod57.z.record(import_zod57.z.string(), import_zod57.z.iso.datetime({ offset: true }));
28584
28700
  timesPath = () => path20.join(appDataDir(), "brief-read-times.json");
@@ -28596,7 +28712,15 @@ var init_brief_panels = __esm({
28596
28712
  stripePanel = livePanel("stripe", "Stripe", async ({ now, signal }) => {
28597
28713
  if (!listConnectors({ strict: true }).some((c) => c.system === "stripe")) throw new NotConnected();
28598
28714
  return withKey("stripe", async (key) => {
28599
- const result = await readStripe(stripeFetcher(key, signal));
28715
+ const fetcher = stripeFetcher(key, signal);
28716
+ const [result, paid] = await Promise.all([readStripe(fetcher), readStripeMoney(fetcher, now).then(
28717
+ (value) => ({ value, why: "" }),
28718
+ (error) => {
28719
+ if (error instanceof Error && error.name === "TimeoutError" || signal?.aborted) throw error;
28720
+ const text2 = error instanceof Error ? error.message : "";
28721
+ return { value: null, why: /401|403|permission/i.test(text2) ? "Money figures need Read access to Charges and Products on your Stripe key; add them in Stripe and the figures appear on the next refresh." : /more records than this read can cover/.test(text2) ? "Money figures are not shown: this account has more payments or products than one read covers." : "Money figures could not be read this time; try Refresh." };
28722
+ }
28723
+ )]);
28600
28724
  const names = new Map(result.customers.map((c) => [c.id, c.name || c.email || c.id]));
28601
28725
  const owed = outstanding(result.invoices, now);
28602
28726
  const renewals = renewalsWithStale(result.subs, 30, now);
@@ -28608,8 +28732,12 @@ var init_brief_panels = __esm({
28608
28732
  return {
28609
28733
  rows: [...owed.map(invoiceRow), ...renewals.due.map((r) => ({ text: `${names.get(r.customerId) ?? r.customerId}: ${r.cancelling ? "cancels" : "renews"} at ${amountText(r.amount, r.currency)}`, when: r.renewsAt }))],
28610
28734
  needsYou: owed.filter((i) => i.daysOverdue !== null).map(invoiceRow),
28611
- money: { owed: money2(owed), renewals: money2(renewals.due) },
28612
- ...renewals.stale.length ? { why: `${renewals.stale.length} subscriptions have a past period end; they are excluded from renewals due.` } : {}
28735
+ money: {
28736
+ owed: money2(owed),
28737
+ renewals: money2(renewals.due),
28738
+ ...paid.value ? { summary: moneySummary(result.subs, result.invoices, paid.value.charges, paid.value.products, now) } : {}
28739
+ },
28740
+ ...renewals.stale.length || paid.why ? { why: [paid.why, renewals.stale.length ? `${renewals.stale.length} subscriptions have a past period end; they are excluded from renewals due.` : ""].filter(Boolean).join(" ") } : {}
28613
28741
  };
28614
28742
  });
28615
28743
  });
@@ -28971,7 +29099,7 @@ var require_package = __commonJS({
28971
29099
  "package.json"(exports2, module2) {
28972
29100
  module2.exports = {
28973
29101
  name: "@elitedcs/ghl-mcp",
28974
- version: "3.84.4",
29102
+ version: "3.84.6",
28975
29103
  mcpName: "io.github.drjerryrelth/ghl-command",
28976
29104
  description: "GoHighLevel MCP Server for Claude. 250 tools \u2014 full CRM, automation, marketing control, account-wide workflow audit, live funnel-capture verification, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
28977
29105
  main: "dist/index.js",
@@ -29350,6 +29478,7 @@ main.brief{max-width:64rem;margin:auto;padding:0 1.2rem 4rem;color:var(--ink);li
29350
29478
  .brief .stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));gap:.7rem;margin-top:1.4rem}
29351
29479
  .brief .stat{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);padding:.9rem 1rem}
29352
29480
  .brief .stat .big{display:block;font-family:var(--font-display);font-weight:600;font-size:1.5rem;line-height:1.15;color:var(--heading);font-variant-numeric:tabular-nums}
29481
+ .brief .stat.warn .big{color:var(--warn)}.brief .money-stats{margin:0 0 1rem}
29353
29482
  .brief .stat .lbl{display:block;font-size:var(--fs-label);letter-spacing:.12em;text-transform:uppercase;color:var(--ink-3);margin-top:.3rem}
29354
29483
  .brief .stats-meta{margin:.8rem 0 0;font-size:var(--fs-sm);color:var(--ink-3)}
29355
29484
  .brief blockquote{margin:1.2rem 0 0;padding:.2rem 0 .2rem 1rem;border-left:3px solid var(--gold);font-family:var(--font-display);font-size:1.25rem;line-height:1.4;color:var(--heading)}
@@ -29444,6 +29573,8 @@ const panelDate=p=>{
29444
29573
  return line;
29445
29574
  };
29446
29575
  const panelTone=p=>p.state==="read"?"ok":p.state==="not-connected"?"wait":p.state==="not-recorded"?"draft":"warn";
29576
+ const cash=(t)=>{try{return new Intl.NumberFormat(undefined,{style:"currency",currency:t.currency,maximumFractionDigits:Number.isInteger(t.amount)?0:2}).format(t.amount)}catch{return t.currency+" "+t.amount.toFixed(2)}};
29577
+ const cashAll=ts=>ts.length?ts.map(cash).join(" + "):cash({currency:"USD",amount:0});
29447
29578
  const amount=m=>m.totals.length?m.totals.map(t=>t.currency+" "+t.amount.toFixed(2)).join(" + "):m.count?"Total not stated":"No amount due";
29448
29579
  const plural=(n,one,many)=>n+" "+(n===1?one:many);
29449
29580
  /* The work queue's titles carry the client's name ("Fix 2 things the verify
@@ -29517,8 +29648,10 @@ function draw(){
29517
29648
  tile((b.readFrom?.accounts||0)+" of "+data.accounts.total,"accounts read");
29518
29649
  tile(needs.length,"needs you today");
29519
29650
  const stripe=data.panels.find(p=>p.id==="stripe");const money=stripe?.ok?stripe.money:null;
29520
- if(money){tile(amount(money.owed),"owed on "+plural(money.owed.count,"open invoice","open invoices"));tile(money.renewals.count,"renewals within 30 days")}
29521
- else{tile("Not read","open invoices owed");tile("Not read","renewals within 30 days")}
29651
+ const sum=money?.summary;
29652
+ if(sum){tile(cashAll(sum.mrr),"monthly recurring revenue");tile(cashAll(sum.received30.totals),"received before Stripe fees, last 30 days")}
29653
+ else if(money){tile(amount(money.owed),"owed on "+plural(money.owed.count,"open invoice","open invoices"));tile(money.renewals.count,"renewals within 30 days")}
29654
+ else{tile("Not read","monthly recurring revenue");tile("Not read","received before Stripe fees, last 30 days")}
29522
29655
  tile(data.accounts.unread,"accounts could not be read");
29523
29656
  tiles.appendChild(el("p",readLine(),"stats-meta"));
29524
29657
  const tb=el("div","","tiles-bar");const total=data.accounts.total||0,got=b.readFrom?.accounts||0;tb.appendChild(el("span",got+" of "+total+" accounts read","lbl"));const bar=el("div","","bar");const fill=el("i");fill.style.width=(total?Math.round(100*got/total):0)+"%";bar.appendChild(fill);tb.appendChild(bar);tiles.appendChild(tb);
@@ -29559,10 +29692,20 @@ function draw(){
29559
29692
  list(card(lc,"By source"),(b.leadsBySource||[]).map(r=>({text:r.client+": "+r.line})),"No source breakdown available.");
29560
29693
  /* One panel per connected system */
29561
29694
  data.panels.forEach(p=>{
29562
- const s=section(root,"panel-"+p.id,"Connected system",p.name,"",panelDate(p));
29695
+ const isMoney=p.id==="stripe";
29696
+ const s=section(root,"panel-"+p.id,isMoney?"From Stripe":"Connected system",isMoney?"Money":p.name,"",panelDate(p));
29563
29697
  const head=el("div","","panel-head");head.appendChild(el("p","Results from "+p.name+" for this brief.","intro"));
29564
29698
  const button=el("button","Refresh "+p.name,"refresh");button.type="button";button.dataset.refresh=p.id;button.onclick=()=>refreshPanel(p.id,button);head.appendChild(button);s.appendChild(head);
29565
29699
  const c=card(s,"");
29700
+ const sm=p.id==="stripe"&&p.ok?p.money?.summary:null;
29701
+ if(sm){const g=el("div","","stats money-stats");const st=(v,l,w)=>{const b=el("div","","stat"+(w?" warn":""));b.appendChild(el("strong",String(v),"big"));b.appendChild(el("span",l,"lbl"));g.appendChild(b)};
29702
+ st(cashAll(sm.mrr),"monthly recurring revenue");st(cashAll(sm.received7.totals),"received before Stripe fees, last 7 days \xB7 "+plural(sm.received7.count,"payment","payments"));
29703
+ st(cashAll(sm.received30.totals),"received before Stripe fees, last 30 days \xB7 "+plural(sm.received30.count,"payment","payments"));st(sm.failed30,"failed payment attempts, last 30 days (Stripe retries count each time)",sm.failed30>0);c.appendChild(g);
29704
+ if(sm.byProduct.length){const t=el("table","","rows");const hr=el("tr");["Product","Subscribers","Monthly"].forEach(h=>hr.appendChild(el("th",h)));t.appendChild(hr);
29705
+ sm.byProduct.forEach(r=>{const tr=el("tr");tr.appendChild(el("td",r.name));tr.appendChild(el("td",String(r.subscribers),"num"));tr.appendChild(el("td",cash({currency:r.currency,amount:r.monthly}),"num"));t.appendChild(tr)});c.appendChild(t)}
29706
+ const split=el("table");const srow=(k,v)=>{const tr=el("tr");tr.appendChild(el("th",k));tr.appendChild(el("td",v,"num"));split.appendChild(tr)};
29707
+ srow("Subscription invoices paid, last 30 days",cashAll(sm.recurring30));srow("Everything else received, last 30 days",cashAll(sm.oneOff30));c.appendChild(split);
29708
+ c.appendChild(el("p","MRR is list price per month for active and past-due subscriptions. Everything else received is money in minus subscription invoices paid, never below zero."+(sm.mrrUnpriced?" "+plural(sm.mrrUnpriced,"line has no fixed price and is","lines have no fixed price and are")+" not counted.":""),"intro"))}
29566
29709
  if(p.id==="stripe"&&p.ok&&p.money){const t=el("table");const row=(k,v)=>{const tr=el("tr");tr.appendChild(el("th",k));tr.appendChild(el("td",v,"num"));t.appendChild(tr)};
29567
29710
  row(plural(p.money.owed.count,"open invoice","open invoices"),amount(p.money.owed));row(plural(p.money.renewals.count,"renewal within 30 days","renewals within 30 days"),amount(p.money.renewals));
29568
29711
  if(p.money.owed.unpriced||p.money.renewals.unpriced)row("Amounts not stated",String(p.money.owed.unpriced+p.money.renewals.unpriced));c.appendChild(t)}
@@ -29570,6 +29713,7 @@ function draw(){
29570
29713
  if(p.id==="stripe")rowsTable(c,marked.map(splitRow),["Customer","What","When"],p.ok?"No items returned by this source.":"");
29571
29714
  else list(c,marked,p.ok?"No items returned by this source.":"");
29572
29715
  });
29716
+ const mail=section(root,"mail","Mail, files and calendar","In your Claude brief","");mail.appendChild(el("p","Your Claude reads your mail, files and calendar in its own morning brief. Ask it: \u201CRun my morning brief.\u201D","intro"));
29573
29717
  highlight();
29574
29718
  }
29575
29719
  /* The active pill follows the section in view. */
@@ -29673,15 +29817,15 @@ async function loadBriefDashboard(registry2, now = /* @__PURE__ */ new Date(), s
29673
29817
  }
29674
29818
  async function secretsKnownToThisProcess(registry2, extras = []) {
29675
29819
  const secrets = /* @__PURE__ */ new Set();
29676
- const add = (key) => {
29820
+ const add2 = (key) => {
29677
29821
  if (typeof key !== "string" || !key.trim()) throw new Error("A saved credential could not be read.");
29678
29822
  secrets.add(key);
29679
29823
  };
29680
29824
  const addOptional = (key) => {
29681
29825
  if (typeof key === "string" && !key.trim()) return;
29682
- add(key);
29826
+ add2(key);
29683
29827
  };
29684
- extras.forEach(add);
29828
+ extras.forEach(add2);
29685
29829
  const credentialFields = [
29686
29830
  ["license_key", "GHL_LICENSE_KEY"],
29687
29831
  ["ghl_api_key", "GHL_API_KEY"],
@@ -29702,14 +29846,14 @@ async function secretsKnownToThisProcess(registry2, extras = []) {
29702
29846
  }
29703
29847
  for (const { system } of listConnectors({ strict: true })) {
29704
29848
  await withKey(system, async (key) => {
29705
- add(key);
29849
+ add2(key);
29706
29850
  });
29707
29851
  }
29708
29852
  if (calendarConnectionStatus().connected) {
29709
29853
  await withCalendarSecret(async (secret) => {
29710
- add(secret.clientId);
29711
- add(secret.clientSecret);
29712
- add(secret.refreshToken);
29854
+ add2(secret.clientId);
29855
+ add2(secret.clientSecret);
29856
+ add2(secret.refreshToken);
29713
29857
  });
29714
29858
  }
29715
29859
  if (registry2) {
@@ -29721,7 +29865,7 @@ async function secretsKnownToThisProcess(registry2, extras = []) {
29721
29865
  for (const { locationId: locationId2 } of locations) {
29722
29866
  const token2 = registry2.getToken(locationId2);
29723
29867
  if (!token2) throw new Error("Account credentials could not be read.");
29724
- add(token2.apiKey);
29868
+ add2(token2.apiKey);
29725
29869
  }
29726
29870
  const agency = registry2.getAgencyKey();
29727
29871
  if (agency !== void 0) addOptional(agency);
@@ -29868,9 +30012,8 @@ function cardHtml(c) {
29868
30012
  return `<section class="connection" id="${c.id}" data-system="${c.id}">
29869
30013
  <h2 class="${proven ? "compact" : ""}">${esc2(c.name)}</h2>
29870
30014
  <p><span class="dot ${c.readError ? "saved-unproven" : c.state}" aria-hidden="true"></span><span class="status">${esc2(statusText(c))}</span> <span class="needs-marker"${needsYou(c) ? "" : " hidden"}>Needs you today</span></p>
29871
- <p class="credential-definition"${proven ? " hidden" : ""}>${esc2(entry.whatItIs)}</p>
29872
- <button class="instructions-toggle" type="button" aria-controls="instructions-${c.id}" aria-expanded="${proven ? "false" : "true"}"${proven ? "" : " hidden"}>Instructions</button>
29873
- <div class="connector-instructions" id="instructions-${c.id}"${proven ? " hidden" : ""}>${connectorInstructions(entry, "html", { includeDefinition: false })}</div>
30015
+ <button class="instructions-toggle" type="button" aria-controls="instructions-${c.id}" aria-expanded="${!proven && !needsSetup}"${proven && !needsSetup ? "" : " hidden"}>Instructions</button>
30016
+ <div class="connector-instructions" id="instructions-${c.id}"${proven || needsSetup ? " hidden" : ""}>${connectorInstructions(entry, "html")}</div>
29874
30017
  ${google ? `<p class="setup-notice"${needsSetup ? "" : " hidden"}>${esc2(CALENDAR_SETUP_MESSAGE)}</p>` : ""}
29875
30018
  <div class="action-controls">
29876
30019
  <button class="replace-toggle" type="button" aria-controls="connect-${c.id}" aria-expanded="false"${proven && !needsSetup ? "" : " hidden"}>${google ? "Reconnect" : "Replace key"}</button>
@@ -29897,7 +30040,7 @@ main.connections{max-width:50rem;margin:0 auto;padding:1.6rem 1.2rem 4rem;color:
29897
30040
  .connections label{display:block;margin:.7rem 0 .25rem}.connections input{width:100%;padding:.5rem .6rem;border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--bg);color:var(--ink);font:inherit}
29898
30041
  .connections button{background:var(--gold);color:var(--on-accent);border-radius:var(--radius-sm);padding:.6rem 1.1rem;font:inherit;font-weight:600;margin:.7rem .4rem .7rem 0}
29899
30042
  .connections button:disabled{opacity:.5;cursor:wait}
29900
- .connector-instructions{margin:1rem 0}.connector-instructions>.credential-definition{display:none}
30043
+ .connector-instructions{margin:1rem 0}.credential-definition{font-weight:600}
29901
30044
  .connections h4{font-size:inherit;margin:.7rem 0}.instructions-checked{margin-bottom:.7rem}
29902
30045
  .dot{display:inline-block;width:.6rem;height:.6rem;border-radius:50%;margin-right:.4rem;background:var(--wait)}
29903
30046
  .dot.proven{background:var(--ok)}.dot.saved-unproven{background:var(--warn)}.dot.grey{background:#8A857D}
@@ -29949,8 +30092,7 @@ function update(next,fresh){
29949
30092
  el.querySelector('.dot').className='dot '+(c.readError?'saved-unproven':c.state);
29950
30093
  var proven=c.state==='proven',needsSetup=c.id==='google-calendar'&&c.setup==='needs-setup';
29951
30094
  el.querySelector('h2').classList.toggle('compact',proven);
29952
- el.querySelector('.credential-definition').hidden=proven;
29953
- resetControl(el,'.instructions-toggle',proven,!proven);
30095
+ resetControl(el,'.instructions-toggle',proven&&!needsSetup,!proven&&!needsSetup);
29954
30096
  var pendingGoogle=c.id==='google-calendar'&&Boolean(googleSignInUrl);
29955
30097
  resetControl(el,'.replace-toggle',proven&&!needsSetup,pendingGoogle||(!proven&&!needsSetup));
29956
30098
  var link=el.querySelector('.sign-in');
@@ -31307,7 +31449,7 @@ function scrubForClient(text2) {
31307
31449
  }
31308
31450
  function clientNextSteps(items) {
31309
31451
  const steps = [];
31310
- const add = (s) => {
31452
+ const add2 = (s) => {
31311
31453
  if (s && !steps.includes(s)) steps.push(s);
31312
31454
  };
31313
31455
  for (const it of items) {
@@ -31315,12 +31457,12 @@ function clientNextSteps(items) {
31315
31457
  if (it.state === "waiting") {
31316
31458
  const who = scrubForClient(it.resolvedBy ?? "");
31317
31459
  if (who) {
31318
- add(`${it.title}: ${who}${it.roughly ? ` \u2014 roughly ${scrubForClient(it.roughly)}` : ""}`);
31460
+ add2(`${it.title}: ${who}${it.roughly ? ` \u2014 roughly ${scrubForClient(it.roughly)}` : ""}`);
31319
31461
  continue;
31320
31462
  }
31321
31463
  }
31322
31464
  const rule5 = NEXT_STEP_RULES.find((r) => r.re.test(it.sentence));
31323
- add(rule5 ? rule5.step : `Confirm "${it.title}" with your team once the item above has been sorted.`);
31465
+ add2(rule5 ? rule5.step : `Confirm "${it.title}" with your team once the item above has been sorted.`);
31324
31466
  }
31325
31467
  return steps;
31326
31468
  }
@@ -34640,7 +34782,7 @@ function compute(ans, skip, asm) {
34640
34782
  O.push(o);
34641
34783
  return o;
34642
34784
  }
34643
- function add(id, title2, needs, assumption, fn, fix) {
34785
+ function add2(id, title2, needs, assumption, fn, fix) {
34644
34786
  var missing = needs.filter(function(n) {
34645
34787
  return (n.share ? share(n.id) : raw(n.id)) === null;
34646
34788
  }).map(function(n) {
@@ -34671,7 +34813,7 @@ function compute(ans, skip, asm) {
34671
34813
  }
34672
34814
  F.push({ id, title: title2, monthly: m, assumption: assumption || null, fix });
34673
34815
  }
34674
- add(
34816
+ add2(
34675
34817
  "missed",
34676
34818
  "Calls you never get back to",
34677
34819
  [{ id: "calls_missed" }, { id: "missed_never_return", share: true }],
@@ -34681,7 +34823,7 @@ function compute(ans, skip, asm) {
34681
34823
  },
34682
34824
  "Call answering, day and night"
34683
34825
  );
34684
- add(
34826
+ add2(
34685
34827
  "waited",
34686
34828
  "Enquiries that waited",
34687
34829
  [{ id: "waited_month" }],
@@ -34699,7 +34841,7 @@ function compute(ans, skip, asm) {
34699
34841
  reason: "you told us more quotes were won than were sent \u2014 worth checking before this is priced"
34700
34842
  });
34701
34843
  } else {
34702
- add(
34844
+ add2(
34703
34845
  "quotes",
34704
34846
  "Quotes nobody chases",
34705
34847
  [{ id: "quotes_month" }, { id: "quotes_won" }],
@@ -34710,7 +34852,7 @@ function compute(ans, skip, asm) {
34710
34852
  "Follow-up that runs itself"
34711
34853
  );
34712
34854
  }
34713
- add(
34855
+ add2(
34714
34856
  "noshow",
34715
34857
  "No-shows nobody rebooks",
34716
34858
  [{ id: "booked_month" }, { id: "show_rate", share: true }],
@@ -50549,18 +50691,18 @@ function owedByClient(owing, identities, links, nameFor2) {
50549
50691
  const byCurrency = /* @__PURE__ */ new Map();
50550
50692
  const unattributedByCurrency = /* @__PURE__ */ new Map();
50551
50693
  let unattributedCount = 0;
50552
- const add = (m, currency, amount) => {
50694
+ const add2 = (m, currency, amount) => {
50553
50695
  const c = currency ?? "unstated";
50554
50696
  m.set(c, (m.get(c) ?? 0) + amount);
50555
50697
  };
50556
50698
  for (const o of owing) {
50557
50699
  const tenant = tenantOfCustomer.get(o.customerId);
50558
50700
  if (!tenant) {
50559
- add(unattributedByCurrency, o.currency, o.amount);
50701
+ add2(unattributedByCurrency, o.currency, o.amount);
50560
50702
  unattributedCount += 1;
50561
50703
  continue;
50562
50704
  }
50563
- add(byCurrency, o.currency, o.amount);
50705
+ add2(byCurrency, o.currency, o.amount);
50564
50706
  lines.push({
50565
50707
  client: nameFor2(tenant),
50566
50708
  amount: o.amount,
@@ -51632,7 +51774,7 @@ function isIdShapedKey(key) {
51632
51774
  }
51633
51775
  function scanArgs(args, maxNodes = MAX_ARG_NODES) {
51634
51776
  const out = { ids: [], locations: [], unknown: [], uncheckable: [] };
51635
- const add = (kind, v, key) => {
51777
+ const add2 = (kind, v, key) => {
51636
51778
  const list2 = Array.isArray(v) ? v : [v];
51637
51779
  for (const x of list2) {
51638
51780
  const id = typeof x === "number" ? String(x) : x;
@@ -51668,28 +51810,28 @@ function scanArgs(args, maxNodes = MAX_ARG_NODES) {
51668
51810
  if (k === "locationId") {
51669
51811
  if (typeof x === "string" && x.length > 0) out.locations.push(x);
51670
51812
  } else if (k === "traffic_weightage" && isRecord10(x)) {
51671
- add("user", Object.keys(x), k);
51813
+ add2("user", Object.keys(x), k);
51672
51814
  continue;
51673
51815
  } else if (k === "traffic_index" && Array.isArray(x)) {
51674
- add("user", x.map((e) => isRecord10(e) ? e.id : void 0), k);
51816
+ add2("user", x.map((e) => isRecord10(e) ? e.id : void 0), k);
51675
51817
  } else if (k === "selectedUser") {
51676
- if (v.userType !== "all") add("user", x, k);
51818
+ if (v.userType !== "all") add2("user", x, k);
51677
51819
  } else if (k === "field" && inFields) {
51678
51820
  if (typeof x === "string") {
51679
51821
  const standardKey = STANDARD_CONTACT_FIELDS.has(x.toLowerCase());
51680
- if (!standardKey || isIdShaped(x)) add("custom_field", x, "fields[].field");
51822
+ if (!standardKey || isIdShaped(x)) add2("custom_field", x, "fields[].field");
51681
51823
  }
51682
51824
  } else if (k === "customFields" && Array.isArray(x)) {
51683
- add("custom_field", x.map((e) => isRecord10(e) ? e.id : void 0), "customFields[].id");
51825
+ add2("custom_field", x.map((e) => isRecord10(e) ? e.id : void 0), "customFields[].id");
51684
51826
  } else if (k === "customField" && isRecord10(x)) {
51685
- add("custom_field", x.id, "customField.id");
51827
+ add2("custom_field", x.id, "customField.id");
51686
51828
  } else if (Object.hasOwn(ID_ARG_KINDS, k)) {
51687
- add(ID_ARG_KINDS[k], x, k);
51829
+ add2(ID_ARG_KINDS[k], x, k);
51688
51830
  } else if (isIdShapedKey(k) && !Object.hasOwn(NOT_CHECKABLE_ID_ARGS, k) && hasValue(x)) {
51689
51831
  out.unknown.push({ key: k, value: JSON.stringify(x).slice(0, 80), ...nodeType ? { nodeType } : {} });
51690
51832
  }
51691
51833
  if ((k === "filterField" || k === "field" && !inFields) && typeof x === "string" && Object.hasOwn(CONDITION_FIELD_KINDS, x)) {
51692
- add(CONDITION_FIELD_KINDS[x], Object.hasOwn(v, "secondValue") ? v.secondValue : v.value, `${k} ${x}`);
51834
+ add2(CONDITION_FIELD_KINDS[x], Object.hasOwn(v, "secondValue") ? v.secondValue : v.value, `${k} ${x}`);
51693
51835
  }
51694
51836
  if (x && typeof x === "object") stack.push({ v: x, inFields: k === "fields" && Array.isArray(x), nodeType });
51695
51837
  }
@@ -53766,11 +53908,11 @@ function previewShowsPlanBody(preview, planHtml) {
53766
53908
  }
53767
53909
  function bindingIdentities(plan) {
53768
53910
  const result = [];
53769
- const add = (type, ref, name, identity2 = name) => result.push({ ref, type, name, identity: `${type}:${norm5(identity2)}` });
53770
- for (const u of planUsers(plan)) add("user", u.ref, userDisplayName(u), u.email);
53911
+ const add2 = (type, ref, name, identity2 = name) => result.push({ ref, type, name, identity: `${type}:${norm5(identity2)}` });
53912
+ for (const u of planUsers(plan)) add2("user", u.ref, userDisplayName(u), u.email);
53771
53913
  for (const p of plan.pipelines ?? []) {
53772
- add("pipeline", p.ref, p.name);
53773
- for (const stage of p.stages) add("stage", stage.ref, stage.name, `${p.ref}:${p.name}:${stage.name}`);
53914
+ add2("pipeline", p.ref, p.name);
53915
+ for (const stage of p.stages) add2("stage", stage.ref, stage.name, `${p.ref}:${p.name}:${stage.name}`);
53774
53916
  }
53775
53917
  for (const [type, objects] of [
53776
53918
  ["field", plan.customFields],
@@ -53781,7 +53923,7 @@ function bindingIdentities(plan) {
53781
53923
  ["workflow", plan.workflows],
53782
53924
  ["email_template", planEmailTemplates(plan)],
53783
53925
  ["sms_template", planSmsTemplates(plan)]
53784
- ]) for (const obj of objects ?? []) add(type, obj.ref, obj.name);
53926
+ ]) for (const obj of objects ?? []) add2(type, obj.ref, obj.name);
53785
53927
  return result;
53786
53928
  }
53787
53929
  function matchingPriorBindings(plan, previous, ids = {}) {
package/guide/guide.html CHANGED
@@ -4,7 +4,7 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>GHL Command — User Guide</title>
7
- <!-- guides-hash: b475d5f4d5112179 -->
7
+ <!-- guides-hash: b807bb53aa200532 -->
8
8
  <style>
9
9
  /* Deliberately light in every environment, including a dark-mode OS. This is a
10
10
  reference document people read at length, print, and save to PDF, and a page
@@ -1209,12 +1209,12 @@ Before you build: look up the real calendar, the real workflows you are removing
1209
1209
  <p class="gpara">A restricted key is a password Stripe issues that lets this machine read only the parts of your account you allow.</p>
1210
1210
  <p class="gpara">not yet checked</p>
1211
1211
  <h3>What it gives you</h3>
1212
- <p class="gpara">Adds who owes you money, what each client pays and when they renew to your brief.</p>
1212
+ <p class="gpara">Adds your monthly recurring revenue, money received, failed payments, who owes you and when clients renew to your brief.</p>
1213
1213
  <h3>What you need</h3>
1214
1214
  <p class="gpara">Have these ready before you connect.</p>
1215
1215
  <ul>
1216
1216
  <li>Access to your Stripe account's API keys.</li>
1217
- <li>A restricted key with Read access to Customers, Invoices and Subscriptions.</li>
1217
+ <li>A restricted key with Read access to Customers, Invoices and Subscriptions, plus Charges and Products for money received and revenue by product.</li>
1218
1218
  </ul>
1219
1219
  <h3>How to connect</h3>
1220
1220
  <p class="gpara">Follow these steps to give this machine access.</p>
@@ -1223,11 +1223,11 @@ Before you build: look up the real calendar, the real workflows you are removing
1223
1223
  <li>Sign in to your Stripe Dashboard and choose the account you want to read. (not yet checked on a real screen)</li>
1224
1224
  <li>Open Developers, then API keys. If your dashboard shows Workbench, open its API keys page. (not yet checked on a real screen)</li>
1225
1225
  <li>Under Restricted keys, choose Create restricted key and give it a name you recognise. (not yet checked on a real screen)</li>
1226
- <li>Set Customers, Invoices and Subscriptions to Read. Leave other permissions at None, then create the key. (not yet checked on a real screen)</li>
1226
+ <li>Set Customers, Invoices, Subscriptions, Charges and Products to Read. Leave other permissions at None, then create the key. (not yet checked on a real screen)</li>
1227
1227
  <li>Copy the key into the password field on the Stripe card on the Connections screen, add an account label if useful, and choose Connect. Use a live-mode key for real income. (not yet checked on a real screen)</li>
1228
1228
  </ol>
1229
1229
  <h3>What to say</h3>
1230
- <p class="gpara">Say one of these sentences to Claude to use this connection.</p>
1230
+ <p class="gpara">In a Claude chat you can say one of these to connect or use it.</p>
1231
1231
  <ul>
1232
1232
  <li>“Connect my Stripe.”</li>
1233
1233
  <li>“Sync my Stripe.”</li>
@@ -1261,7 +1261,7 @@ Before you build: look up the real calendar, the real workflows you are removing
1261
1261
  <li>In Slack, open each public channel you want read and invite the app with /invite followed by its name. (not yet checked on a real screen)</li>
1262
1262
  </ol>
1263
1263
  <h3>What to say</h3>
1264
- <p class="gpara">Say one of these sentences to Claude to use this connection.</p>
1264
+ <p class="gpara">In a Claude chat you can say one of these to connect or use it.</p>
1265
1265
  <ul>
1266
1266
  <li>“Connect my Slack.”</li>
1267
1267
  <li>“Show me what is waiting in Slack.”</li>
@@ -1292,7 +1292,7 @@ Before you build: look up the real calendar, the real workflows you are removing
1292
1292
  <li>Return to the Google Calendar card and choose Finish. Your sign-in is saved only after your calendars can be read.</li>
1293
1293
  </ol>
1294
1294
  <h3>What to say</h3>
1295
- <p class="gpara">Say one of these sentences to Claude to use this connection.</p>
1295
+ <p class="gpara">In a Claude chat you can say one of these to connect or use it.</p>
1296
1296
  <ul>
1297
1297
  <li>“Connect my own Google Calendar.”</li>
1298
1298
  <li>“Finish connecting my Google Calendar.”</li>
@@ -1305,7 +1305,7 @@ Before you build: look up the real calendar, the real workflows you are removing
1305
1305
  <div class="gsec"><h2>Gmail</h2>
1306
1306
  <p class="gpara">Your Claude's Gmail connection lets Claude read the mail you permit in your conversation.</p>
1307
1307
  <h3>What it gives you</h3>
1308
- <p class="gpara">Adds client email waiting on you through your Claude's Gmail connector.</p>
1308
+ <p class="gpara">Your Claude reads client email waiting on you in its own morning brief. The cockpit does not show mail.</p>
1309
1309
  <h3>What you need</h3>
1310
1310
  <p class="gpara">Have these ready before you connect.</p>
1311
1311
  <ul>
@@ -1314,10 +1314,10 @@ Before you build: look up the real calendar, the real workflows you are removing
1314
1314
  <h3>How to connect</h3>
1315
1315
  <p class="gpara">Answered by your Claude's Gmail connector; nothing to connect here</p>
1316
1316
  <h3>What to say</h3>
1317
- <p class="gpara">Say one of these sentences to Claude to use this connection.</p>
1317
+ <p class="gpara">In a Claude chat you can say one of these to connect or use it.</p>
1318
1318
  <ul>
1319
- <li>“Run my morning brief and check client email waiting on me through Gmail.”</li>
1320
- <li>“After checking Gmail, call record&#95;brief&#95;answers to put the results on my brief dashboard.”</li>
1319
+ <li>“Run my morning brief.”</li>
1320
+ <li>“What client email is waiting on me?”</li>
1321
1321
  </ul>
1322
1322
  <h3>How to take access back</h3>
1323
1323
  <p class="gpara">Remove the saved connection or take back the provider's permission.</p>
@@ -1335,10 +1335,9 @@ Before you build: look up the real calendar, the real workflows you are removing
1335
1335
  <h3>How to connect</h3>
1336
1336
  <p class="gpara">Answered by your Claude's Drive connector; nothing to connect here</p>
1337
1337
  <h3>What to say</h3>
1338
- <p class="gpara">Say one of these sentences to Claude to use this connection.</p>
1338
+ <p class="gpara">In a Claude chat you can say one of these to connect or use it.</p>
1339
1339
  <ul>
1340
1340
  <li>“Find this client's agreement, onboarding form and brand files in Google Drive, using their name and domain.”</li>
1341
- <li>“After checking Drive on request, call record&#95;brief&#95;answers to put the results on my brief dashboard.”</li>
1342
1341
  </ul>
1343
1342
  <h3>How to take access back</h3>
1344
1343
  <p class="gpara">Remove the saved connection or take back the provider's permission.</p>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elitedcs/ghl-mcp",
3
- "version": "3.84.4",
3
+ "version": "3.84.6",
4
4
  "mcpName": "io.github.drjerryrelth/ghl-command",
5
5
  "description": "GoHighLevel MCP Server for Claude. 250 tools — full CRM, automation, marketing control, account-wide workflow audit, live funnel-capture verification, and the only programmatic GHL workflow builder, now multi-tenant across client accounts.",
6
6
  "main": "dist/index.js",