@elitedcs/ghl-mcp 3.83.4 → 3.83.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
@@ -2,6 +2,57 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 3.83.6 — what a design run actually costs you, measured
6
+
7
+ 3.83.5 gave a running job a clock. This gives it an honest number on it.
8
+
9
+ How long a run takes was written by hand in three places and every one was wrong.
10
+ The brand page said a deck takes "roughly ten minutes". The run API said "twenty
11
+ minutes or more". The clock added in 3.83.5 said "ten to twenty". Nobody had ever
12
+ timed one.
13
+
14
+ Two runs have now been measured. A six-slide deck took 12 minutes 39 seconds and
15
+ $1.74 for a single pass. A conversion page on a real brand took 26 minutes and
16
+ $3.75 across three. So a pass is 9 to 13 minutes and $1.25 to $1.75, and a run may
17
+ use three of them.
18
+
19
+ That sentence now lives in one file and every screen reads it, so the three cannot
20
+ drift apart again.
21
+
22
+ **The cost is now stated before you press the button, not discovered afterwards.**
23
+ These runs use your own Claude, never ours. On a Max plan that is usage rather than
24
+ a bill, which is exactly why the figure belongs on the screen: on pay as you go it
25
+ is real money, and you should know roughly what a button costs before you press it.
26
+
27
+ ## 3.83.5 — the brand page's buttons work, and a run tells you it is working
28
+
29
+ If you use Design Studio, every button on a brand's own page has been dead since
30
+ 3.78.0. "Make it" did nothing but load a blank page. So did "Save changes", which
31
+ means any accent, font, name or logo you edited there was thrown away without
32
+ saying so. Nine days, five releases. One piece of broken punctuation in the page's
33
+ script switched all of it off at once, and because a browser discards a script like
34
+ that in silence, nothing on screen ever said so.
35
+
36
+ Both are fixed, and a new check now reads every page the cockpit serves and refuses
37
+ to ship one whose script will not run.
38
+
39
+ A design run also used to tell you nothing at all while it worked. Claude writes the
40
+ whole file in a single answer, so the log stayed empty for ten or twenty minutes,
41
+ and there was no way to tell a working run from a dead one. The sensible thing to do
42
+ was stop it, which was the wrong thing to do. The run now shows a clock, counts the
43
+ passes, and says plainly that nothing will appear until a pass finishes.
44
+
45
+ While reading what gets sent to Claude on those runs, we found the brief was asking
46
+ for two tools that are deliberately switched off for them, and then telling Claude
47
+ in the next line that it had no tools. It no longer does that.
48
+
49
+ **A correction to 3.83.4.** That release said a form field written without its id,
50
+ key and data type "is stored and shows as raw data instead of a rendered field."
51
+ That is wrong, and we tested it after we shipped it rather than before. A field
52
+ without those still renders normally in the form editor. What they actually do is
53
+ tie the answer to the contact record, which is reason enough to set them, and
54
+ `update_form` now says only that.
55
+
5
56
  ## 3.83.4 — one door for every write, so "protected" cannot quietly stop meaning protected
6
57
 
7
58
  3.83.3 fixed the holes. This closes the shape that kept making them.
@@ -28,6 +79,11 @@ RENDER rather than only how to send one. A custom question needs the real custom
28
79
  field's id, key and data type from your account; a field written without them is
29
80
  stored and shows as raw data instead of a rendered field.
30
81
 
82
+ > **Corrected 2026-09-12, in 3.83.5.** The last sentence is wrong. It was tested
83
+ > after this release shipped, not before: a field without those keys still renders
84
+ > normally in the form editor. They tie the answer to the contact record. That is
85
+ > what `update_form` says now.
86
+
31
87
  ## 3.83.3 — an account you marked protected is now actually protected
32
88
 
33
89
  **Marking a client account protected did not stop fourteen write paths from writing to it.**
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.83.4
900
+ **Version:** 3.83.6
901
901
 
902
902
  ---
903
903
 
package/dist/index.js CHANGED
@@ -17177,7 +17177,7 @@ function registerFormBuilderTools(server2, builderClient, publicClient) {
17177
17177
  );
17178
17178
  server2.tool(
17179
17179
  "update_form",
17180
- "Update a form's structure \u2014 fields, labels, conditional logic, auto-responder, email notifications, styling. Use get_form_full first to read the current form, modify the formData object, then pass it here. If name is omitted, the current name is preserved (one extra GET). IMPORTANT \u2014 a NEW field will not render in GoHighLevel's form editor unless it carries the binding GHL needs: a custom-question field must include the real custom field's `id`, `fieldKey` and `dataType` from THIS account (read them with get_custom_fields first; do not invent them), and a standard field (first_name, last_name, email, phone) needs `standard: true` with its `tag`. A field written without that binding is stored but shows as raw data instead of a rendered field. These shapes were reverse-engineered from live UI-built forms; copy an existing field from get_form_full rather than composing one from memory.",
17180
+ "Update a form's structure \u2014 fields, labels, conditional logic, auto-responder, email notifications, styling. Use get_form_full first to read the current form, modify the formData object, then pass it here. If name is omitted, the current name is preserved (one extra GET). IMPORTANT \u2014 a custom-question field should carry the real custom field's `id`, `fieldKey` and `dataType` from THIS account (read them with get_custom_fields; do not invent them), and a standard field (first_name, last_name, email, phone) needs `standard: true` with its `tag`. That binding is what ties the answer to the contact record. A field WITHOUT it still renders in the form editor \u2014 verified live on 2026-09-12, so do not expect a missing binding to show up as a broken field. Copy an existing field from get_form_full rather than composing one from memory; these shapes were reverse-engineered from live UI-built forms.",
17181
17181
  {
17182
17182
  formId: import_zod44.z.string().describe("The form ID to update."),
17183
17183
  formData: import_zod44.z.record(import_zod44.z.string(), import_zod44.z.unknown()).describe("The updated formData object containing form fields, settings, autoResponder config, etc. Fields live at formData.form.fields; each one needs the binding described above or the editor cannot render it."),
@@ -21368,7 +21368,7 @@ function tokensCss(d, theme) {
21368
21368
  ];
21369
21369
  return [...light, "", ...dark].join("\n");
21370
21370
  }
21371
- function composeBrief(d, brief) {
21371
+ function composeBrief(d, brief, packOpts = {}) {
21372
21372
  const conversionMode = brief.options.pageMode === "conversion" && brief.conversion;
21373
21373
  const { design, antiDefault } = rulesFor(brief.medium, "html", conversionMode ? "conversion" : "standard");
21374
21374
  const opts = brief.options;
@@ -21485,7 +21485,7 @@ ${cleanLogo.slice(0, 6e3)}` : d.logo?.dataUri ? `Logo markup: <img src="${d.logo
21485
21485
  "## Before you finish",
21486
21486
  ...checklist.map((c) => `- [ ] ${c}`),
21487
21487
  "",
21488
- `Write the whole file to \`${fileName}\`. Then run lint_design_contract, then verify_design; fix what they name; stop after three passes and report.`
21488
+ ...packOpts.tools === false ? [] : [`Write the whole file to \`${fileName}\`. Then run lint_design_contract, then verify_design; fix what they name; stop after three passes and report.`]
21489
21489
  ].join("\n");
21490
21490
  return { fileName, markdown, tokensCss: css2, rules: design, antiDefault, contract: chosenContract, checklist, bannedTells };
21491
21491
  }
@@ -27187,6 +27187,20 @@ var init_run_jobs = __esm({
27187
27187
  }
27188
27188
  });
27189
27189
 
27190
+ // src/command-os/design/run-expectations.ts
27191
+ var RUN_MAX_PASSES, RUN_PASS_MINUTES, RUN_PASS_COST, RUN_TOTAL_MINUTES, RUN_TOTAL_COST, RUN_EXPECTATION;
27192
+ var init_run_expectations = __esm({
27193
+ "src/command-os/design/run-expectations.ts"() {
27194
+ "use strict";
27195
+ RUN_MAX_PASSES = 3;
27196
+ RUN_PASS_MINUTES = "9 to 13 minutes";
27197
+ RUN_PASS_COST = "$1.25 to $1.75";
27198
+ RUN_TOTAL_MINUTES = "25 to 40 minutes";
27199
+ RUN_TOTAL_COST = "about $5";
27200
+ RUN_EXPECTATION = `Each pass takes ${RUN_PASS_MINUTES} and costs ${RUN_PASS_COST} of your own Claude usage, and a run may take up to ${RUN_MAX_PASSES} passes. Allow ${RUN_TOTAL_MINUTES} and up to ${RUN_TOTAL_COST}.`;
27201
+ }
27202
+ });
27203
+
27190
27204
  // src/run-cost.ts
27191
27205
  function runCostPath() {
27192
27206
  return path18.join(appDataDir(), "command-os-run-cost.jsonl");
@@ -28591,7 +28605,7 @@ var require_package = __commonJS({
28591
28605
  "package.json"(exports2, module2) {
28592
28606
  module2.exports = {
28593
28607
  name: "@elitedcs/ghl-mcp",
28594
- version: "3.83.4",
28608
+ version: "3.83.6",
28595
28609
  mcpName: "io.github.drjerryrelth/ghl-command",
28596
28610
  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.",
28597
28611
  main: "dist/index.js",
@@ -32369,7 +32383,7 @@ ${d.voice.samples.length ? `<h2>Voice</h2><ul>${d.voice.samples.map((s) => `<li>
32369
32383
  </div>
32370
32384
  ${opts.screenshot ? `<h2>Their site, as rendered</h2><img class="shot" src="/brand/${encodeURIComponent(d.slug)}/screenshot.png" alt="Screenshot of ${esc5(d.name)}'s website">` : ""}
32371
32385
  <h2>Make something in this brand</h2>
32372
- <p class="small">Runs on <b>your own Claude</b>, on this computer \u2014 never our key. A deck takes roughly ten minutes, a page twenty or more; the run keeps going if you close this tab. Write the headline and the points you want made, not instructions.</p>
32386
+ <p class="small">Runs on <b>your own Claude</b>, on this computer, never our key. ${esc5(RUN_EXPECTATION)} The run keeps going if you close this tab. Write the headline and the points you want made, not instructions.</p>
32373
32387
  <form id="make"><div class="two">
32374
32388
  <div><label for="mk-medium">What</label><select id="mk-medium"><option value="deck">A deck</option><option value="site">A landing page</option><option value="report">A report</option></select></div>
32375
32389
  <div><label for="mk-slides">Slides (a deck only)</label><input id="mk-slides" type="number" min="1" max="40" value="6"></div>
@@ -32397,8 +32411,19 @@ ${d.provenance.source ? `<p class="small">Source: ${esc5(d.provenance.source.url
32397
32411
  ${opts.dir ? `<p class="small">Stored at ${esc5(opts.dir)}</p>` : ""}
32398
32412
  ${foot(SHELL_JS(opts.seatToken ?? "") + `
32399
32413
  (function(){
32400
- var poll=null,runId=null;
32401
- function stopPolling(){if(poll){clearInterval(poll);poll=null}}
32414
+ var poll=null,runId=null,started=0,ticker=null,passes=0;
32415
+ var EXPECT=${scriptJson(RUN_EXPECTATION)};
32416
+ function stopPolling(){if(poll){clearInterval(poll);poll=null}if(ticker){clearInterval(ticker);ticker=null}}
32417
+ function elapsed(ms){var s=Math.floor(ms/1000),m=Math.floor(s/60);s=s%60;return m?(m+"m "+(s<10?"0":"")+s+"s"):(s+"s")}
32418
+ /* The run prints NOTHING while Claude writes - it answers in one go, so the log
32419
+ box sits still for ten minutes or more. Silence used to be indistinguishable
32420
+ from a dead run, and the rational move was to press Stop on a working one
32421
+ (BUG-2026-09-12-03). So the cockpit keeps its own clock: it needs nothing
32422
+ from the child process, and it ticks every second, not every poll. */
32423
+ function tick(){
32424
+ if(!started)return;
32425
+ say("mk-status","Working on your Claude: "+elapsed(Date.now()-started)+" so far"+(passes?(", on pass "+(passes+1)):"")+". Claude writes the whole file in one answer, so nothing appears below until a pass finishes. "+EXPECT+" Closing this tab does not stop it.");
32426
+ }
32402
32427
  function show(t){var el=document.getElementById("mk-log");el.hidden=false;el.textContent=t}
32403
32428
  document.getElementById("make").addEventListener("submit",async function(e){
32404
32429
  e.preventDefault();
@@ -32415,24 +32440,25 @@ document.getElementById("make").addEventListener("submit",async function(e){
32415
32440
  var r=await post("/api/design/run",body);
32416
32441
  runId=r.id;
32417
32442
  document.getElementById("mk-cancel").hidden=false;
32418
- say("mk-status","Running on your Claude. This keeps going if you close the tab.");
32443
+ started=Date.now();passes=0;tick();ticker=setInterval(tick,1000);
32419
32444
  poll=setInterval(async function(){
32420
32445
  var s=await (await fetch("/api/design/run?id="+encodeURIComponent(runId))).json();
32421
- show((s.tail||[]).join("
32422
- "));
32446
+ var lines=s.tail||[];
32447
+ show(lines.join("\\n"));
32448
+ passes=lines.filter(function(l){return l.trim().slice(0,5)==="pass "}).length;
32423
32449
  if(s.status!=="running"){
32424
32450
  stopPolling();go.disabled=false;document.getElementById("mk-cancel").hidden=true;
32425
32451
  var run=s.run||{};
32426
32452
  var design=run.ok?"design PASS on pass "+run.greenAt:"design NOT GREEN";
32427
- var copy=run.copy?(run.copy.error?" \xB7 copy not graded":" \xB7 copy "+run.copy.verdict+(run.copy.firstBreakdown?" (first breakdown: "+run.copy.firstBreakdown+")"):"")):"";
32453
+ var copy=run.copy?(run.copy.error?" \xB7 copy not graded":" \xB7 copy "+run.copy.verdict+(run.copy.firstBreakdown?" (first breakdown: "+run.copy.firstBreakdown+")":"")):"";
32428
32454
  say("mk-status",s.status==="done"?design+copy+" \xB7 "+(run.file||s.outDir):"The run stopped: "+design+copy,s.status==="done"&&run.ok?"ok":"err");
32429
32455
  }
32430
32456
  },4000);
32431
- }catch(err){say("mk-status",err.message,"err");go.disabled=false}
32457
+ }catch(err){stopPolling();started=0;say("mk-status",err.message,"err");go.disabled=false}
32432
32458
  });
32433
32459
  document.getElementById("mk-cancel").addEventListener("click",async function(){
32434
32460
  if(!runId)return;
32435
- try{await post("/api/design/cancel",{id:runId});stopPolling();say("mk-status","Stopped.");document.getElementById("mk-go").disabled=false;this.hidden=true}catch(e){say("mk-status",e.message,"err")}
32461
+ try{await post("/api/design/cancel",{id:runId});stopPolling();started=0;say("mk-status","Stopped.");document.getElementById("mk-go").disabled=false;this.hidden=true}catch(e){say("mk-status",e.message,"err")}
32436
32462
  });
32437
32463
  })();
32438
32464
  document.getElementById("override").addEventListener("submit",async(e)=>{e.preventDefault();const g=(id)=>document.getElementById(id).value.trim();const body={slug:${scriptJson(d.slug)}};if(g("accent"))body.accent=g("accent");if(g("displayFont"))body.displayFont=g("displayFont");if(g("bodyFont"))body.bodyFont=g("bodyFont");if(g("brandName"))body.name=g("brandName");if(g("logoSvg"))body.logoSvg=g("logoSvg");if(Object.keys(body).length===1){say("save-status","Nothing to change.");return}const b=document.getElementById("save");b.disabled=true;try{await post("/api/brand/set",body);say("save-status","Saved.","ok");location.reload()}catch(err){say("save-status",err.message,"err");b.disabled=false}});
@@ -32442,6 +32468,7 @@ var esc5, STYLE, SHELL_JS, foot;
32442
32468
  var init_brand_page = __esm({
32443
32469
  "src/command-os/design/brand-page.ts"() {
32444
32470
  "use strict";
32471
+ init_run_expectations();
32445
32472
  init_fonts();
32446
32473
  init_cockpit_shell();
32447
32474
  init_script_json();
@@ -36525,7 +36552,7 @@ async function runDashboard(argv) {
36525
36552
  }
36526
36553
  const job = startDesignRun(checked.value);
36527
36554
  res.writeHead(200, { "Content-Type": "application/json" });
36528
- res.end(JSON.stringify({ ok: true, id: job.id, outDir: job.outDir, note: "Started on your own Claude. This takes twenty minutes or more; poll /api/design/run?id= for progress." }));
36555
+ res.end(JSON.stringify({ ok: true, id: job.id, outDir: job.outDir, note: `Started on your own Claude. ${RUN_EXPECTATION} Poll /api/design/run?id= for progress.` }));
36529
36556
  } catch (e) {
36530
36557
  res.writeHead(400, { "Content-Type": "application/json" });
36531
36558
  res.end(JSON.stringify({ ok: false, error: e.message }));
@@ -37283,6 +37310,7 @@ var init_dashboard = __esm({
37283
37310
  init_claude_cli();
37284
37311
  init_token_registry();
37285
37312
  init_run_jobs();
37313
+ init_run_expectations();
37286
37314
  init_stage_runner();
37287
37315
  init_account_rules();
37288
37316
  init_claude_call();
@@ -43689,7 +43717,7 @@ async function runDesign(opts) {
43689
43717
  const medium = brief.medium;
43690
43718
  const outDir = (0, import_node_path7.resolve)(opts.outDir);
43691
43719
  (0, import_node_fs7.mkdirSync)(outDir, { recursive: true });
43692
- const pack = composeBrief(dna, brief);
43720
+ const pack = composeBrief(dna, brief, { tools: false });
43693
43721
  const maxPasses = Math.max(1, Math.min(3, opts.maxPasses ?? 3));
43694
43722
  const generate = opts.generate ?? claudeGenerate(opts.model ?? DEFAULT_MODEL);
43695
43723
  const conversionRun = brief.options.pageMode === "conversion" && brief.conversion ? { brief: brief.conversion, gate: opts.gate ?? "ship" } : void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elitedcs/ghl-mcp",
3
- "version": "3.83.4",
3
+ "version": "3.83.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",