@elitedcs/ghl-mcp 3.97.2 → 3.97.3

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,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.97.3: A sub-account is never created twice at the same moment, and brain setup names the right fix
4
+
5
+ **Two Creates of one business name at the same moment: the second is refused.** When the same name is being created in the cockpit and in chat, or in two assistant windows on one computer, the second Create now says the sub-account is being created right now and to wait for that to finish. Before, both could go through and make the sub-account twice. This works when every window runs 3.97.3 or later. If GHL Command cannot check this because its shared file is busy, or cannot be read or written, nothing is created and it says to try again in a moment.
6
+
7
+ **Brain setup now gives the right fix for each Anthropic refusal:** a key it does not accept needs a new key; billing, credit or a usage limit keep the same key and say what to fix; a model your workspace cannot use is named, with how to turn on access.
8
+
9
+ The brain's send-or-hold check now has up to 15 seconds to answer (was 6), so the first check after a quiet spell is no longer held just for being slow.
10
+
3
11
  ## 3.97.2: Two assistant windows share one computer more safely, cockpit runs never wait on a question, and the client brain fails safe
4
12
 
5
13
  **Two assistant windows on one computer keep each other's unrelated changes.** When GHL Command runs in more than one assistant window at once, each one now saves your registered accounts, connections and cockpit board under a shared lock, and merges its change onto what the other saved. Before, one could save over an account the other had just registered. Editing the same details in both windows at once can still replace the earlier change. If the lock stays busy for 5 seconds, nothing is written, and the tool or the card says what was not saved and what to do next; only a few background notes (such as when the desktop icon was offered) are skipped without a message. Most of a cockpit run's own saves (a step's status and record, its job list and its check record) try three times over about 2 to 3 seconds instead. If the lock is still busy, the run's progress says in plain words which one could not be saved and what to do next, and a step that finished still reads as finished; its card shows what was last saved. A sub-account that was created is always reported as created, in chat and in the cockpit, even when its key or its card could not be saved. If the cockpit could not save it, the New Client page lists it as created until you register it with its key or press "Dismiss: I removed this sub-account", and neither the cockpit nor chat will create that name again until then.
package/README.md CHANGED
@@ -904,7 +904,7 @@ Built by **[Elite DCs, LLC](https://elitedcs.com)** — a digital marketing and
904
904
 
905
905
  **Tech stack:** TypeScript, Node.js, esbuild, MCP SDK, Zod, GHL API v2, Firebase Auth
906
906
 
907
- **Version:** 3.97.2
907
+ **Version:** 3.97.3
908
908
 
909
909
  ---
910
910
 
@@ -15982,7 +15982,7 @@ function judgeNote(judgmentId, message, answer) {
15982
15982
  if (answer.better_message) lines.push(`What would fit now: ${esc2(answer.better_message)}`);
15983
15983
  return lines.join("\n");
15984
15984
  }
15985
- var JUDGE_ASK_DEADLINE_MS = 6e3;
15985
+ var JUDGE_ASK_DEADLINE_MS = 15e3;
15986
15986
  var JUDGE_SETTLE_MS = 700;
15987
15987
  function workflowAnswer(outcome, judgmentId, kind = "ask", facts = null, rule = DEFAULT_FAIL_RULE, known = {}) {
15988
15988
  const hold = (why) => ({ send: "no", why, judgmentId, held: true, task: rule.taskOnHold ? `The brain held a message to this lead. ${why} Check it and send it by hand if it should go.` : null });
package/dist/index.js CHANGED
@@ -1269,6 +1269,7 @@ function withFileLock(target, fn, opts = {}) {
1269
1269
  Atomics.wait(SLEEP, 0, 0, 25);
1270
1270
  }
1271
1271
  }
1272
+ if (!haveLock && opts.requireLock) throw (opts.busy ?? ((t) => new FileLockBusyError(t)))(target);
1272
1273
  holding.set(lockPath, 1);
1273
1274
  try {
1274
1275
  return fn();
@@ -36996,14 +36997,24 @@ async function anthropicProbe(fetchFn, key2) {
36996
36997
  let reached = true;
36997
36998
  let lastStatus = 0;
36998
36999
  let lastBody = "";
37000
+ let lastModel = "";
37001
+ let modelAnsweredYes = false;
36999
37002
  const r = await probeAnthropicKey({
37000
37003
  apiKey: key2,
37001
37004
  modelIds: BRAIN_CHECKED_MODELS,
37002
37005
  fetchImpl: (async (url, init) => {
37003
37006
  try {
37004
37007
  const res = await fetchFn(String(url), { method: init?.method, headers: init?.headers, body: init?.body, signal: init?.signal ?? void 0, redirect: "error" });
37008
+ if (lastModel && lastStatus >= 200 && lastStatus < 300) modelAnsweredYes = true;
37005
37009
  lastStatus = res.status;
37006
37010
  lastBody = "";
37011
+ lastModel = (() => {
37012
+ try {
37013
+ return String(JSON.parse(String(init?.body ?? "")).model ?? "");
37014
+ } catch {
37015
+ return "";
37016
+ }
37017
+ })();
37007
37018
  return { ok: res.ok, status: res.status, text: async () => {
37008
37019
  const b = await res.text();
37009
37020
  lastBody = b;
@@ -37021,18 +37032,25 @@ async function anthropicProbe(fetchFn, key2) {
37021
37032
  if (r.verdict === "working") return { ok: true };
37022
37033
  const unchecked2 = { ok: false, refused: false, error: ANTHROPIC_KEY_UNCHECKED };
37023
37034
  if (!reached) return unchecked2;
37024
- const anthropicSaid = anthropicErrorBody(lastBody);
37025
- if (r.verdict === "invalid" && lastStatus === 401) return { ok: false, refused: true, error: anthropicKeyRefused(r) };
37026
- if ((r.verdict === "invalid" || r.verdict === "identity-linked" || r.verdict === "no-credit") && anthropicSaid && lastStatus < 500) return { ok: false, refused: true, error: anthropicKeyRefused(r) };
37027
- if (r.verdict === "unclear" && [400, 403, 404].includes(lastStatus) && anthropicSaid) return { ok: false, refused: true, error: anthropicKeyRefused(r) };
37035
+ if (lastStatus === 401) return { ok: false, refused: true, error: anthropicKeyRefused(lastBody ? r : classifyAnthropicResponse(401, "")) };
37036
+ const said = anthropicError(lastBody);
37037
+ if (!said || lastStatus >= 500) return unchecked2;
37038
+ const orgOrIdentity = said.type === "billing_error" || r.verdict === "identity-linked" || r.verdict === "no-credit" || /usage limit/i.test(said.message);
37039
+ if (orgOrIdentity) return { ok: false, refused: true, error: anthropicKeyRefused(r, said.type) };
37040
+ const modelLevel = Boolean(lastModel) && (said.type === "not_found_error" || said.type === "invalid_request_error" && /\bmodel\b/i.test(said.message) || modelAnsweredYes && said.type === "permission_error");
37041
+ const aboutTheKey = !modelLevel && ANTHROPIC_KEY_ERROR_TYPES.has(said.type);
37042
+ if (aboutTheKey) return { ok: false, refused: true, error: anthropicKeyRefused(r, said.type) };
37043
+ const aboutTheModel = modelLevel;
37044
+ if (aboutTheModel) return { ok: false, refused: true, error: ANTHROPIC_MODEL_UNAVAILABLE(lastModel, false, said.raw), model: { id: lastModel, raw: said.raw } };
37028
37045
  return unchecked2;
37029
37046
  }
37030
- function anthropicErrorBody(raw) {
37047
+ function anthropicError(raw) {
37031
37048
  try {
37032
37049
  const b = JSON.parse(raw ?? "");
37033
- return b?.type === "error" && typeof b.error?.type === "string";
37050
+ if (b?.type !== "error" || typeof b.error?.type !== "string") return null;
37051
+ return { type: b.error.type, message: typeof b.error.message === "string" ? b.error.message : "", raw: scrubKeys(String(raw).slice(0, 400)) };
37034
37052
  } catch {
37035
- return false;
37053
+ return null;
37036
37054
  }
37037
37055
  }
37038
37056
  async function licenceProbe(fetchFn, key2, admission) {
@@ -37192,9 +37210,10 @@ async function brainSetup(deps, args) {
37192
37210
  let anthropicNote = null;
37193
37211
  if (anthropicGiven) {
37194
37212
  const probe = await anthropicProbe(deps.fetch, anthropicGiven);
37213
+ if (!probe.ok && anthropicCurrent && probe.model) return { ok: false, wrote: "nothing", error: ANTHROPIC_MODEL_UNAVAILABLE(probe.model.id, true, probe.model.raw) };
37195
37214
  if (!probe.ok && anthropicCurrent && probe.refused) return { ok: false, wrote: "nothing", error: ANTHROPIC_SAVED_KEY_REFUSED(probe.error) };
37196
37215
  if (!probe.ok && anthropicCurrent) anthropicNote = "Anthropic could not be reached to check it just now; it is left as it is.";
37197
- else if (!probe.ok) return { ok: false, wrote: "nothing", error: probe.refused && anthropicSaved && anthropicMarkers.length !== 1 ? `${probe.error} ${ANTHROPIC_UNRECORDED_NOTE}` : probe.error };
37216
+ else if (!probe.ok) return { ok: false, wrote: "nothing", error: probe.refused && !probe.model && anthropicSaved && anthropicMarkers.length !== 1 ? `${probe.error} ${ANTHROPIC_UNRECORDED_NOTE}` : probe.error };
37198
37217
  }
37199
37218
  const licenceMarker = licenceKeyMarker(licenceKeyHere, admissionHere);
37200
37219
  const licenceSaved = secretNames.includes(LICENCE_KEY_SECRET);
@@ -37592,7 +37611,7 @@ async function clientSettingsReadBack(store2, clientId, c) {
37592
37611
  const off = Object.keys(c).filter((k) => JSON.stringify(now[k]) !== JSON.stringify(c[k]));
37593
37612
  return off.length ? { check, passed: false, detail: `These did not read back as saved: ${off.join(", ")}.` } : { check, passed: true, detail: `Saved and read back: ${Object.keys(c).join(", ")}.` };
37594
37613
  }
37595
- var import_node_crypto15, import_zod58, BRAIN_CHECKED_MODELS, ANTHROPIC_KEY_SECRET, ANTHROPIC_KEY_MARKER_PREFIX, anthropicKeyMarker, shortPrint, scrubKeys, anthropicKeyRefused, ANTHROPIC_KEY_UNCHECKED, ANTHROPIC_SAVED_KEY_REFUSED, ANTHROPIC_UNRECORDED_NOTE, ANTHROPIC_KEY_MALFORMED, LICENCE_KEY_MARKER_PREFIX, licenceKeyMarker, LICENCE_NOT_OS, SETUP_BUSY, LICENCE_NO_ADMISSION, LICENCE_NO_KEY, LICENCE_UNCONFIRMED, BRAIN_TABLES, DEFAULT_REGION, PROBE_CONTACT, SETUP_OVERTAKEN, EDGE_PROBE_CHECK, EDGE_PROBE_CLEANUP, probeRowsSql, BRAIN_MANAGER_CHECK, CLIENT_SETTINGS_CHECK, PURPOSE_RE, FORM_ID_RE;
37614
+ var import_node_crypto15, import_zod58, BRAIN_CHECKED_MODELS, ANTHROPIC_KEY_SECRET, ANTHROPIC_KEY_MARKER_PREFIX, anthropicKeyMarker, shortPrint, scrubKeys, anthropicKeyRefused, ANTHROPIC_KEY_UNCHECKED, ANTHROPIC_SAVED_KEY_REFUSED, ANTHROPIC_UNRECORDED_NOTE, ANTHROPIC_KEY_MALFORMED, ANTHROPIC_KEY_ERROR_TYPES, BRAIN_MODEL_JOB, ANTHROPIC_MODEL_UNAVAILABLE, LICENCE_KEY_MARKER_PREFIX, licenceKeyMarker, LICENCE_NOT_OS, SETUP_BUSY, LICENCE_NO_ADMISSION, LICENCE_NO_KEY, LICENCE_UNCONFIRMED, BRAIN_TABLES, DEFAULT_REGION, PROBE_CONTACT, SETUP_OVERTAKEN, EDGE_PROBE_CHECK, EDGE_PROBE_CLEANUP, probeRowsSql, BRAIN_MANAGER_CHECK, CLIENT_SETTINGS_CHECK, PURPOSE_RE, FORM_ID_RE;
37596
37615
  var init_setup = __esm({
37597
37616
  "src/command-os/brain/setup.ts"() {
37598
37617
  "use strict";
@@ -37616,11 +37635,22 @@ var init_setup = __esm({
37616
37635
  anthropicKeyMarker = (key2) => `${ANTHROPIC_KEY_MARKER_PREFIX}${contentHash2(key2).slice(0, 12).toUpperCase()}`;
37617
37636
  shortPrint = (key2) => contentHash2(key2).slice(0, 8);
37618
37637
  scrubKeys = (t) => t.replace(/sk-ant-[A-Za-z0-9_-]+/g, "[a key]");
37619
- anthropicKeyRefused = (r) => `Anthropic refused this brain-only key, so it was not saved and nothing was created or changed. ${r.detail}${r.raw ? ` Anthropic said: ${scrubKeys(r.raw)}` : ""} ${r.verdict === "no-credit" || /usage limit/i.test(r.raw ?? "") ? "Then run brain_setup again with the same key." : "Then run brain_setup again with the new key."}`;
37638
+ anthropicKeyRefused = (r, errorType) => {
37639
+ const said = r.raw ? ` Anthropic said: ${scrubKeys(r.raw)}` : "";
37640
+ const lead = "Anthropic refused this brain-only key, so it was not saved and nothing was created or changed.";
37641
+ if (errorType === "billing_error") return `${lead} Anthropic says the organization's billing stops it, and that applies to every key on the organization, so a new key would answer the same.${said} Fix billing at platform.claude.com under Billing, then run brain_setup again with the same key.`;
37642
+ if (r.verdict === "no-credit") return `${lead} Anthropic says the organization's credit balance is empty, and that applies to every key on it, so a new key would answer the same.${said} Add credit at platform.claude.com under Billing, then run brain_setup again with the same key.`;
37643
+ if (/usage limit/i.test(r.raw ?? "")) return `${lead} Anthropic says a usage limit set on the organization or its workspace stops it, so a new key would answer the same.${said} Raise the limit at platform.claude.com, or wait until it resets, then run brain_setup again with the same key.`;
37644
+ if (r.verdict === "unclear") return `${lead} Anthropic says this key may not do what the brain needs.${said} Create a new key at platform.claude.com under API keys, on a workspace, and run brain_setup again with it.`;
37645
+ return `${lead} ${r.detail}${said} Then run brain_setup again with the new key.`;
37646
+ };
37620
37647
  ANTHROPIC_KEY_UNCHECKED = "The brain-only Anthropic key could not be checked with Anthropic just now, so it was not saved and nothing was created or changed. Run brain_setup again in a few minutes.";
37621
37648
  ANTHROPIC_SAVED_KEY_REFUSED = (why) => `This is the brain-only Anthropic key already saved inside the project, and Anthropic refuses it now, so the brain cannot write drafts with it. Nothing was changed. ${why.replace(/^Anthropic refused this brain-only key, so it was not saved and nothing was created or changed\. /, "")}`;
37622
37649
  ANTHROPIC_UNRECORDED_NOTE = "If this is the key already saved inside the project (its fingerprint was not recorded before 3.95.1), that saved key does not work either, so drafts fail until a working key is saved.";
37623
37650
  ANTHROPIC_KEY_MALFORMED = "This brain-only Anthropic key contains a line break or a character no Anthropic key has, so it was not checked or saved and nothing was created or changed. Copy the key again from platform.claude.com under API keys, as one line, and run brain_setup again.";
37651
+ ANTHROPIC_KEY_ERROR_TYPES = /* @__PURE__ */ new Set(["authentication_error", "permission_error", "billing_error"]);
37652
+ BRAIN_MODEL_JOB = { [DEFAULT_BRAIN_MODEL]: "writes the brain's drafts", [JUDGE_MODEL]: "judges send or hold before a workflow message goes out" };
37653
+ ANTHROPIC_MODEL_UNAVAILABLE = (model, saved, raw) => `${saved ? "This is the brain-only Anthropic key already saved inside the project. Anthropic accepts it, but" : "Anthropic accepts this brain-only key, but"} says it cannot use ${model}, the model that ${BRAIN_MODEL_JOB[model] ?? "the brain calls"}. ${saved ? "Nothing was changed." : "So it was not saved and nothing was created or changed."} Anthropic said: ${raw} At platform.claude.com, check that the key's workspace may use ${model} (a new key on the same workspace would answer the same), then run brain_setup again.`;
37624
37654
  LICENCE_KEY_MARKER_PREFIX = `${LICENCE_KEY_SECRET}_FP_`;
37625
37655
  licenceKeyMarker = (key2, admission) => `${LICENCE_KEY_MARKER_PREFIX}${contentHash2(admission ? `${key2}
37626
37656
  ${admission}` : key2).slice(0, 12).toUpperCase()}`;
@@ -53260,7 +53290,7 @@ var require_package = __commonJS({
53260
53290
  "package.json"(exports2, module2) {
53261
53291
  module2.exports = {
53262
53292
  name: "@elitedcs/ghl-mcp",
53263
- version: "3.97.2",
53293
+ version: "3.97.3",
53264
53294
  mcpName: "io.github.drjerryrelth/ghl-command",
53265
53295
  description: "GoHighLevel MCP Server for Claude. 250 tools for CRM, automation and marketing, an account-wide workflow audit, live funnel-capture verification and a workflow builder, across client accounts.",
53266
53296
  main: "dist/index.js",
@@ -63840,6 +63870,7 @@ var init_cockpit_routes = __esm({
63840
63870
  post("/api/connections/disconnect", "agency", BOTH),
63841
63871
  post("/api/account-admin-settings", "agency", BOTH),
63842
63872
  post("/api/agency-profile", "agency", BOTH),
63873
+ post("/api/created-dismiss", "agency", BOTH),
63843
63874
  post("/api/anthropic-key", "agency", BOTH),
63844
63875
  post("/api/branding", "agency", BOTH),
63845
63876
  post("/api/brand/extract", "agency", BOTH),
@@ -68687,15 +68718,70 @@ function createdNotYetKept(isKept) {
68687
68718
  function subAccountNameKey(name2) {
68688
68719
  return name2.normalize("NFKC").trim().replace(/\s+/g, " ").toLowerCase();
68689
68720
  }
68721
+ function processAlive(pid) {
68722
+ if (pid === process.pid) return true;
68723
+ try {
68724
+ process.kill(pid, 0);
68725
+ return true;
68726
+ } catch (e) {
68727
+ return e.code === "EPERM";
68728
+ }
68729
+ }
68730
+ function liveReservations(now = Date.now()) {
68731
+ let text3 = "";
68732
+ try {
68733
+ text3 = fs50.readFileSync(createdSubAccountsPath(), "utf8");
68734
+ } catch (e) {
68735
+ if (e?.code === "ENOENT") return [];
68736
+ throw e;
68737
+ }
68738
+ const open2 = /* @__PURE__ */ new Map();
68739
+ for (const line4 of text3.split("\n")) {
68740
+ if (!line4.trim()) continue;
68741
+ let raw;
68742
+ try {
68743
+ raw = JSON.parse(line4);
68744
+ } catch {
68745
+ continue;
68746
+ }
68747
+ const r = ReservingLine.safeParse(raw);
68748
+ if (r.success) {
68749
+ open2.set(r.data.id, r.data);
68750
+ continue;
68751
+ }
68752
+ const d = ReleasedLine.safeParse(raw);
68753
+ if (d.success) open2.delete(d.data.released);
68754
+ }
68755
+ return [...open2.values()].filter((r) => now - Date.parse(r.at) < NAME_RESERVATION_STALE_MS && processAlive(r.pid));
68756
+ }
68690
68757
  function reserveSubAccountName(name2) {
68691
68758
  const key2 = subAccountNameKey(name2);
68692
68759
  if (createsInFlight.has(key2)) return null;
68760
+ const id2 = `${process.pid}-${(0, import_crypto4.randomBytes)(6).toString("hex")}`;
68761
+ let reserved = false;
68762
+ try {
68763
+ ensureAppDataDir();
68764
+ reserved = withFileLock(createdSubAccountsPath(), () => {
68765
+ if (liveReservations().some((r) => r.reserving === key2)) return false;
68766
+ fs50.appendFileSync(createdSubAccountsPath(), `${JSON.stringify(ReservingLine.parse({ reserving: key2, id: id2, pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() }))}
68767
+ `, { mode: 384 });
68768
+ return true;
68769
+ }, { requireLock: true });
68770
+ } catch {
68771
+ throw new NameReservationUnavailableError(name2);
68772
+ }
68773
+ if (!reserved) return null;
68693
68774
  createsInFlight.add(key2);
68694
68775
  let released = false;
68695
- return () => {
68696
- if (!released) {
68697
- released = true;
68698
- createsInFlight.delete(key2);
68776
+ return (opts) => {
68777
+ if (released) return;
68778
+ released = true;
68779
+ createsInFlight.delete(key2);
68780
+ if (opts?.keepReservation) return;
68781
+ try {
68782
+ fs50.appendFileSync(createdSubAccountsPath(), `${JSON.stringify(ReleasedLine.parse({ released: id2, at: (/* @__PURE__ */ new Date()).toISOString() }))}
68783
+ `, { mode: 384 });
68784
+ } catch {
68699
68785
  }
68700
68786
  };
68701
68787
  }
@@ -68711,7 +68797,7 @@ function createdNotSavedNote(name2) {
68711
68797
  function alreadyCreatedRefusal(name2, where = "below") {
68712
68798
  return `A sub-account named "${name2}" was already created in GoHighLevel. Register it ${where} instead of creating it again.`;
68713
68799
  }
68714
- var fs50, path50, import_zod89, CreatedRecord, RemovedMarker, JournalLine, createsInFlight, DISMISS_CREATED_LABEL, DISMISSED_CARD_BUSY_NOTE;
68800
+ var fs50, path50, import_zod89, import_crypto4, CreatedRecord, RemovedMarker, JournalLine, createsInFlight, NAME_RESERVATION_STALE_MS, ReservingLine, ReleasedLine, NameReservationUnavailableError, DISMISS_CREATED_LABEL, DISMISSED_CARD_BUSY_NOTE;
68715
68801
  var init_created_sub_accounts = __esm({
68716
68802
  "src/created-sub-accounts.ts"() {
68717
68803
  "use strict";
@@ -68719,10 +68805,21 @@ var init_created_sub_accounts = __esm({
68719
68805
  path50 = __toESM(require("path"));
68720
68806
  import_zod89 = require("zod");
68721
68807
  init_credentials_store();
68808
+ init_file_lock();
68809
+ import_crypto4 = require("crypto");
68722
68810
  CreatedRecord = import_zod89.z.object({ locationId: import_zod89.z.string().min(1), name: import_zod89.z.string(), at: import_zod89.z.string() });
68723
68811
  RemovedMarker = import_zod89.z.object({ locationId: import_zod89.z.string().min(1), removed: import_zod89.z.literal(true), at: import_zod89.z.string() });
68724
68812
  JournalLine = import_zod89.z.union([RemovedMarker, CreatedRecord]);
68725
68813
  createsInFlight = /* @__PURE__ */ new Set();
68814
+ NAME_RESERVATION_STALE_MS = 15 * 6e4;
68815
+ ReservingLine = import_zod89.z.object({ reserving: import_zod89.z.string().min(1), id: import_zod89.z.string().min(1), pid: import_zod89.z.number().int(), at: import_zod89.z.string() });
68816
+ ReleasedLine = import_zod89.z.object({ released: import_zod89.z.string().min(1), at: import_zod89.z.string() });
68817
+ NameReservationUnavailableError = class extends Error {
68818
+ constructor(name2) {
68819
+ super(`GHL Command could not check whether "${name2}" is being created in another window (its shared file is busy, or cannot be read or written). Nothing was created; try again in a moment.`);
68820
+ this.name = "NameReservationUnavailableError";
68821
+ }
68822
+ };
68726
68823
  DISMISS_CREATED_LABEL = "Dismiss: I removed this sub-account";
68727
68824
  DISMISSED_CARD_BUSY_NOTE = "Dismissed. The pending card could not be removed yet because another window was saving; refresh in a moment.";
68728
68825
  }
@@ -69287,20 +69384,30 @@ ${lines.join("\n")}
69287
69384
  async (args) => {
69288
69385
  if (readAgencyProfile().canCreateSubAccounts === false) return { content: [{ type: "text", text: "Create it in GoHighLevel and register it in GHL Command with its key." }], isError: true };
69289
69386
  const { isKeptLocationIn: isKeptLocationIn2 } = await Promise.resolve().then(() => (init_dashboard(), dashboard_exports));
69290
- const releaseName = reserveSubAccountName(args.name);
69387
+ let releaseName;
69388
+ try {
69389
+ releaseName = reserveSubAccountName(args.name);
69390
+ } catch (re) {
69391
+ return { content: [{ type: "text", text: re instanceof Error ? re.message : String(re) }], isError: true };
69392
+ }
69291
69393
  if (!releaseName) return { content: [{ type: "text", text: createInProgressRefusal(args.name) }], isError: true };
69292
69394
  let outcome;
69395
+ let journaled = false, createdHere2 = false;
69293
69396
  try {
69294
69397
  const already = createdNotYetKept((id2) => registry2 ? isKeptLocationIn2(registry2, id2) : false).find((r) => sameSubAccountName(r.name, args.name));
69295
69398
  if (already) return { content: [{ type: "text", text: alreadyCreatedRefusal(already.name, "with its key (register_location)") }], isError: true };
69296
69399
  outcome = await provisionSubAccount(client, registry2, args, { onCreated: (locationId2) => {
69297
- try {
69298
- recordCreatedSubAccount({ locationId: locationId2, name: args.name, at: (/* @__PURE__ */ new Date()).toISOString() });
69299
- } catch {
69400
+ for (let tries = 0; tries < 3 && !journaled; tries++) {
69401
+ try {
69402
+ recordCreatedSubAccount({ locationId: locationId2, name: args.name, at: (/* @__PURE__ */ new Date()).toISOString() });
69403
+ journaled = true;
69404
+ } catch {
69405
+ }
69300
69406
  }
69407
+ createdHere2 = true;
69301
69408
  } });
69302
69409
  } finally {
69303
- releaseName();
69410
+ releaseName({ keepReservation: createdHere2 && !journaled });
69304
69411
  }
69305
69412
  if (!outcome.ok) {
69306
69413
  const { newClientFailure: newClientFailure2 } = await Promise.resolve().then(() => (init_dashboard(), dashboard_exports));
@@ -72708,7 +72815,7 @@ function errLine(msg4) {
72708
72815
  function preflightWritable() {
72709
72816
  try {
72710
72817
  const dir = ensureAppDataDir();
72711
- const probe = path55.join(dir, `.write-probe.${process.pid}.${(0, import_crypto4.randomBytes)(4).toString("hex")}`);
72818
+ const probe = path55.join(dir, `.write-probe.${process.pid}.${(0, import_crypto5.randomBytes)(4).toString("hex")}`);
72712
72819
  fs55.writeFileSync(probe, "ok");
72713
72820
  fs55.unlinkSync(probe);
72714
72821
  return true;
@@ -72996,14 +73103,14 @@ async function runCli(subcommand, argv) {
72996
73103
  return EXIT_USAGE2;
72997
73104
  }
72998
73105
  }
72999
- var import_node_util4, fs55, path55, import_crypto4, EXIT_OK2, EXIT_USAGE2, EXIT_VALIDATION, EXIT_FS, USAGE;
73106
+ var import_node_util4, fs55, path55, import_crypto5, EXIT_OK2, EXIT_USAGE2, EXIT_VALIDATION, EXIT_FS, USAGE;
73000
73107
  var init_cli = __esm({
73001
73108
  "src/cli.ts"() {
73002
73109
  "use strict";
73003
73110
  import_node_util4 = require("node:util");
73004
73111
  fs55 = __toESM(require("fs"));
73005
73112
  path55 = __toESM(require("path"));
73006
- import_crypto4 = require("crypto");
73113
+ import_crypto5 = require("crypto");
73007
73114
  init_ghl_client();
73008
73115
  init_token_registry();
73009
73116
  init_credentials_store();
@@ -75978,6 +76085,7 @@ __export(dashboard_exports, {
75978
76085
  CLAUDE_REPAIR_MESSAGES: () => CLAUDE_REPAIR_MESSAGES,
75979
76086
  COCKPIT_AFTER_CANCEL_PATHS: () => COCKPIT_AFTER_CANCEL_PATHS,
75980
76087
  CONNECT_AGENCY_BEFORE_CREATE: () => CONNECT_AGENCY_BEFORE_CREATE,
76088
+ CREATED_DISMISS_NOT_LISTED: () => CREATED_DISMISS_NOT_LISTED,
75981
76089
  DOC_NOT_SAVED: () => DOC_NOT_SAVED,
75982
76090
  GATE_BODY: () => GATE_BODY,
75983
76091
  HANDOFF_BUILD_DID_NOT_FINISH: () => HANDOFF_BUILD_DID_NOT_FINISH,
@@ -77128,7 +77236,7 @@ function startRun(locationId2, stage, onDone, meta = {}) {
77128
77236
  if (stage === 0 || stage === 3) await saveStepJobList(rk, run.lines);
77129
77237
  run.ok = outcome.ok;
77130
77238
  const before = readCockpitState().clients[rk]?.stages;
77131
- if (!await saveRetryingBusy(() => onDone(outcome.ok, outcome))) run.lines.push(stage === -1 ? outcome.ok ? CAPABILITY_RUN_NOT_SAVED : CAPABILITY_RUN_STOPPED_NOT_SAVED : outcome.ok ? STEP_STATUS_NOT_SAVED : STOPPED_STEP_STATUS_NOT_SAVED);
77239
+ if (!await saveRetryingBusy(() => onDone(outcome.ok, outcome, run.lines))) run.lines.push(stage === -1 ? outcome.ok ? CAPABILITY_RUN_NOT_SAVED : CAPABILITY_RUN_STOPPED_NOT_SAVED : outcome.ok ? STEP_STATUS_NOT_SAVED : STOPPED_STEP_STATUS_NOT_SAVED);
77132
77240
  const after = readCockpitState().clients[rk]?.stages;
77133
77241
  if (stage === 4 && secondClient) {
77134
77242
  run.lines.push("The client handoff page is prepared for the sub-account's default client only; this client's handoff is not published from here yet.");
@@ -77203,7 +77311,7 @@ function startRun(locationId2, stage, onDone, meta = {}) {
77203
77311
  run.ok = false;
77204
77312
  run.lines.push(err instanceof Error ? err.message : String(err));
77205
77313
  try {
77206
- if (!await saveRetryingBusy(() => onDone(false))) run.lines.push(stage === -1 ? CAPABILITY_RUN_STOPPED_NOT_SAVED : STOPPED_STEP_STATUS_NOT_SAVED);
77314
+ if (!await saveRetryingBusy(() => onDone(false, void 0, run.lines))) run.lines.push(stage === -1 ? CAPABILITY_RUN_STOPPED_NOT_SAVED : STOPPED_STEP_STATUS_NOT_SAVED);
77207
77315
  } catch (e) {
77208
77316
  logQuietly(`run ${rk} stage ${stage}: status not saved: ${e instanceof Error ? e.message : String(e)}`);
77209
77317
  }
@@ -80433,7 +80541,7 @@ function renderPage(state,run){if(state.roster)mergeRoster(state.roster);syncRev
80433
80541
  const p=document.createElement("p");p.textContent=${scriptJson(createdNotSavedNote("{name}"))}.replace("{name}",()=>r.name);shownNotes.push(p.textContent);box.appendChild(p);
80434
80542
  /* rc.3: a sub-account the member removed in GoHighLevel (or will not use) is cleared here for good. */
80435
80543
  const dismiss=document.createElement("button");dismiss.type="button";dismiss.className="cds-btn ghost nc-created-dismiss";dismiss.textContent=${scriptJson(DISMISS_CREATED_LABEL)};
80436
- dismiss.onclick=async()=>{dismiss.disabled=true;const j=await postJSON("/api/pending-remove",{locationId:r.locationId});if(j){if(j.note)alertFor(r.locationId,j.note,r.name);load()}else dismiss.disabled=false};box.appendChild(dismiss);m.appendChild(box)}
80544
+ dismiss.onclick=async()=>{dismiss.disabled=true;const j=await postJSON(PLAN==="pro"?"/api/created-dismiss":"/api/pending-remove",{locationId:r.locationId});if(j){if(j.note)alertFor(r.locationId,j.note,r.name);load()}else dismiss.disabled=false};box.appendChild(dismiss);m.appendChild(box)}
80437
80545
  if(!c0&&registerNotice&&!shownNotes.includes(registerNotice)){const note=document.createElement("p");note.className="cds-callout warn";note.setAttribute("role","alert");note.textContent=registerNotice;m.appendChild(note)}
80438
80546
  let waiting=null;{const wc=bsWaitingCard(state,run,c0?c0.locationId:"");if(wc&&(!c0||bsWhere(c0,state,run).step<3)){m.appendChild(wc);waiting=wc}}
80439
80547
  if(!c0){
@@ -82002,11 +82110,11 @@ async function runDashboard(argv) {
82002
82110
  const st = readCockpitState();
82003
82111
  const approvedAt = st.buildApproved?.[locationId2];
82004
82112
  assertCockpitLicenseHolds();
82005
- startRun(locationId2, -1, (ok, outcome) => {
82113
+ startRun(locationId2, -1, (ok, outcome, runLines) => {
82006
82114
  cockpitCache.invalidate(locationId2, [`capability:${cap2.key}`]);
82007
82115
  try {
82008
82116
  const now = readCockpitState();
82009
- const last = activeRun;
82117
+ const last = { lines: runLines ?? [] };
82010
82118
  const runs = { ...now.capabilityRuns ?? {} };
82011
82119
  const done = (last?.lines ?? []).filter((l) => /^Done[:.]/.test(l)).pop() ?? "";
82012
82120
  runs[`${locationId2}:${cap2.key}`] = {
@@ -82380,7 +82488,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
82380
82488
  const proClient = gate3.plan === "pro" ? new URL(req.url || "/", "http://x").searchParams.get("locationId") ?? "" : void 0;
82381
82489
  const st = proClient === void 0 ? readCockpitState() : sliceStateFor(readCockpitState(), proClient);
82382
82490
  const rosterNow = proClient === void 0 ? currentRoster() : currentRoster().filter((c) => c.locationId === proClient);
82383
- const createdNotKept = createdNotYetKept((id2) => isKeptLocationIn(registry2, id2)).filter((r) => proClient === void 0 || r.locationId === proClient);
82491
+ const createdNotKept = createdNotYetKept((id2) => isKeptLocationIn(registry2, id2));
82384
82492
  const liveClaims = {};
82385
82493
  for (const [loc, c] of Object.entries(st.clients ?? {})) {
82386
82494
  liveClaims[loc] = liveClaim(c.stages?.[6] === "done", st.goLive?.[loc], st.outcomes?.[`${loc}:3`]?.at);
@@ -83863,12 +83971,19 @@ ${opportunityNoteFailure?.detail ?? ""}`);
83863
83971
  if (req.method === "POST" && url === "/api/new-client") {
83864
83972
  readBody(req, res, 2e4, async (body) => {
83865
83973
  let releaseName = null;
83974
+ let createdNotJournaled = false;
83866
83975
  try {
83867
83976
  const args = JSON.parse(body);
83868
83977
  if (!args.name || typeof args.name !== "string") throw new Error("business name is required");
83869
83978
  if (readAgencyProfile().canCreateSubAccounts === false) throw new Error(CREATE_PLAN_REFUSAL);
83870
83979
  args.industry = canonicalIndustry(args.industry);
83871
- releaseName = reserveSubAccountName(args.name);
83980
+ try {
83981
+ releaseName = reserveSubAccountName(args.name);
83982
+ } catch (re) {
83983
+ res.writeHead(503, { "Content-Type": "application/json" });
83984
+ res.end(JSON.stringify({ ok: false, error: re instanceof Error ? re.message : String(re) }));
83985
+ return;
83986
+ }
83872
83987
  if (!releaseName) {
83873
83988
  res.writeHead(409, { "Content-Type": "application/json" });
83874
83989
  res.end(JSON.stringify({ ok: false, error: createInProgressRefusal(args.name) }));
@@ -83906,6 +84021,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
83906
84021
  }
83907
84022
  const dummy = new GHLClient({ apiKey: registry2.getAgencyKey() || "unset" });
83908
84023
  let journaled = false;
84024
+ createdNotJournaled = false;
83909
84025
  const outcome = await provisionSubAccount(dummy, registry2, {
83910
84026
  name: args.name,
83911
84027
  snapshot_id: args.snapshot_id || void 0,
@@ -83918,9 +84034,11 @@ ${opportunityNoteFailure?.detail ?? ""}`);
83918
84034
  timezone: args.timezone || void 0
83919
84035
  }, { onCreated: (locationId2) => {
83920
84036
  journaled = journalCreatedSubAccount(locationId2, args.name);
84037
+ createdNotJournaled = !journaled;
83921
84038
  } });
83922
84039
  if (!outcome.ok) throw new Error(outcome.error);
83923
- if (!journaled) journalCreatedSubAccount(outcome.locationId, args.name);
84040
+ if (!journaled) journaled = journalCreatedSubAccount(outcome.locationId, args.name);
84041
+ createdNotJournaled = !journaled;
83924
84042
  let notSaved = false;
83925
84043
  let cardNotSaved = false;
83926
84044
  try {
@@ -83960,7 +84078,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
83960
84078
  res.writeHead(400, { "Content-Type": "application/json" });
83961
84079
  res.end(JSON.stringify({ ok: false, error: newClientFailure(e) + (refused ? savedNo ? " Command OS saved No for creating sub-accounts." : " Command OS could not save No; update Your agency when storage is available." : ""), ...refused ? { canCreateSubAccounts: false } : {} }));
83962
84080
  } finally {
83963
- releaseName?.();
84081
+ releaseName?.({ keepReservation: createdNotJournaled });
83964
84082
  }
83965
84083
  });
83966
84084
  return;
@@ -84019,6 +84137,22 @@ ${opportunityNoteFailure?.detail ?? ""}`);
84019
84137
  }, "That request is too large.", true);
84020
84138
  return;
84021
84139
  }
84140
+ if (req.method === "POST" && url === "/api/created-dismiss") {
84141
+ readBody(req, res, 1e4, (body) => {
84142
+ try {
84143
+ const { locationId: locationId2 } = JSON.parse(body || "{}");
84144
+ const listed = typeof locationId2 === "string" && locationId2 !== "" && createdNotYetKept((id2) => isKeptLocationIn(registry2, id2)).some((r) => r.locationId === locationId2);
84145
+ if (!listed) throw new Error(CREATED_DISMISS_NOT_LISTED);
84146
+ recordRemovedSubAccount(locationId2);
84147
+ res.writeHead(200, { "Content-Type": "application/json" });
84148
+ res.end(JSON.stringify({ ok: true }));
84149
+ } catch (e) {
84150
+ res.writeHead(400, { "Content-Type": "application/json" });
84151
+ res.end(JSON.stringify({ ok: false, error: e instanceof Error && e.message === CREATED_DISMISS_NOT_LISTED ? CREATED_DISMISS_NOT_LISTED : "That could not be dismissed. Refresh the page and try again." }));
84152
+ }
84153
+ });
84154
+ return;
84155
+ }
84022
84156
  if (req.method === "POST" && url === "/api/pending-remove") {
84023
84157
  readBody(req, res, 1e4, (body) => {
84024
84158
  try {
@@ -84317,7 +84451,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
84317
84451
  process.on("SIGTERM", stop3);
84318
84452
  });
84319
84453
  }
84320
- var import_zod92, import_node_util6, fs57, path57, http2, import_child_process4, import_child_process5, osmod, VIEWER_COCKPIT_MSG, STAGES2, ACCOUNT_TYPES, SECOND_CLIENT_NO_INTAKE, stateRecovery, lastRead, READ_BASE, isMap, same2, stateReadFs, stateSaveFs, CHOOSE_WHAT_THEY_BOUGHT, CHOOSE_BEFORE_BUILD, TEXT_SNIPPETS_LOCKED, SCOPE_REFUSAL, APPROVAL_WITHDRAWN_FOR_STAFF, APPROVAL_WITHDRAWN_NO_TEAM, HANDOFF_WORKFLOW, TEXTING_CONFIRM_HEAD, NOTHING_BOUGHT, NOTHING_BUILT_FROM_TICKS, ASSESSMENT_CHANGED_SINCE_APPROVAL, APPROVAL_WITHDRAWN_FOR_ASSESSMENT, APPROVE_NEEDS_INTAKE, APPROVE_NEEDS_BRIEF, INDUSTRY_FIRST, NOTHING_TICKED_IN_PLAN, SCOPE_CHANGED_SINCE_APPROVAL, APPROVAL_WITHDRAWN_FOR_SCOPE, BUILD_WHAT_WAS_WAITING, BUILD_KEEPS_WHAT_WAS_REMOVED, BUILD_ADDS_AND_KEEPS, BRIEF_BEHIND_BOUGHT, BUILD_BEHIND_BOUGHT, APPROVE_WAITS_FOR_REVIEW_UPDATE, ALWAYS_BUILT_NOTE, APPROVE_NEEDS_FRESH_REVIEW, BUILD_LOCK_BUSY, APPROVE_WAITS_FOR_BUILD, REVOKE_WAITS_FOR_BUILD, HANDOFF_WAITS_ON_REBUILD, HANDOFF_BUILD_DID_NOT_FINISH, HANDOFF_VERIFY_DID_NOT_PASS, HANDOFF_NEEDS_APPROVAL, HANDOFF_PAGE_NOT_APPROVED, HANDOFF_PAGE_BEHIND_BOUGHT, INACTIVE, OFF_LICENSE, LICENSE_RECHECK_MS, activeRun, runStarting, buildOneTime, oneTimeView, runForTests, handoffEmailPreviews, handoffsSending, REVOKE_WAITS_FOR_HANDOFF, busyRetry, UPGRADE_MSG, DOC_EXTS, DOC_MAX_BYTES, queueScanWatch, queueScanOnce, ASSESSMENT_PIPELINE_MISSING, GATE_BODY, GATE_BODY_MAX, DOC_NOT_SAVED, NO_KEY_ON_FILE, PARKED_NOT_BUILT, NEEDS_AGENCY_KEY, CONNECT_AGENCY_BEFORE_CREATE, NEW_CLIENT_CARD_NOT_SAVED, BUILD_RECORD_NOT_SAVED, VERIFY_JOBS_NOT_SAVED, STEP_JOBS_NOT_SAVED, VERIFY_ATTEMPT_NOT_SAVED, STEP_STATUS_NOT_SAVED, STOPPED_STEP_STATUS_NOT_SAVED, OUTCOME_NOT_SAVED, CAPABILITY_RUN_NOT_SAVED, CAPABILITY_RUN_STOPPED_NOT_SAVED, INTAKE_UNREADABLE, INTAKE_READ_TRANSIENT, INTAKE_REFRESH_RETRYING, ACCOUNT_CHANGED_DURING_READ, commitWithoutRead, SNAPSHOTS_CHANGED_DURING_READ, IntakeColdError, INTAKE_ANSWERS_MISSING, INTAKE_READ_FAILED, RUN_IN_PROGRESS, BRIEF_NEEDS_INTAKE2, BUSINESS_TYPE_FOR_INDUSTRY, INTAKE_FORM_REQUIRED, INTAKE_CONNECT_REQUIRED, INTAKE_OWN_MESSAGES, currentRosterRef, staffGateRef, BOARD_ROUTES, BOARD_TITLES, SCREEN_HEADS, NO_SUCH_PAGE, CHOOSE_CLIENT, ONE_CLIENT_ONLY, anthropicKeyWrites, SEAT_TOKEN_HEADER, notSignedInMsg, NOT_SIGNED_IN_MSG, SEAT_GUARDED_READS, COCKPIT_AFTER_CANCEL_PATHS, licenceRefusedHooks, ConnectionRequest, DisconnectRequest, ReportBrandSaveRequest;
84454
+ var import_zod92, import_node_util6, fs57, path57, http2, import_child_process4, import_child_process5, osmod, VIEWER_COCKPIT_MSG, STAGES2, ACCOUNT_TYPES, SECOND_CLIENT_NO_INTAKE, stateRecovery, lastRead, READ_BASE, isMap, same2, stateReadFs, stateSaveFs, CHOOSE_WHAT_THEY_BOUGHT, CHOOSE_BEFORE_BUILD, TEXT_SNIPPETS_LOCKED, SCOPE_REFUSAL, APPROVAL_WITHDRAWN_FOR_STAFF, APPROVAL_WITHDRAWN_NO_TEAM, HANDOFF_WORKFLOW, TEXTING_CONFIRM_HEAD, NOTHING_BOUGHT, NOTHING_BUILT_FROM_TICKS, ASSESSMENT_CHANGED_SINCE_APPROVAL, APPROVAL_WITHDRAWN_FOR_ASSESSMENT, APPROVE_NEEDS_INTAKE, APPROVE_NEEDS_BRIEF, INDUSTRY_FIRST, NOTHING_TICKED_IN_PLAN, SCOPE_CHANGED_SINCE_APPROVAL, APPROVAL_WITHDRAWN_FOR_SCOPE, BUILD_WHAT_WAS_WAITING, BUILD_KEEPS_WHAT_WAS_REMOVED, BUILD_ADDS_AND_KEEPS, BRIEF_BEHIND_BOUGHT, BUILD_BEHIND_BOUGHT, APPROVE_WAITS_FOR_REVIEW_UPDATE, ALWAYS_BUILT_NOTE, APPROVE_NEEDS_FRESH_REVIEW, BUILD_LOCK_BUSY, APPROVE_WAITS_FOR_BUILD, REVOKE_WAITS_FOR_BUILD, HANDOFF_WAITS_ON_REBUILD, HANDOFF_BUILD_DID_NOT_FINISH, HANDOFF_VERIFY_DID_NOT_PASS, HANDOFF_NEEDS_APPROVAL, HANDOFF_PAGE_NOT_APPROVED, HANDOFF_PAGE_BEHIND_BOUGHT, INACTIVE, OFF_LICENSE, LICENSE_RECHECK_MS, activeRun, runStarting, buildOneTime, oneTimeView, runForTests, handoffEmailPreviews, handoffsSending, REVOKE_WAITS_FOR_HANDOFF, busyRetry, UPGRADE_MSG, DOC_EXTS, DOC_MAX_BYTES, queueScanWatch, queueScanOnce, ASSESSMENT_PIPELINE_MISSING, GATE_BODY, GATE_BODY_MAX, DOC_NOT_SAVED, NO_KEY_ON_FILE, PARKED_NOT_BUILT, NEEDS_AGENCY_KEY, CONNECT_AGENCY_BEFORE_CREATE, CREATED_DISMISS_NOT_LISTED, NEW_CLIENT_CARD_NOT_SAVED, BUILD_RECORD_NOT_SAVED, VERIFY_JOBS_NOT_SAVED, STEP_JOBS_NOT_SAVED, VERIFY_ATTEMPT_NOT_SAVED, STEP_STATUS_NOT_SAVED, STOPPED_STEP_STATUS_NOT_SAVED, OUTCOME_NOT_SAVED, CAPABILITY_RUN_NOT_SAVED, CAPABILITY_RUN_STOPPED_NOT_SAVED, INTAKE_UNREADABLE, INTAKE_READ_TRANSIENT, INTAKE_REFRESH_RETRYING, ACCOUNT_CHANGED_DURING_READ, commitWithoutRead, SNAPSHOTS_CHANGED_DURING_READ, IntakeColdError, INTAKE_ANSWERS_MISSING, INTAKE_READ_FAILED, RUN_IN_PROGRESS, BRIEF_NEEDS_INTAKE2, BUSINESS_TYPE_FOR_INDUSTRY, INTAKE_FORM_REQUIRED, INTAKE_CONNECT_REQUIRED, INTAKE_OWN_MESSAGES, currentRosterRef, staffGateRef, BOARD_ROUTES, BOARD_TITLES, SCREEN_HEADS, NO_SUCH_PAGE, CHOOSE_CLIENT, ONE_CLIENT_ONLY, anthropicKeyWrites, SEAT_TOKEN_HEADER, notSignedInMsg, NOT_SIGNED_IN_MSG, SEAT_GUARDED_READS, COCKPIT_AFTER_CANCEL_PATHS, licenceRefusedHooks, ConnectionRequest, DisconnectRequest, ReportBrandSaveRequest;
84321
84455
  var init_dashboard = __esm({
84322
84456
  "src/dashboard.ts"() {
84323
84457
  "use strict";
@@ -84559,6 +84693,7 @@ var init_dashboard = __esm({
84559
84693
  PARKED_NOT_BUILT = "Template and hidden accounts are not built from Command OS. Change the account's type to Client first.";
84560
84694
  NEEDS_AGENCY_KEY = `Your agency is not connected yet. ${AGENCY_KEY_HOW}`;
84561
84695
  CONNECT_AGENCY_BEFORE_CREATE = `Connect your agency key before creating a sub-account. ${AGENCY_KEY_HOW}`;
84696
+ CREATED_DISMISS_NOT_LISTED = "That sub-account is not listed as created here. Refresh the page.";
84562
84697
  NEW_CLIENT_CARD_NOT_SAVED = "The sub-account was created, but its card could not be saved yet: refresh in a moment.";
84563
84698
  BUILD_RECORD_NOT_SAVED = "This run finished and may have changed the account; only its record could not be saved because another window was saving. Check the account before running it again.";
84564
84699
  VERIFY_JOBS_NOT_SAVED = "The job list from this check could not be saved because another window was saving; run it again.";
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: f56d1be300fe9f07 -->
7
+ <!-- guides-hash: 2f739a97ecc1ad29 -->
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
@@ -1715,7 +1715,7 @@ Show me the first ten rows in the chat so I can sanity check them before you wri
1715
1715
  <li><b>Done means:</b> you asked &quot;Which account am I in?&quot; and Claude answered with the right account name. That is the whole test.</li>
1716
1716
  <li>Step 2 is part of the install, not an extra. Until it is done, Claude will keep reminding you the install is one step from finished. Nothing will pretend to be complete when it is not.</li>
1717
1717
  <li>If anything says &quot;not working&quot; afterward, say &quot;run a health check&quot;: it tests every connection and tells you in plain English what, if anything, needs attention. Its first check, Host, names the assistant GHL Command is running in, and its License plan line says which plan this session runs on.</li>
1718
- <li>You can have GHL Command open in more than one assistant window on the same computer. Each window saves your registered accounts, connections and cockpit board under a shared lock and adds its change to what the other saved. Saves keep unrelated changes made by another window. Editing the same details in both windows at once can still replace the earlier change. If the other window still holds the save after 5 seconds, the waiting save does not happen; if your plan includes the cockpit, some of a cockpit run's own saves try up to three times over a few seconds instead. When a tool, a run or the cockpit says something was not saved, follow what it tells you to do, such as running it again or checking the account and refreshing. A few background notes are skipped without a message. A sub-account that was created is still reported as created, even if its key or its card could not be saved yet. Do not create it again; register it with its key. If neither its key nor its card was saved and your plan includes the cockpit, its New Client page may show it as &quot;Created:&quot; followed by its name when no client is selected, and while it shows, the cockpit and chat refuse to create that name again. To clear it, register it with its key or, if you already removed it in GoHighLevel, press &quot;Dismiss: I removed this sub-account&quot;. Dismiss does not delete the sub-account or check that you removed it. If nothing shows, check GoHighLevel before you create it again.</li>
1718
+ <li>You can have GHL Command open in more than one assistant window on the same computer. Each window saves your registered accounts, connections and cockpit board under a shared lock and adds its change to what the other saved. Saves keep unrelated changes made by another window. Editing the same details in both windows at once can still replace the earlier change. If the other window still holds the save after 5 seconds, the waiting save does not happen; if your plan includes the cockpit, some of a cockpit run's own saves try up to three times over a few seconds instead. When a tool, a run or the cockpit says something was not saved, follow what it tells you to do, such as running it again or checking the account and refreshing. A few background notes are skipped without a message. A sub-account that was created is still reported as created, even if its key or its card could not be saved yet. Do not create it again; register it with its key. If neither its key nor its card was saved and your plan includes the cockpit, its New Client page may show it as &quot;Created:&quot; followed by its name when no client is selected, and while it shows, the cockpit and chat refuse to create that name again. To clear it, register it with its key or, if you already removed it in GoHighLevel, press &quot;Dismiss: I removed this sub-account&quot;. Dismiss does not delete the sub-account or check that you removed it. If nothing shows, check GoHighLevel before you create it again. If the same business name is already being created in another window on this computer, or, if your plan includes the cockpit, in the cockpit while you ask in chat, the second attempt is refused before GoHighLevel is asked and nothing is created: wait for the first to finish; do not create it again. This check works only when every open window, and the cockpit, run 3.97.3 or later; one started before you updated does not take part, so after updating, restart your computer before you create a sub-account. If GHL Command cannot check this because its shared file is busy, or cannot be read or written, it also creates nothing and asks you to try again in a moment.</li>
1719
1719
  <li>Stuck at any point? Email support@ghlcommand.com and a human answers.</li>
1720
1720
  </ul>
1721
1721
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elitedcs/ghl-mcp",
3
- "version": "3.97.2",
3
+ "version": "3.97.3",
4
4
  "mcpName": "io.github.drjerryrelth/ghl-command",
5
5
  "description": "GoHighLevel MCP Server for Claude. 250 tools for CRM, automation and marketing, an account-wide workflow audit, live funnel-capture verification and a workflow builder, across client accounts.",
6
6
  "main": "dist/index.js",