@elitedcs/ghl-mcp 3.84.9 → 3.84.10

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,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.84.10 — confirming a call's numbers no longer files the call twice
4
+
5
+ When you answered "confirm all" after bringing in a client call, the call was filed a second time: a duplicate call file marked "(2)" and the same call added to "Client understanding" twice. Now the confirmation adds one dated "Confirmed by you" section to the call you already filed, and to Client understanding, listing the numbers that saved. If the first call was never filed, the whole call is filed then, so nothing is lost. The reply says the numbers saved only when they actually did.
6
+
3
7
  ## 3.84.9 — build steps you can read
4
8
 
5
9
  Each client's build now shows a progress bar, the next step across the full width with one clear button, and one readable row per step, instead of seven narrow columns. Nothing about how building works changed: the same steps, checks and typed confirmations. Step descriptions now say what each step really does. Verify checks the links between workflows, pipelines and fields and tests the form, and reports anything it cannot check.
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.9
900
+ **Version:** 3.84.10
901
901
 
902
902
  ---
903
903
 
package/dist/index.js CHANGED
@@ -29058,7 +29058,7 @@ var require_package = __commonJS({
29058
29058
  "package.json"(exports2, module2) {
29059
29059
  module2.exports = {
29060
29060
  name: "@elitedcs/ghl-mcp",
29061
- version: "3.84.9",
29061
+ version: "3.84.10",
29062
29062
  mcpName: "io.github.drjerryrelth/ghl-command",
29063
29063
  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.",
29064
29064
  main: "dist/index.js",
@@ -39495,7 +39495,8 @@ __export(client_calls_exports, {
39495
39495
  gateAnalysis: () => gateAnalysis,
39496
39496
  processClientCall: () => processClientCall,
39497
39497
  safeName: () => safeName,
39498
- writeClientFileLocally: () => writeClientFileLocally
39498
+ writeClientFileLocally: () => writeClientFileLocally,
39499
+ writeConfirmationLocally: () => writeConfirmationLocally
39499
39500
  });
39500
39501
  function gateAnalysis(transcript, analysis) {
39501
39502
  const rejected = [];
@@ -39722,6 +39723,37 @@ function writeClientFileLocally(baseDir, file2) {
39722
39723
  return { written: false, error: `could not write the client file: ${e?.message ?? String(e)}` };
39723
39724
  }
39724
39725
  }
39726
+ function writeConfirmationLocally(baseDir, file2) {
39727
+ const c = file2.confirmation;
39728
+ if (!c) return writeClientFileLocally(baseDir, file2);
39729
+ try {
39730
+ if (!path32.isAbsolute(baseDir)) return { written: false, error: `the client files folder must be a full path, not "${baseDir}".` };
39731
+ const base = path32.resolve(baseDir);
39732
+ const callPath = path32.resolve(base, ...file2.call.folderParts, c.callName);
39733
+ const understandingPath = path32.resolve(base, ...file2.understanding.folderParts, file2.understanding.name);
39734
+ if (!inside(base, callPath) || !inside(base, understandingPath)) return { written: false, error: "the client folder name would land outside the client files folder." };
39735
+ let st;
39736
+ try {
39737
+ st = fs34.lstatSync(callPath);
39738
+ } catch {
39739
+ }
39740
+ if (!st) return writeClientFileLocally(baseDir, { ...file2, confirmation: void 0 });
39741
+ if (st.isSymbolicLink() || !st.isFile() || st.nlink > 1) return { written: false, error: `${callPath} is a link, not a plain file, so nothing was added to it.` };
39742
+ if (!inside(fs34.realpathSync(base), fs34.realpathSync(callPath))) return { written: false, error: "the call file resolves outside the client files folder, so nothing was added." };
39743
+ fs34.appendFileSync(callPath, c.appendToCall, { encoding: "utf8", mode: 384 });
39744
+ let u;
39745
+ try {
39746
+ u = fs34.lstatSync(understandingPath);
39747
+ } catch {
39748
+ }
39749
+ if (u && (u.isSymbolicLink() || !u.isFile() || u.nlink > 1)) return { written: false, error: `${understandingPath} is a link, not a plain file, so it was not appended to. The call file was updated: ${callPath}` };
39750
+ if (u) fs34.appendFileSync(understandingPath, c.appendToUnderstanding, { encoding: "utf8", mode: 384 });
39751
+ else fs34.writeFileSync(understandingPath, file2.understanding.headerIfNew + c.appendToUnderstanding, { encoding: "utf8", flag: "wx", mode: 384 });
39752
+ return { written: true, callFile: callPath, transcriptFile: "", understandingFile: understandingPath };
39753
+ } catch (e) {
39754
+ return { written: false, error: `could not add the confirmation to the client file: ${e?.message ?? String(e)}` };
39755
+ }
39756
+ }
39725
39757
  function contactNames(raw) {
39726
39758
  const c = raw?.contact ?? {};
39727
39759
  const s = (k) => typeof c[k] === "string" && c[k].trim() ? c[k].trim() : "";
@@ -39916,8 +39948,32 @@ To confirm:
39916
39948
  ${confirmList(gated.held).map((l) => `- ${l}`).join("\n")}
39917
39949
  ${CONFIRM_INSTRUCTION}`;
39918
39950
  const clientName = save?.businessName ?? save?.contactName ?? clean(input.businessName);
39951
+ const confirmPass = (input.confirmed?.length ?? 0) > 0;
39919
39952
  const clientFile = clientName ? buildClientFile({ clientName, date, source: input.source, gated, analysis, transcript: input.transcript, saveLine, confirmedByOwner: save?.confirmedByOwner }) : null;
39920
- const localCopy = clientFile && deps.clientFilesDir ? writeClientFileLocally(deps.clientFilesDir, clientFile) : null;
39953
+ if (clientFile && confirmPass) {
39954
+ const today2 = now.toISOString().slice(0, 10);
39955
+ const confirmedIds = new Set(save?.confirmedByOwner ?? []);
39956
+ const lines = save?.status === "SAVED" ? gated.accepted.filter((a) => confirmedIds.has(a.questionId)).map((a) => `${ASK.get(a.questionId) ?? a.questionId} **${a.value}**. They said: ${q2(a.quote)}`) : [];
39957
+ const body = lines.length ? `Saved on the GoHighLevel contact ${save?.contactId}:
39958
+ ${bullet(lines)}` : `Not saved on the contact: ${save?.reason ?? save?.status ?? "no contact was named"}.`;
39959
+ clientFile.confirmation = {
39960
+ callName: clientFile.call.name,
39961
+ appendToCall: `
39962
+
39963
+ ## Confirmed by you, ${today2}
39964
+
39965
+ ${body}
39966
+ `,
39967
+ appendToUnderstanding: `
39968
+
39969
+ ### ${today2}: numbers from the ${date} call confirmed by you
39970
+
39971
+ ${body}
39972
+ `
39973
+ };
39974
+ clientFile.howToWrite = `This call confirms answers from a call you have probably already filed. First SEARCH the client's Calls folder for "${clientFile.call.name}". If it is there, append clientFile.confirmation.appendToCall to it and clientFile.confirmation.appendToUnderstanding to Client understanding, and write nothing else. If it is NOT there, the call was never filed: write the whole call as follows. ${clientFile.howToWrite}`;
39975
+ }
39976
+ const localCopy = clientFile && deps.clientFilesDir ? clientFile.confirmation ? writeConfirmationLocally(deps.clientFilesDir, clientFile) : writeClientFileLocally(deps.clientFilesDir, clientFile) : null;
39921
39977
  const { applied, ...legacyRest } = legacyOf(gated);
39922
39978
  return {
39923
39979
  // Never hand back unchecked values beside a failed save (review D-3).
@@ -39929,7 +39985,7 @@ ${CONFIRM_INSTRUCTION}`;
39929
39985
  source: noteSource ? { ...noteSource, dateFromRecording: fromRecording } : null,
39930
39986
  analysis: analysis ? { ...analysis.kept, leftOutForQuote: analysis.rejected, forTheOwnerOnly: true } : null,
39931
39987
  clientFile,
39932
- clientFileNote: clientFile ? null : "No client name was known (no contact, no businessName), so no client file entry was made.",
39988
+ clientFileNote: !clientFile ? "No client name was known (no contact, no businessName), so no client file entry was made." : clientFile.confirmation ? `This call confirms answers from an earlier call. Add only the "Confirmed by you" sections to that call's file and to Client understanding; do not file the call again.${save?.status === "SAVED" ? " The confirmed answers are on the contact." : " The confirmed answers did NOT save on the contact; see the reply."}` : null,
39933
39989
  localCopy
39934
39990
  };
39935
39991
  }
@@ -40245,7 +40301,7 @@ function registerAssessmentTools(server2, client) {
40245
40301
  safeTool(
40246
40302
  server2,
40247
40303
  "read_assessment_transcript",
40248
- `Bring a recorded client call (Plaud or Zoom through your own connection, or a transcript file the owner exports from any other recorder and attaches) into the client's assessment, their needs analysis and their client file. The flow: 1) get the transcript yourself, with your own Plaud or Zoom connection or from the attached file; 2) propose assessment answers, each with the client's words copied VERBATIM from the transcript as the quote; for a text question the value is the client's own words taken from that quote, and for a number it is the number they said, written in digits (Plaud writes "a hundred percent"; send "100%"); 3) write the needs analysis, every need, unnamed problem and recommended solution tied to a verbatim quote of at least three words; 4) call this with the client's contactId and the source; 5) write the returned clientFile to the owner's Google Drive with your Drive connection, following clientFile.howToWrite; 6) show the owner the reply as given, including the "To confirm" list. EVERY NUMBER waits for the owner's OK and never saves on its own, because no word check can tell a figure that answers the question from one said in passing. Text answers in the client's own words save straight away. When the owner answers the list ("confirm all", or "confirm all except ..."), call again with the same transcript, proposed and contactId, and confirmed holding exactly the approved items (questionId and value from toConfirm); for an item marked replacesSaved, also set replace: true on that proposed answer. Never put an item in confirmed that the owner has not approved in this conversation. Drive allows two folders with the same name, so SEARCH for each folder in the path (and for the client's folder above all) before creating it, and never create a second tree. To append to "Client understanding": read it, add the new dated section at the end, write it back, then read it again and confirm every earlier section is still there. Answers and analysis points whose quote is not in the transcript are thrown out. With a contactId the saved answers are read back: the reply starts SAVED, NOT SAVED, NOT CONFIRMED or NOTHING TO SAVE, and only SAVED means they are on the contact. The needs analysis is advice for the owner and is never sent to the client. Without a contactId nothing is saved.`,
40304
+ `Bring a recorded client call (Plaud or Zoom through your own connection, or a transcript file the owner exports from any other recorder and attaches) into the client's assessment, their needs analysis and their client file. The flow: 1) get the transcript yourself, with your own Plaud or Zoom connection or from the attached file; 2) propose assessment answers, each with the client's words copied VERBATIM from the transcript as the quote; for a text question the value is the client's own words taken from that quote, and for a number it is the number they said, written in digits (Plaud writes "a hundred percent"; send "100%"); 3) write the needs analysis, every need, unnamed problem and recommended solution tied to a verbatim quote of at least three words; 4) call this with the client's contactId and the source; 5) write the returned clientFile to the owner's Google Drive with your Drive connection, following clientFile.howToWrite (on the confirmation call below, follow clientFile.howToWrite exactly: it adds a "Confirmed by you" section to the call already filed instead of filing it again); 6) show the owner the reply as given, including the "To confirm" list. EVERY NUMBER waits for the owner's OK and never saves on its own, because no word check can tell a figure that answers the question from one said in passing. Text answers in the client's own words save straight away. When the owner answers the list ("confirm all", or "confirm all except ..."), call again with the same transcript, proposed and contactId, and confirmed holding exactly the approved items (questionId and value from toConfirm); for an item marked replacesSaved, also set replace: true on that proposed answer. Never put an item in confirmed that the owner has not approved in this conversation. Drive allows two folders with the same name, so SEARCH for each folder in the path (and for the client's folder above all) before creating it, and never create a second tree. To append to "Client understanding": read it, add the new dated section at the end, write it back, then read it again and confirm every earlier section is still there. Answers and analysis points whose quote is not in the transcript are thrown out. With a contactId the saved answers are read back: the reply starts SAVED, NOT SAVED, NOT CONFIRMED or NOTHING TO SAVE, and only SAVED means they are on the contact. The needs analysis is advice for the owner and is never sent to the client. Without a contactId nothing is saved.`,
40249
40305
  {
40250
40306
  transcript: import_zod61.z.string().min(20).describe("The recording, as text. Paste the whole thing."),
40251
40307
  proposed: import_zod61.z.array(import_zod61.z.object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elitedcs/ghl-mcp",
3
- "version": "3.84.9",
3
+ "version": "3.84.10",
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",