@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 +12 -0
- package/README.md +1 -1
- package/dist/index.js +233 -91
- package/guide/guide.html +12 -13
- package/package.json +1 -1
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
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
|
|
451
|
+
const add2 = (v) => {
|
|
452
452
|
if (typeof v === "string" && v.trim()) found.add(v.trim());
|
|
453
|
-
else if (Array.isArray(v)) v.forEach(
|
|
453
|
+
else if (Array.isArray(v)) v.forEach(add2);
|
|
454
454
|
};
|
|
455
455
|
const seg = /\/locations\/([^/?#]+)/i.exec(path42);
|
|
456
|
-
if (seg)
|
|
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
|
-
|
|
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: "
|
|
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
|
|
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
|
|
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: "
|
|
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
|
|
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."
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 ?? [])
|
|
22479
|
+
for (const u of plan.users ?? []) add2(u.ref);
|
|
22375
22480
|
for (const p of plan.pipelines ?? []) {
|
|
22376
|
-
|
|
22377
|
-
for (const s of p.stages)
|
|
22378
|
-
}
|
|
22379
|
-
for (const f of plan.customFields ?? [])
|
|
22380
|
-
for (const t of plan.tags ?? [])
|
|
22381
|
-
for (const cv of plan.customValues ?? [])
|
|
22382
|
-
for (const c of plan.calendars ?? [])
|
|
22383
|
-
for (const fm of plan.forms ?? [])
|
|
22384
|
-
for (const e of plan.emails ?? [])
|
|
22385
|
-
for (const s of plan.sms ?? [])
|
|
22386
|
-
for (const t of plan.templates?.emails ?? [])
|
|
22387
|
-
for (const t of plan.templates?.sms ?? [])
|
|
22388
|
-
for (const w of plan.workflows ?? [])
|
|
22389
|
-
for (const h of plan.handoffs ?? [])
|
|
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
|
|
24630
|
+
const totals2 = planProgressTotals(plan);
|
|
24526
24631
|
this.tallies = PROGRESS_GROUPS.map(({ key, label }) => ({
|
|
24527
24632
|
key,
|
|
24528
24633
|
label,
|
|
24529
|
-
total:
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
28629
|
+
totals2.set(row.currency, (totals2.get(row.currency) ?? 0) + row.amount);
|
|
28525
28630
|
}
|
|
28526
|
-
return { count: rows2.length, totals: [...
|
|
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
|
|
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: {
|
|
28612
|
-
|
|
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.
|
|
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
|
-
|
|
29521
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
29826
|
+
add2(key);
|
|
29683
29827
|
};
|
|
29684
|
-
extras.forEach(
|
|
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
|
-
|
|
29849
|
+
add2(key);
|
|
29706
29850
|
});
|
|
29707
29851
|
}
|
|
29708
29852
|
if (calendarConnectionStatus().connected) {
|
|
29709
29853
|
await withCalendarSecret(async (secret) => {
|
|
29710
|
-
|
|
29711
|
-
|
|
29712
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
29872
|
-
<
|
|
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}.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
50701
|
+
add2(unattributedByCurrency, o.currency, o.amount);
|
|
50560
50702
|
unattributedCount += 1;
|
|
50561
50703
|
continue;
|
|
50562
50704
|
}
|
|
50563
|
-
|
|
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
|
|
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
|
-
|
|
51813
|
+
add2("user", Object.keys(x), k);
|
|
51672
51814
|
continue;
|
|
51673
51815
|
} else if (k === "traffic_index" && Array.isArray(x)) {
|
|
51674
|
-
|
|
51816
|
+
add2("user", x.map((e) => isRecord10(e) ? e.id : void 0), k);
|
|
51675
51817
|
} else if (k === "selectedUser") {
|
|
51676
|
-
if (v.userType !== "all")
|
|
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))
|
|
51822
|
+
if (!standardKey || isIdShaped(x)) add2("custom_field", x, "fields[].field");
|
|
51681
51823
|
}
|
|
51682
51824
|
} else if (k === "customFields" && Array.isArray(x)) {
|
|
51683
|
-
|
|
51825
|
+
add2("custom_field", x.map((e) => isRecord10(e) ? e.id : void 0), "customFields[].id");
|
|
51684
51826
|
} else if (k === "customField" && isRecord10(x)) {
|
|
51685
|
-
|
|
51827
|
+
add2("custom_field", x.id, "customField.id");
|
|
51686
51828
|
} else if (Object.hasOwn(ID_ARG_KINDS, k)) {
|
|
51687
|
-
|
|
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
|
-
|
|
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
|
|
53770
|
-
for (const u of planUsers(plan))
|
|
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
|
-
|
|
53773
|
-
for (const stage of p.stages)
|
|
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 ?? [])
|
|
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:
|
|
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
|
|
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
|
|
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">
|
|
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">
|
|
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">
|
|
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">
|
|
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">
|
|
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
|
|
1320
|
-
<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">
|
|
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_brief_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.
|
|
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",
|