@elitedcs/ghl-mcp 3.84.8 → 3.84.9

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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.84.9 — build steps you can read
4
+
5
+ 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.
6
+
7
+ Call recordings: Plaud is proven and Zoom should work the same way but has not been checked yet. For any other recorder, export the transcript and attach it.
8
+
3
9
  ## 3.84.8 — client calls into assessments, and locks you can always reach
4
10
 
5
11
  Bring a recorded client call into their assessment in one step. Your Claude fetches the transcript from its own Plaud or Zoom connection (or you attach the file), proposes answers with the client's exact words, and read_assessment_transcript now saves them to the client's contact and reads them back. Answers in the client's own words save straight away. Every number waits for your OK in one short list: reply "confirm all" or "confirm all except …", and those save marked as confirmed by you. A needs analysis comes with it: what they said they want, what they actually need, problems they didn't name, and solutions they didn't know to ask for, each tied to their own words and written for you, never sent to the client. The reply also lays out a client file for your Google Drive (Clients / the client / Calls, plus a running "Client understanding" document), and can keep a copy in a local folder you choose.
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.8
900
+ **Version:** 3.84.9
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.8",
29061
+ version: "3.84.9",
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",
@@ -39943,7 +39943,7 @@ var init_client_calls = __esm({
39943
39943
  init_words();
39944
39944
  init_store3();
39945
39945
  init_assessment();
39946
- RECORDERS = ["Plaud", "Zoom", "Pocket", "Fathom", "Other"];
39946
+ RECORDERS = ["Plaud", "Zoom", "Other"];
39947
39947
  SOLUTION_KINDS = ["GoHighLevel build", "AI tool", "custom build", "process"];
39948
39948
  clean = (s) => String(s ?? "").replace(/\s+/g, " ").trim();
39949
39949
  ANALYSIS_MIN_WORDS = 3;
@@ -40245,7 +40245,7 @@ function registerAssessmentTools(server2, client) {
40245
40245
  safeTool(
40246
40246
  server2,
40247
40247
  "read_assessment_transcript",
40248
- `Bring a recorded client call (Plaud, Zoom, Pocket, Fathom, or any transcript file the owner 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.`,
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.`,
40249
40249
  {
40250
40250
  transcript: import_zod61.z.string().min(20).describe("The recording, as text. Paste the whole thing."),
40251
40251
  proposed: import_zod61.z.array(import_zod61.z.object({
@@ -40265,7 +40265,7 @@ function registerAssessmentTools(server2, client) {
40265
40265
  businessName: import_zod61.z.string().optional().describe("The client's business name, used when the contact and its saved assessment carry none."),
40266
40266
  createPipeline: import_zod61.z.boolean().optional().describe('Create the "Assessments" pipeline if it is missing. Only when the owner has said yes.'),
40267
40267
  source: import_zod61.z.object({
40268
- recorder: import_zod61.z.enum(["Plaud", "Zoom", "Pocket", "Fathom", "Other"]).describe("What recorded the call."),
40268
+ recorder: import_zod61.z.enum(["Plaud", "Zoom", "Other"]).describe("What recorded the call. Use Other for any recorder whose transcript was exported and attached."),
40269
40269
  recordedAt: import_zod61.z.string().describe("When the call happened, as an ISO date (2026-09-24) or timestamp, from the recording itself."),
40270
40270
  title: import_zod61.z.string().optional().describe("The recording's title, as the recorder shows it."),
40271
40271
  fetchedBy: import_zod61.z.enum(["claude", "upload"]).optional().describe("claude when you fetched it with your own connector, upload when the owner attached a file.")
@@ -41202,17 +41202,46 @@ function html(roster, seatToken, screen = "board", cap) {
41202
41202
  .sandbox-badge{background:var(--ok-soft);color:var(--ok);border-radius:999px;padding:.14rem .55rem;font-size:var(--fs-label);font-weight:700;letter-spacing:.04em;vertical-align:middle;white-space:nowrap}
41203
41203
 
41204
41204
  /* Stage tiles . same shapes as the hub's 7-stage track, clickable */
41205
- .stages{display:flex;gap:.4rem;flex-wrap:wrap}
41206
- .stage{flex:1;min-width:5.2rem;border:1px solid var(--line);border-radius:var(--radius-sm);padding:.45rem .5rem;text-align:center;cursor:pointer;user-select:none;font-size:var(--fs-label);background:var(--surface-2);color:var(--ink-2)}
41207
- .stage button{box-sizing:border-box;max-width:100%;white-space:normal;overflow-wrap:anywhere;height:auto}
41208
- .stage b{display:block;font-weight:600;color:var(--heading);font-size:var(--fs-label)}
41209
- .stage small{display:block;color:var(--ink-3);font-size:var(--fs-sm);margin-top:.15rem}
41210
- .stage.in_progress{border-color:var(--gold);background:var(--gold-soft)}
41211
- .stage.in_progress small{color:var(--gold-ink)}
41212
- .stage.done{border-color:var(--ok);background:var(--ok-soft)}
41213
- .stage.done small,.stage.done b{color:var(--ok)}
41214
- .stage.notpassed{border-color:var(--warn);background:var(--warn-soft)}
41215
- .stage.notpassed small{color:var(--warn)}
41205
+ /* The steps (owner-approved layout, 2026-09-25): progress bar, next-step panel, one row per step. */
41206
+ .sprog{margin:1rem 0 .9rem}
41207
+ .strack{display:grid;grid-template-columns:repeat(7,1fr);gap:.35rem}
41208
+ .sseg{height:.45rem;border-radius:999px;background:var(--wait-soft)}
41209
+ .sseg.done{background:var(--ok)}.sseg.now{background:var(--gold)}.sseg.stale{background:var(--warn)}
41210
+ .splabel{display:flex;justify-content:space-between;flex-wrap:wrap;gap:.3rem 1rem;margin-top:.55rem;font-size:var(--fs-sm)}
41211
+ .splabel b{color:var(--heading);font-weight:600}.splabel span{color:var(--ink-3)}
41212
+ .snext{border:1px solid var(--gold);background:var(--gold-soft);border-radius:var(--radius);padding:1rem 1.1rem;display:grid;grid-template-columns:1fr auto;gap:.6rem 1.2rem;align-items:center}
41213
+ .snext .sk{font-size:var(--fs-label);letter-spacing:.14em;text-transform:uppercase;color:var(--gold-ink);font-weight:700;margin:0 0 .2rem}
41214
+ .snext h3{margin:0 0 .35rem;font-size:var(--fs-lg);color:var(--heading)}
41215
+ .snext p{margin:0;color:var(--ink-2);font-size:var(--fs-sm);max-width:60ch}
41216
+ .snext .swarn{margin-top:.5rem;color:var(--warn);font-weight:500}
41217
+ .snext .snoteline{margin-top:.4rem}
41218
+ .snext .slive{margin-top:.5rem}
41219
+ .sbtns{display:flex;flex-direction:column;align-items:flex-end;gap:.5rem}
41220
+ .sbtns .sbig{padding:.6rem 1.1rem;font-size:var(--fs-body)}
41221
+ .stage button,.snext button{box-sizing:border-box;max-width:100%;white-space:normal;overflow-wrap:anywhere;height:auto}
41222
+ .sbtns .runbtn.sbig{color:var(--on-accent)}
41223
+ .sbtns .swhy{margin:0;font-size:var(--fs-sm);max-width:22rem;text-align:right}
41224
+ .stages{margin-top:1rem;border-top:1px solid var(--line)}
41225
+ .stage{display:grid;grid-template-columns:2rem 9.5rem 7.5rem 1fr auto;gap:.4rem 1rem;align-items:center;padding:.7rem .3rem;border-bottom:1px solid var(--line)}
41226
+ .stage.here{background:var(--gold-soft)}
41227
+ .snum{width:1.7rem;height:1.7rem;border-radius:50%;display:grid;place-items:center;font-size:var(--fs-label);font-weight:700;background:var(--wait-soft);color:var(--wait)}
41228
+ .snum.done{background:var(--ok-soft);color:var(--ok)}.snum.stale{background:var(--warn-soft);color:var(--warn)}.snum.now{background:var(--gold);color:var(--on-accent)}
41229
+ .sname{font-weight:600;color:var(--heading)}
41230
+ .schip{justify-self:start;font-size:var(--fs-label);letter-spacing:.06em;text-transform:uppercase;font-weight:700;padding:.15rem .55rem;border-radius:999px;white-space:nowrap}
41231
+ .schip.done{background:var(--ok-soft);color:var(--ok)}.schip.stale{background:var(--warn-soft);color:var(--warn)}.schip.wait{background:var(--wait-soft);color:var(--wait)}.schip.you,.schip.now{background:var(--gold-soft);color:var(--gold-ink)}
41232
+ .swhat{color:var(--ink-2);font-size:var(--fs-sm);min-width:0;overflow-wrap:anywhere}
41233
+ .swhat .sreason{color:var(--warn)}
41234
+ .snote{display:block;font-size:var(--fs-sm);line-height:1.35}
41235
+ .slive{font-size:var(--fs-sm);line-height:1.35;margin-top:.25rem}
41236
+ .sact{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:.2rem .8rem}
41237
+ .sactbtn{font-size:var(--fs-sm);color:var(--gold-ink);font-weight:600;background:none;border:0;padding:.2rem 0;cursor:pointer;text-align:right}
41238
+ .sactbtn:hover{text-decoration:underline}
41239
+ .sactbtn:disabled{color:var(--ink-3);cursor:default;text-decoration:none}
41240
+ @media (max-width: 52rem){
41241
+ .stage{grid-template-columns:2rem 1fr auto;grid-template-areas:"n name act" ". chip chip" ". what what"}
41242
+ .stage .snum{grid-area:n}.stage .sname{grid-area:name}.stage .schip{grid-area:chip}.stage .swhat{grid-area:what}.stage .sact{grid-area:act}
41243
+ .snext{grid-template-columns:1fr}.sbtns{align-items:flex-start}.sbtns .swhy{text-align:left}
41244
+ }
41216
41245
 
41217
41246
  /* \u2500\u2500 Other accounts: one-line rows. One account at a time is the rule. \u2500\u2500 */
41218
41247
  .acct-list{padding:.2rem .9rem}
@@ -41322,8 +41351,6 @@ function html(roster, seatToken, screen = "board", cap) {
41322
41351
 
41323
41352
  @media (max-width: 46rem){
41324
41353
  .brand-sub,.localpill{display:none}
41325
- .stages{gap:.3rem}
41326
- .stage{min-width:4.2rem}
41327
41354
  }
41328
41355
  @media (prefers-reduced-motion: reduce){*{transition:none !important}}
41329
41356
  ` })}<body data-screen="${screen}"${cap ? ` data-capability="${cap.key}"` : ""}>
@@ -41358,6 +41385,14 @@ const GATES=${scriptJson(HUMAN_GATE_STAGES)};let PROTECTED=${scriptJson(PROTECTE
41358
41385
  const TYPES=["client","sandbox","template","hidden"];
41359
41386
  const TYPE_LABELS={client:"Client",sandbox:"Practice sandbox",template:"Template",hidden:"Hidden"};
41360
41387
  const LABELS=["Intake","Brief","Approve","Build","Verify","Manual tasks","Go-live"];
41388
+ // What each step does: [the one line on its row, the fuller sentence in the next-step panel].
41389
+ const STEP_ABOUT=[["Creates the intake form, its fields and one notification workflow.","Creates or repairs this client's intake form, its custom fields and one published notification workflow."],
41390
+ ["Reads the intake into a brief.","Reads this client's intake, from the submitted form or the one completed here, and turns it into the brief the plan is made from."],
41391
+ ["You approve the proposed build.","Review the intake and the brief, then approve the proposed build for this client."],
41392
+ ["Builds the approved plan into this account.","Builds the approved plan into this client's account. Anything it cannot build is listed as an issue."],
41393
+ ["Checks the build for broken links.","Checks the account for broken links between workflows, pipelines and fields, and tests the form. It may submit and delete one test lead. Anything it cannot check is reported as unchecked."],
41394
+ ["Jobs only a person can do. You confirm them.","The jobs only a person can do, such as A2P, Google sign-in, staff and phone. Confirm them when they are finished."],
41395
+ ["You sign this client live.","Sign this client live once Verify is clean and the manual tasks are finished."]];
41361
41396
  const STATUS_TEXT={pending:"not started",in_progress:"in progress",done:"done"};
41362
41397
  const NEXT={pending:"in_progress",in_progress:"done",done:"pending"};
41363
41398
  let polling=false,query="",showParked=false,showNew=false,snapshots=null,snapshotError="";
@@ -41902,8 +41937,22 @@ function card(c,state,run){const st=(state.clients[c.locationId]||{stages:STAGES
41902
41937
  if(!response.ok||r.ok!==true){r.ok=false;r.error=r.error||"Command OS could not confirm the result. Refresh this client to read its saved state before retrying."}
41903
41938
  if(!r.ok)alert(r.error);else load()
41904
41939
  }catch(e){alert("Command OS could not confirm the result. Refresh this client to read its saved state before retrying.")}};
41905
- const row=document.createElement("div");row.className="stages";
41906
- STAGES.forEach((name,i)=>{const stale=state.buildViews?.[c.locationId]?.stale?.find(x=>x.stage===i);const el=document.createElement("div");el.className="stage "+(stale?"pending":(st[i]||"pending"));
41940
+ // The steps, laid out as the owner approved on 2026-09-25: a progress bar, the next step in
41941
+ // full, then one row per step. Only the layout changed. Every button below keeps the condition
41942
+ // and the handler it had in the seven-column layout; the next-step panel draws the SAME
41943
+ // buttons again for the current step, so nothing appears there that its row would not offer.
41944
+ const staleOf=i=>state.buildViews?.[c.locationId]?.stale?.find(x=>x.stage===i);
41945
+ const isDone=i=>(st[i]||"pending")==="done"&&!staleOf(i);
41946
+ let curI=-1;for(let i=0;i<STAGES.length;i++){if(!isDone(i)){curI=i;break}}
41947
+ const nDone=STAGES.filter((_,i)=>isDone(i)).length;
41948
+ const prog=document.createElement("div");prog.className="sprog";prog.setAttribute("aria-label","Progress: "+nDone+" of "+STAGES.length+" steps done");
41949
+ const track=document.createElement("div");track.className="strack";prog.appendChild(track);
41950
+ const plabel=document.createElement("div");plabel.className="splabel";prog.appendChild(plabel);
41951
+ div.appendChild(prog);
41952
+ const nextBox=document.createElement("div");nextBox.className="snext";div.appendChild(nextBox);
41953
+ const row=document.createElement("div");row.className="stages";row.setAttribute("role","list");
41954
+ let curLive=false;
41955
+ STAGES.forEach((name,i)=>{const stale=staleOf(i);const el=document.createElement("div");el.className="stage "+(stale?"pending":(st[i]||"pending"))+(i===curI?" here":"");el.setAttribute("role","listitem");
41907
41956
  const gate=GATES[String(i)];
41908
41957
  const oc0=(state.outcomes||{})[c.locationId+":"+i];const notPassed=(st[i]||"pending")==="pending"&&oc0&&(oc0.issues||[]).length>0;
41909
41958
  if(notPassed)el.className+=" notpassed";
@@ -41913,8 +41962,16 @@ function card(c,state,run){const st=(state.clients[c.locationId]||{stages:STAGES
41913
41962
  // from before the run (a "stale" warning and an old issue count are both wrong then).
41914
41963
  const live=Boolean(run&&run.running&&run.locationId===c.locationId&&run.stage===i);
41915
41964
  const anyBusy=Boolean(run&&run.running&&run.locationId===c.locationId);
41916
- el.innerHTML="<b>"+(i+1)+". "+LABELS[i]+"</b><small>"+(live?"running now":stale?"stale":notPassed?"not passed":STATUS_TEXT[st[i]||"pending"])+(gate&&!live?" \xB7 your call":"")+"</small>";
41917
- if(live){const box=document.createElement("div");box.style.cssText="margin-top:.35rem;font-size:var(--fs-sm);line-height:1.35";
41965
+ if(i===curI&&live)curLive=true;
41966
+ const tone=i===curI?"now":isDone(i)?"done":(stale||notPassed)?"stale":"";
41967
+ const seg=document.createElement("span");seg.className="sseg "+tone;track.appendChild(seg);
41968
+ const num=document.createElement("span");num.className="snum "+tone;num.textContent=String(i+1);el.appendChild(num);
41969
+ const nm=document.createElement("b");nm.className="sname";nm.textContent=LABELS[i];el.appendChild(nm);
41970
+ const chipOf=live?["Running\u2026","now"]:stale?["Run again","stale"]:notPassed?["Not passed","stale"]:isDone(i)?["Done","done"]:gate?["Your call","you"]:(st[i]||"pending")==="in_progress"?["In progress","now"]:i===curI?["Up next","now"]:["Waiting","wait"];
41971
+ const chip=document.createElement("span");chip.className="schip "+chipOf[1];chip.textContent=chipOf[0];el.appendChild(chip);
41972
+ const what=document.createElement("div");what.className="swhat";el.appendChild(what);
41973
+ const act=document.createElement("div");act.className="sact";el.appendChild(act);
41974
+ const mkLive=()=>{const box=document.createElement("div");box.className="slive";
41918
41975
  const clock=document.createElement("div");clock.style.cssText="font-variant-numeric:tabular-nums;color:var(--ok)";
41919
41976
  const began=run.startedAt?new Date(run.startedAt).getTime():0;
41920
41977
  const tick=()=>{if(!began)return;const sec=Math.max(0,Math.round((Date.now()-began)/1000));
@@ -41927,29 +41984,60 @@ function card(c,state,run){const st=(state.clients[c.locationId]||{stages:STAGES
41927
41984
  line.textContent=(doneAll?"\u2713 ":g.state==="in_progress"?"\u203A ":" ")+g.label+" "+g.done+"/"+g.total;box.appendChild(line)}}
41928
41985
  if(pr&&pr.current){const now=document.createElement("div");now.style.cssText="margin-top:.25rem;color:var(--muted)";
41929
41986
  now.textContent=String(pr.current).replace(/^"|"$/g,"").slice(0,110);box.appendChild(now)}
41930
- el.appendChild(box)}
41987
+ return box};
41931
41988
  // One short line here; the button's confirmation carries the full wording. At 135
41932
41989
  // characters this sentence made every stage box 677 px tall (auditor, 2026-09-07).
41933
- if(i===4){const disclosure=document.createElement("small");disclosure.textContent="Reads every planned item back. May submit and delete one test lead.";el.appendChild(disclosure)}
41990
+ let about=document.createElement("span");about.textContent=STEP_ABOUT[i][0];
41991
+ if(i===4){const disclosure=document.createElement("span");disclosure.textContent="Checks links and the form. May submit and delete one test lead.";about=disclosure}
41934
41992
  if(gate)el.title=gate;
41935
- if(stale&&!live){const reason=document.createElement("small");reason.textContent=stale.reason;el.appendChild(reason)}
41936
41993
  const oc=(state.outcomes||{})[c.locationId+":"+i];
41937
- if(!live&&oc&&(oc.summary||(oc.issues||[]).length)){const note=document.createElement("small");
41938
- const nIss=(oc.issues||[]).length;
41939
- note.style.cssText="display:block;margin-top:.25rem;color:"+(nIss?"var(--warn)":"var(--ok)")+";font-size:var(--fs-sm);line-height:1.25;text-decoration:"+(nIss?"underline":"none")+";cursor:"+(nIss?"pointer":"default");
41994
+ const nIss=oc?(oc.issues||[]).length:0;
41995
+ const toggleIssues=(ev)=>{ev.stopPropagation();oOpen[c.locationId+":"+i]=!oOpen[c.locationId+":"+i];render(lastState,lastRun)};
41996
+ const mkNote=()=>{let note=null;
41997
+ if(!live&&oc&&(oc.summary||(oc.issues||[]).length)){note=document.createElement("small");note.className="snote";
41998
+ note.style.cssText="color:"+(nIss?"var(--warn)":"var(--ok)")+";text-decoration:"+(nIss?"underline":"none")+";cursor:"+(nIss?"pointer":"default");
41940
41999
  note.textContent=i===4&&oc.ok===true?(${verifyPassedLine.toString()})((state.manualTasks?.[c.locationId]||[]).length):nIss?("\u26A0 "+nIss+" issue"+(nIss>1?"s":"")+". See details"):((oc.plain&&oc.plain.summary)||oc.summary||"").slice(0,90);
41941
- if(nIss)note.onclick=(ev)=>{ev.stopPropagation();oOpen[c.locationId+":"+i]=!oOpen[c.locationId+":"+i];render(lastState,lastRun)};
41942
- el.appendChild(note)}
42000
+ if(nIss)note.onclick=toggleIssues}
42001
+ return note};
42002
+ // What the row says, in one place: the live run, else why it went stale, else what the last
42003
+ // run found, else what the step does.
42004
+ if(live){const rn=document.createElement("span");rn.textContent="Running now.";what.appendChild(rn);what.appendChild(mkLive())}
42005
+ if(stale&&!live){const reason=document.createElement("span");reason.className="sreason";reason.textContent=stale.reason;what.appendChild(reason)}
42006
+ else if(!live){const note=mkNote();what.appendChild(note||about)}
41943
42007
  if(POWERED.includes(i)&&(st[i]||"pending")!=="done"){el.style.cursor="default";
41944
42008
  el.title="Command OS runs and checks this step. Its result comes from a run, never from a click."}
41945
- if(gate&&!anyBusy&&!parkedType){const action=document.createElement("button");action.className="intakebtn";action.textContent=({2:"Approve this plan",5:"Confirm these tasks are finished",6:"Sign this client live"})[i];action.onclick=()=>{if(i===2)return approveBuilding();return markStage(c.locationId,i,"done")};el.appendChild(action)}
42009
+ const gateLabel=({2:"Approve this plan",5:"Confirm these tasks are finished",6:"Sign this client live"})[i];
42010
+ const mkGate=cls=>{const action=document.createElement("button");action.className=cls;action.textContent=gateLabel;action.onclick=()=>{if(i===2)return approveBuilding();return markStage(c.locationId,i,"done")};return action};
41946
42011
  const canBuild=!parkedType&&(i<=1||sandbox||Boolean((state.buildApproved||{})[c.locationId]));
41947
- if(canBuild&&POWERED.includes(i)){const b=document.createElement("button");b.className="runbtn";
41948
- const busy=run&&run.running;b.textContent=busy&&run.locationId===c.locationId&&run.stage===i?"Running\u2026":"Run this step";
42012
+ const runIt=async(ev)=>{ev.stopPropagation();if(i===0&&!confirm("In "+c.name+", Intake writes custom fields, one form, and one published notification workflow."+String.fromCharCode(10,10)+INTAKE_NOTIFICATION_REVIEW.join(String.fromCharCode(10))+String.fromCharCode(10,10)+"Create or repair these intake items?"))return;if(i===4&&!confirm("Verify submits one test lead to "+c.name+"\u2019s funnel, watches it arrive, and deletes the test contact afterwards. It may fire this client\u2019s own automations once. Run the check?"))return;const res=await fetch("/api/run",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({locationId:c.locationId,stage:i,intakeConfirmed:i===0})});const j=await res.json();if(!j.ok)alert(j.error);load()};
42013
+ const mkRun=(cls,label)=>{const b=document.createElement("button");b.className=cls;
42014
+ const busy=run&&run.running;b.textContent=busy&&run.locationId===c.locationId&&run.stage===i?"Running\u2026":label;
41949
42015
  b.disabled=Boolean(busy)||(!st.some(s=>s==="done")&&!preflights[c.locationId]?.ok);
41950
- b.onclick=async(ev)=>{ev.stopPropagation();if(i===0&&!confirm("In "+c.name+", Intake writes custom fields, one form, and one published notification workflow."+String.fromCharCode(10,10)+INTAKE_NOTIFICATION_REVIEW.join(String.fromCharCode(10))+String.fromCharCode(10,10)+"Create or repair these intake items?"))return;if(i===4&&!confirm("Verify submits one test lead to "+c.name+"\u2019s funnel, watches it arrive, and deletes the test contact afterwards. It may fire this client\u2019s own automations once. Run the check?"))return;const res=await fetch("/api/run",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({locationId:c.locationId,stage:i,intakeConfirmed:i===0})});const j=await res.json();if(!j.ok)alert(j.error);load()};
41951
- el.appendChild(document.createElement("br"));el.appendChild(b)}
42016
+ b.onclick=runIt;return b};
42017
+ if(!live&&nIss){const si=document.createElement("button");si.className="sactbtn";si.textContent=nIss>1?"See issues":"See issue";si.onclick=toggleIssues;act.appendChild(si)}
42018
+ if(gate&&!anyBusy&&!parkedType)act.appendChild(mkGate("sactbtn"));
42019
+ if(canBuild&&POWERED.includes(i))act.appendChild(mkRun("sactbtn srun",i===curI?"Run "+LABELS[i]:(st[i]||"pending")==="done"?"Run again":"Run this step"));
42020
+ if(i===curI){const main=document.createElement("div");
42021
+ const k=document.createElement("p");k.className="sk";k.textContent=parkedType?"Building is off":"Next step";main.appendChild(k);
42022
+ const h3=document.createElement("h3");h3.textContent=parkedType?"Nothing is built into this account":(i+1)+". "+LABELS[i];main.appendChild(h3);
42023
+ const pa=document.createElement("p");pa.textContent=parkedType?"It opens here so you can lock or unlock it, or change its type to Client to build it.":STEP_ABOUT[i][1];main.appendChild(pa);
42024
+ if(live)main.appendChild(mkLive());
42025
+ if(stale&&!live){const w=document.createElement("p");w.className="swarn";w.textContent=stale.reason;main.appendChild(w)}
42026
+ const pn=mkNote();if(pn){const w=document.createElement("p");w.className="snoteline";w.appendChild(pn);main.appendChild(w)}
42027
+ const btns=document.createElement("div");btns.className="sbtns";
42028
+ if(gate&&!anyBusy&&!parkedType)btns.appendChild(mkGate("intakebtn sbig"));
42029
+ if(canBuild&&POWERED.includes(i))btns.appendChild(mkRun("runbtn sbig","Run "+LABELS[i]));
42030
+ if(!parkedType&&canBuild&&POWERED.includes(i)&&run&&run.running&&run.locationId!==c.locationId){const why=document.createElement("p");why.className="muted swhy";why.textContent="A step is running for another client. This one can start when it finishes.";btns.appendChild(why)}
42031
+ if(!btns.children.length&&!live&&!parkedType){const why=document.createElement("p");why.className="muted swhy";
42032
+ why.textContent=anyBusy?"Another step is running for this client. This one opens when it finishes.":POWERED.includes(i)?"Approve building for this client below, and this step can run.":"";
42033
+ if(why.textContent)btns.appendChild(why)}
42034
+ nextBox.title=el.title||"";nextBox.appendChild(main);nextBox.appendChild(btns)}
41952
42035
  row.appendChild(el)});
42036
+ if(curI<0){const main=document.createElement("div");const k=document.createElement("p");k.className="sk";k.textContent="All done";main.appendChild(k);
42037
+ const h3=document.createElement("h3");h3.textContent="All "+STAGES.length+" steps are done.";main.appendChild(h3);nextBox.appendChild(main)}
42038
+ {const b=document.createElement("b");b.textContent=curI<0?"All "+STAGES.length+" steps done":"Step "+(curI+1)+" of "+STAGES.length+": "+LABELS[curI];plabel.appendChild(b);
42039
+ const after=STAGES.length-nDone-(curI<0?0:1);const s=document.createElement("span");
42040
+ s.textContent=nDone+" done"+(curI<0?"":" \xB7 "+(curLive?"1 running now":"1 needs you")+" \xB7 "+after+" after that");plabel.appendChild(s)}
41953
42041
  div.appendChild(row);
41954
42042
  for(let i=0;i<STAGES.length;i++){if(oOpen[c.locationId+":"+i]){const op=outcomePanel(c.locationId,i,state);if(op)div.appendChild(op)}}
41955
42043
  const view=(state.buildViews||{})[c.locationId];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elitedcs/ghl-mcp",
3
- "version": "3.84.8",
3
+ "version": "3.84.9",
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",