@elitedcs/ghl-mcp 3.91.0 → 3.91.1

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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.91.1: Step 7, Staff, says what is true, and one rule decides who gets an admin login
4
+
5
+ This release is everything in 3.91.0, plus the changes below.
6
+
7
+ **One rule for who gets an admin login.** Anyone whose job says owner, founder, admin, administrator, CEO, principal or director gets an admin login. If nobody on the team has a job like that, the first person listed gets one, so someone can run the account. Build and Add them for me now follow the same rule, and both mark that person with Admin login before anything is made. Someone who already has a login keeps the level they have.
8
+
9
+ **Step 7, Staff, says only what it checked.** It says Build waits for the staff, not Intake, it no longer says calendars and lead alerts will use the people it found, and when it cannot add people it says why: the agency key is not connected yet, your plan does not allow it, or GoHighLevel refused it. The board's staff panel now says when someone is in the account at a different login level, as step 7 does. Blueprint Review says the staff are not checked yet, rather than not in the account, when it could not read them.
10
+
11
+ **A one-time password stays on the page you are on.** When GoHighLevel sets a password for someone it has not listed yet, the page keeps it and shows it the first time a check finds them; Check again and a second Add them for me keep it too. Reloading the page still clears it, so copy it when it appears. Someone taken off the team while their login was being made is shown as still being set up, never as could not be added. When a Build in the cockpit makes someone and GoHighLevel requires a password, the cockpit never shows or keeps it: reset that person's password in GoHighLevel.
12
+
13
+ **Known limits.** Other programs on this computer, or other people signed in to it, can read what the cockpit shows, including a one-time password while it is on step 7, Staff. Keep the computer to yourself. If two people on the team share a name, their password lines look the same; check the email in GoHighLevel. If you take someone off the team while their login is still being set up, their password is not shown; reset it in GoHighLevel if you add them back.
14
+
3
15
  ## 3.91.0: The board and the brief open much faster
4
16
 
5
17
  This release is everything in 3.90.1, plus the change below.
@@ -26,8 +38,6 @@ This release is everything in 3.90.0, plus the fix below. It fixes behavior that
26
38
 
27
39
  **A new login is never made twice while GoHighLevel catches up.** GoHighLevel can take a minute to list a login it has just made. Until it does, step 7 and the board say the person "is still being set up in GoHighLevel", and Add them for me does not send them again, from any page or window, for up to 10 minutes. If GoHighLevel insisted on a password, step 7 now shows the one-time password, as the board does.
28
40
 
29
- **Known limits, fixed in the next release.** The board's staff panel does not show when someone's login level differs from the list. If someone is taken off the team while their login is being made and the connection drops, step 7 can still say they could not be added. Without an agency key, step 7 says your plan does not let you add people. The staff step says Intake waits for it (only Build does), and says calendars and lead alerts will use the people found, which it does not check. Blueprint Review says staff are not in the account when it could not read them. When nobody's job says owner, Build makes the first person listed an admin, while Add them for me makes them a user; Build's confirmation shows it before anything is made.
30
-
31
41
  After Add them for me or Confirm and build, check the client's GoHighLevel Users list.
32
42
 
33
43
  ## 3.90.0: the cockpit never acts on the wrong client, and refuses a change made from an old page
@@ -860,7 +870,6 @@ website, and that pages are a separate job after the account exists, on hosting
860
870
  member owns. A test pins the skill and the plan format together so they cannot
861
871
  disagree again.
862
872
 
863
-
864
873
  ## 3.83.0 — a build you can check, and no funnel at the end of it
865
874
 
866
875
  **Blueprint no longer builds a funnel, a page, or a website of any kind.** GoHighLevel's own page
@@ -889,14 +898,12 @@ retried into a duplicate; and read-backs retry a timeout but never retry a genui
889
898
  running GHL Command, the stages use the plan you already pay for. Saving a key switches every stage to
890
899
  usage billing, which the cockpit and the member hub now say plainly.
891
900
 
892
-
893
901
  - Brief checks the intake and writes the review; the build plan is prepared when Build runs.
894
902
  - Changed intake answers require a fresh review, changed build plans require verification, and live workflow repairs need approval of the listed changes.
895
903
  - Client pages keep the selected client, open the shared intake answers, separate existing inventory from build evidence, and keep generated drafts for review.
896
904
  - Verify prepares the client handoff. Local files are shown as prepared, not published, and handoff email delivery requires a reviewed recipient and explicit confirmation.
897
905
  - Your agency now has a creation-only switch for sub-accounts from chat. Package preparation creates catalog products and leaves pricing and payment links as a visible manual task.
898
906
 
899
-
900
907
  - Register an existing sub-account on either plan path, replace a saved account key, and see chat registrations in the cockpit without restarting. Creation checks the agency connection first and keeps an account visible when its key could not be saved.
901
908
  - Intake keeps Interview answers, loads submitted answers, asks before replacing corrections, and identifies missing respondent details. Source uploads are limited to supported text formats and show the 60,000-character limit. Prepared forms and client documents have clickable links.
902
909
  - Client Builds checks account access and setup before running, explains Intake’s writes, and uses the same practice-account rules everywhere. Failed verification blocks sign-off, and manual obligations remain visible until checked.
@@ -910,7 +917,6 @@ usage billing, which the cockpit and the member hub now say plainly.
910
917
  - **Build a client account guidance corrected.** Existing clients are behind “Work on a client already registered →” and are never pre-selected. Registration confirms the sub-account name and id, never a key hint.
911
918
  - **Deletion is separate from registration.** Removing a saved key does not delete the GoHighLevel account. Deletion requires separate authorization; no claim is made that the account's creation method determines whether it can be deleted.
912
919
 
913
-
914
920
  ## 3.82.0 — the cockpit laid out like the hub, for Command OS subscribers
915
921
 
916
922
  The Command OS cockpit was built for one job, the Blueprint build, and that one section
@@ -1502,7 +1508,6 @@ It caught two files that a careful pass by hand had already missed.
1502
1508
 
1503
1509
  Counts unchanged: 247 tools, 51 modules, free tier 111. No tool behaviour changed.
1504
1510
 
1505
-
1506
1511
  ## 3.72.0 — an audit that counts your workflows once
1507
1512
 
1508
1513
  A member ran the account audit on a large agency account. The report came back claiming
@@ -2088,7 +2093,6 @@ them were visible until a guide was written against them and run for real.
2088
2093
  supported). Documented so nobody builds a "send to a third party" node that
2089
2094
  silently mails the contact.
2090
2095
 
2091
-
2092
2096
  ## 3.62.0 — The user guide ships inside the product
2093
2097
 
2094
2098
  - **New tool `get_user_guide`** (free tier included): opens the full GHL Command
@@ -2487,7 +2491,6 @@ builds inside it and verify_funnel proves it.
2487
2491
  a temp dir AND writeCredentials refuses to run under vitest without an
2488
2492
  override.
2489
2493
 
2490
-
2491
2494
  ## 3.50.0 — FREE read-only tier
2492
2495
 
2493
2496
  A free license (`ghlcommand.com/free`, key by email, no card) now runs the same
@@ -2541,7 +2544,6 @@ No behavior changes; text only.
2541
2544
  - `enable_workflow_builder` and `auto_capture_firebase_script` tool descriptions
2542
2545
  point to `capture_firebase_interactive` first.
2543
2546
 
2544
-
2545
2547
  ## 3.49.0 — One-click Workflow Builder unlock (`capture_firebase_interactive`)
2546
2548
 
2547
2549
  The buyer's whole job is now: log into GHL in a Chrome window the tool opens.
@@ -2566,7 +2568,6 @@ No DevTools, no console paste, no JSON shuttling.
2566
2568
  console-paste path, unchanged).
2567
2569
  - Tool count: 228 → 229 (177 without Firebase).
2568
2570
 
2569
-
2570
2571
  ## 3.48.2 — Docs patch: tool-count claims corrected (the real number is 228)
2571
2572
 
2572
2573
  No behavior changes; text only, plus one new test.
@@ -2581,7 +2582,6 @@ No behavior changes; text only, plus one new test.
2581
2582
  `registerAllTools` and fails the suite if package.json, README, or setup-tool copy
2582
2583
  ever disagrees again.
2583
2584
 
2584
-
2585
2585
  ## 3.48.1 — Docs patch: Firebase capture friction + live pricing copy
2586
2586
 
2587
2587
  No behavior changes; text only.
@@ -2597,7 +2597,6 @@ No behavior changes; text only.
2597
2597
  7-step IndexedDB walk is demoted to a collapsed fallback for locked-down browsers;
2598
2598
  multi-tenant instructions reference the script instead of the manual steps.
2599
2599
 
2600
-
2601
2600
  ## 3.48.0 — Blueprint: complete workflow-building (opportunities, branching, appointment timers, opt-in publish)
2602
2601
 
2603
2602
  Completes the `apply_build_plan` executor so a Blueprint build produces workflows that
package/README.md CHANGED
@@ -899,7 +899,7 @@ Built by **[Elite DCs, LLC](https://elitedcs.com)** — a digital marketing and
899
899
 
900
900
  **Tech stack:** TypeScript, Node.js, esbuild, MCP SDK, Zod, GHL API v2, Firebase Auth
901
901
 
902
- **Version:** 3.91.0
902
+ **Version:** 3.91.1
903
903
 
904
904
  ---
905
905
 
package/dist/index.js CHANGED
@@ -8298,19 +8298,24 @@ async function createOrBindUserWith(args, deps) {
8298
8298
  let generatedPassword;
8299
8299
  let raw;
8300
8300
  let lastError = "";
8301
+ const hide = (t) => password ? t.split(password).join("[hidden]") : t;
8301
8302
  for (let attempt = 1; attempt <= 3; attempt++) {
8302
8303
  try {
8303
8304
  raw = await deps.postUser(buildCreateUserBody(args, { includePermissions, password }));
8304
8305
  lastError = "";
8305
8306
  break;
8306
8307
  } catch (e) {
8307
- lastError = msg(e);
8308
+ lastError = hide(msg(e));
8308
8309
  if (inBuildRequestScope() && isRetryableAccountError(e)) break;
8309
8310
  if (classifyCreateUserError(lastError) === "feature_unavailable") break;
8310
8311
  if (!isDefinitiveRefusal(lastError)) break;
8311
8312
  if (/password/i.test(lastError) && !password) {
8312
8313
  password = generateTemporaryPassword();
8313
8314
  generatedPassword = password;
8315
+ try {
8316
+ await deps.onPassword?.(password);
8317
+ } catch {
8318
+ }
8314
8319
  continue;
8315
8320
  }
8316
8321
  if (/permission/i.test(lastError) && includePermissions) {
@@ -8321,7 +8326,7 @@ async function createOrBindUserWith(args, deps) {
8321
8326
  break;
8322
8327
  }
8323
8328
  }
8324
- if (lastError) return { ok: false, stage: "create", error: lastError, errorKind: classifyCreateUserError(lastError) };
8329
+ if (lastError) return { ok: false, stage: "create", error: lastError, errorKind: classifyCreateUserError(lastError), ...generatedPassword && !isDefinitiveRefusal(lastError) ? { generatedPassword } : {} };
8325
8330
  const beforeIds = new Set(before.map((u) => u.id));
8326
8331
  let found;
8327
8332
  for (let attempt = 1; attempt <= retries; attempt++) {
@@ -8329,7 +8334,7 @@ async function createOrBindUserWith(args, deps) {
8329
8334
  const now = await deps.listUsers();
8330
8335
  found = findUserByEmail(now, args.email) ?? now.find((u) => !beforeIds.has(u.id) && !u.email);
8331
8336
  } catch (error) {
8332
- if (inBuildRequestScope()) return { ok: false, stage: "verify", error: msg(error) };
8337
+ if (inBuildRequestScope()) return { ok: false, stage: "verify", error: hide(msg(error)), ...generatedPassword ? { generatedPassword } : {} };
8333
8338
  found = void 0;
8334
8339
  }
8335
8340
  if (found) break;
@@ -8341,11 +8346,9 @@ async function createOrBindUserWith(args, deps) {
8341
8346
  warnings.push("the user was created but did not appear on this location's user list yet (read-after-write lag) \u2014 the id comes from the create response; re-run get_users to confirm.");
8342
8347
  return { ok: true, userId: fromResponse, status: "created", email: args.email, generatedPassword, warnings, rawResponse: raw };
8343
8348
  }
8344
- return { ok: false, stage: "verify", error: "POST /users/ succeeded but the user never appeared on this location's user list and the response carried no id \u2014 check " + GHL_STAFF_PAGE + " in GHL before re-running.", rawResponse: raw };
8345
- }
8346
- if (generatedPassword) {
8347
- warnings.push("GoHighLevel required a password, so one was generated. It is returned ONCE here (generatedPassword) \u2014 hand it to the staff member over a secure channel and have them change it at first login. It is not stored anywhere.");
8349
+ return { ok: false, stage: "verify", error: "POST /users/ succeeded but the user never appeared on this location's user list and the response carried no id \u2014 check " + GHL_STAFF_PAGE + " in GHL before re-running.", rawResponse: raw, ...generatedPassword ? { generatedPassword } : {} };
8348
8350
  }
8351
+ if (generatedPassword) warnings.push(PASSWORD_HANDOVER_WARNING);
8349
8352
  return { ok: true, userId: found.id, status: "created", email: args.email, generatedPassword, warnings, rawResponse: raw };
8350
8353
  }
8351
8354
  function extractUserId(raw) {
@@ -8361,7 +8364,7 @@ function extractUserId(raw) {
8361
8364
  }
8362
8365
  return void 0;
8363
8366
  }
8364
- async function createOrBindUser(client, registry2, args) {
8367
+ async function createOrBindUser(client, registry2, args, onPassword) {
8365
8368
  const agencyKey = registry2?.getAgencyKey();
8366
8369
  if (!agencyKey) {
8367
8370
  return {
@@ -8381,14 +8384,15 @@ async function createOrBindUser(client, registry2, args) {
8381
8384
  listUsers: async () => pickUsers(await client.get("/users/", { params: { locationId: args.locationId } })),
8382
8385
  // noRetry: an invite is not idempotent — a retry after a lost-but-successful
8383
8386
  // response would create a second user / send a second invite.
8384
- postUser: (body) => agencyClient.post("/users/", { body, noRetry: true })
8387
+ postUser: (body) => agencyClient.post("/users/", { body, noRetry: true }),
8388
+ ...onPassword ? { onPassword } : {}
8385
8389
  }
8386
8390
  );
8387
8391
  }
8388
8392
  function msg(e) {
8389
8393
  return e instanceof Error ? e.message : String(e);
8390
8394
  }
8391
- var import_node_crypto4, normEmail, FEATURE_UNAVAILABLE_TEXT, sleep2;
8395
+ var import_node_crypto4, normEmail, FEATURE_UNAVAILABLE_TEXT, sleep2, PASSWORD_HANDOVER_WARNING;
8392
8396
  var init_user_provisioning = __esm({
8393
8397
  "src/user-provisioning.ts"() {
8394
8398
  "use strict";
@@ -8399,6 +8403,7 @@ var init_user_provisioning = __esm({
8399
8403
  normEmail = (e) => e.trim().toLowerCase();
8400
8404
  FEATURE_UNAVAILABLE_TEXT = "does not have access to this feature";
8401
8405
  sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
8406
+ PASSWORD_HANDOVER_WARNING = "GoHighLevel required a password, so one was generated. It is returned ONCE here (generatedPassword) \u2014 hand it to the staff member over a secure channel and have them change it at first login.";
8402
8407
  }
8403
8408
  });
8404
8409
 
@@ -8434,7 +8439,7 @@ function registerUserTools(server2, client, registry2) {
8434
8439
  safeTool(
8435
8440
  server2,
8436
8441
  "create_user",
8437
- "Add a staff member (user) to the current sub-account. AGENCY-level write: GoHighLevel creates users through POST /users/ with an agency Private Integration key (users.write), so this needs register_agency_key first \u2014 the sub-account key cannot do it. IDEMPOTENT: if a user with that email already exists on the location (get_users) the tool binds to them and creates nothing. No password is invented: the first attempt omits it so GoHighLevel sends the staff member an invite email [UNVERIFIED live \u2014 if GHL insists on a password, one is generated, the create is retried once, and the password is returned ONCE as `generatedPassword`; hand it over securely, it is not stored]. Permissions default to a sane per-role set (documented UserPermissions booleans, [UNVERIFIED] key-for-key; if GHL rejects the object the create retries without it and GHL's own defaults apply \u2014 a warning says so). The new user is verified by re-reading get_users before its id is returned. On failure GHL's error text is returned verbatim. Used by apply_build_plan to build the staff a Blueprint plan lists; workflow notification / task steps then target these real user ids instead of a placeholder.",
8442
+ "Add a staff member (user) to the current sub-account. AGENCY-level write: GoHighLevel creates users through POST /users/ with an agency Private Integration key (users.write), so this needs register_agency_key first \u2014 the sub-account key cannot do it. IDEMPOTENT: if a user with that email already exists on the location (get_users) the tool binds to them and creates nothing. No password is invented: the first attempt omits it so GoHighLevel sends the staff member an invite email [UNVERIFIED live \u2014 if GHL insists on a password, one is generated, the create is retried once, and the password is returned ONCE as `generatedPassword`; hand it over securely]. Permissions default to a sane per-role set (documented UserPermissions booleans, [UNVERIFIED] key-for-key; if GHL rejects the object the create retries without it and GHL's own defaults apply \u2014 a warning says so). The new user is verified by re-reading get_users before its id is returned. On failure GHL's error text is returned verbatim. Used by apply_build_plan to build the staff a Blueprint plan lists; workflow notification / task steps then target these real user ids instead of a placeholder.",
8438
8443
  {
8439
8444
  firstName: import_zod22.z.string().min(1).describe("Staff member's first name."),
8440
8445
  lastName: import_zod22.z.string().describe("Staff member's last name (GHL requires it as a string; pass '' only if you truly have none)."),
@@ -32228,7 +32233,7 @@ function runStage(locationId2, stage, onEvent, opts = {}) {
32228
32233
  const cmd = claudeCommand(bin, buildClaudeArgs(spec, locationId2, locationName, opts.context));
32229
32234
  const child = (0, import_child_process3.spawn)(cmd.command, cmd.args, {
32230
32235
  stdio: ["ignore", "pipe", "pipe"],
32231
- env: { ...process.env, ...stageFirebaseEnv(), ...progressDir ? { [PROGRESS_DIR_ENV]: progressDir } : {}, GHL_COMMAND_STAGE: String(stage), GHL_COMMAND_INTAKE_HASH: opts.context?.inputHash ?? "", GHL_COMMAND_INTAKE_FACTS: intakeFactsEnv(opts.context?.normalizedAnswers), GHL_COMMAND_TEXT_SNIPPETS: opts.context?.textSnippets ? "1" : "", GHL_COMMAND_INTAKE_BRIEF: opts.context?.intakeBrief ? JSON.stringify(opts.context.intakeBrief) : "", [EXPECT_BUILD_ENV]: ownBuildId(), MCP_CONNECTION_NONBLOCKING: "false", ...opts.clientKey ? { [CLIENT_KEY_ENV]: opts.clientKey } : {} }
32236
+ env: { ...process.env, ...stageFirebaseEnv(), ...opts.secretEnv ?? {}, ...progressDir ? { [PROGRESS_DIR_ENV]: progressDir } : {}, GHL_COMMAND_STAGE: String(stage), GHL_COMMAND_INTAKE_HASH: opts.context?.inputHash ?? "", GHL_COMMAND_INTAKE_FACTS: intakeFactsEnv(opts.context?.normalizedAnswers), GHL_COMMAND_TEXT_SNIPPETS: opts.context?.textSnippets ? "1" : "", GHL_COMMAND_INTAKE_BRIEF: opts.context?.intakeBrief ? JSON.stringify(opts.context.intakeBrief) : "", [EXPECT_BUILD_ENV]: ownBuildId(), MCP_CONNECTION_NONBLOCKING: "false", ...opts.clientKey ? { [CLIENT_KEY_ENV]: opts.clientKey } : {} }
32232
32237
  });
32233
32238
  let lastProgressStamp = "";
32234
32239
  let inventoryError;
@@ -36499,7 +36504,7 @@ var require_package = __commonJS({
36499
36504
  "package.json"(exports2, module2) {
36500
36505
  module2.exports = {
36501
36506
  name: "@elitedcs/ghl-mcp",
36502
- version: "3.91.0",
36507
+ version: "3.91.1",
36503
36508
  mcpName: "io.github.drjerryrelth/ghl-command",
36504
36509
  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.",
36505
36510
  main: "dist/index.js",
@@ -37117,6 +37122,39 @@ function removeCacheFileAtRest(file3) {
37117
37122
  } catch {
37118
37123
  }
37119
37124
  }
37125
+ function deadlineClock(ms, now, expire) {
37126
+ let timer;
37127
+ let left = ms;
37128
+ let since = now();
37129
+ let holds = 0;
37130
+ let fired = false;
37131
+ const arm = () => {
37132
+ since = now();
37133
+ timer = setTimeout(() => {
37134
+ timer = void 0;
37135
+ fired = true;
37136
+ expire();
37137
+ }, Math.max(0, left));
37138
+ timer.unref?.();
37139
+ };
37140
+ arm();
37141
+ return {
37142
+ hold: () => {
37143
+ if (holds++ === 0 && timer) {
37144
+ clearTimeout(timer);
37145
+ timer = void 0;
37146
+ left -= now() - since;
37147
+ }
37148
+ },
37149
+ go: () => {
37150
+ if (holds > 0 && --holds === 0 && !fired && !timer) arm();
37151
+ },
37152
+ stop: () => {
37153
+ if (timer) clearTimeout(timer);
37154
+ timer = void 0;
37155
+ }
37156
+ };
37157
+ }
37120
37158
  function isSlowDown(e) {
37121
37159
  return e instanceof Error && /GHL API Error 429\b/.test(e.message);
37122
37160
  }
@@ -37669,6 +37707,7 @@ var init_cockpit_cache = __esm({
37669
37707
  gateFor(locationId2, deadline2, expired, clock2) {
37670
37708
  return {
37671
37709
  deadline: deadline2,
37710
+ expired,
37672
37711
  take: async () => {
37673
37712
  for (; ; ) {
37674
37713
  if (expired()) throw new PacerDeadlineError();
@@ -37733,9 +37772,6 @@ var init_cockpit_cache = __esm({
37733
37772
  this.busyLocations.set(j.locationId, (this.busyLocations.get(j.locationId) ?? 0) + 1);
37734
37773
  this.maxInFlightLocations = Math.max(this.maxInFlightLocations, this.busyLocations.size);
37735
37774
  let expired = false;
37736
- let timer;
37737
- let left = this.limits.taskDeadlineMs;
37738
- let since = this.now();
37739
37775
  let expire;
37740
37776
  const deadline2 = new Promise((_, reject) => {
37741
37777
  expire = () => {
@@ -37745,24 +37781,10 @@ var init_cockpit_cache = __esm({
37745
37781
  });
37746
37782
  deadline2.catch(() => {
37747
37783
  });
37748
- const arm = () => {
37749
- since = this.now();
37750
- timer = setTimeout(expire, Math.max(0, left));
37751
- timer.unref?.();
37752
- };
37753
- const clock2 = { hold: () => {
37754
- if (timer) {
37755
- clearTimeout(timer);
37756
- timer = void 0;
37757
- left -= this.now() - since;
37758
- }
37759
- }, go: () => {
37760
- if (!expired && !timer) arm();
37761
- } };
37762
- arm();
37784
+ const clock2 = deadlineClock(this.limits.taskDeadlineMs, this.now, () => expire());
37763
37785
  void Promise.race([j.task(this.gateFor(j.locationId, deadline2, () => expired, clock2)), deadline2]).catch(() => {
37764
37786
  }).finally(() => {
37765
- if (timer) clearTimeout(timer);
37787
+ clock2.stop();
37766
37788
  this.running.delete(j.key);
37767
37789
  const n = (this.busyLocations.get(j.locationId) ?? 1) - 1;
37768
37790
  if (n <= 0) this.busyLocations.delete(j.locationId);
@@ -37872,7 +37894,7 @@ var init_cockpit_cache = __esm({
37872
37894
  const started = this.now();
37873
37895
  let late = false;
37874
37896
  try {
37875
- const data = await Promise.race([opts.read((client) => pacedClient(client, gate2, count2), (fn) => !late && this.cache.commitIfCurrent(ticket, fn)), gate2.deadline ?? NEVER]);
37897
+ const data = await Promise.race([opts.read((client) => pacedClient(client, gate2, count2), (fn) => !late && !gate2.expired?.() && this.cache.commitIfCurrent(ticket, fn)), gate2.deadline ?? NEVER]);
37876
37898
  const kept = this.cache.putFor(ticket, data, { reach: opts.reach, calls: count2.calls, ghlMs: this.now() - started, holdSave: opts.holdSave });
37877
37899
  if (kept === null) {
37878
37900
  resolve13({ ok: false, error: new StaleReadError() });
@@ -37907,7 +37929,7 @@ var init_cockpit_cache = __esm({
37907
37929
  let ok = false;
37908
37930
  let late = false;
37909
37931
  try {
37910
- const data = await Promise.race([opts.read((client) => pacedClient(client, gate2, count2), (fn) => !late && this.cache.commitIfCurrent(ticket, fn)), gate2.deadline ?? NEVER]);
37932
+ const data = await Promise.race([opts.read((client) => pacedClient(client, gate2, count2), (fn) => !late && !gate2.expired?.() && this.cache.commitIfCurrent(ticket, fn)), gate2.deadline ?? NEVER]);
37911
37933
  const kept = this.cache.putFor(ticket, data, { reach: opts.reach, calls: count2.calls, ghlMs: this.now() - started, holdSave: opts.holdSave });
37912
37934
  ok = kept !== null;
37913
37935
  if (kept?.changed) opts.onChange?.(data);
@@ -45412,6 +45434,20 @@ var init_normalizer = __esm({
45412
45434
  }
45413
45435
  });
45414
45436
 
45437
+ // src/login-level.ts
45438
+ function teamLoginLevels(jobs2) {
45439
+ const levels = jobs2.map((j) => j && ADMIN_ROLE_RE.test(j) ? "admin" : "user");
45440
+ if (levels.length && !levels.includes("admin")) levels[0] = "admin";
45441
+ return levels;
45442
+ }
45443
+ var ADMIN_ROLE_RE;
45444
+ var init_login_level = __esm({
45445
+ "src/login-level.ts"() {
45446
+ "use strict";
45447
+ ADMIN_ROLE_RE = /\b(owner|founder|admin|administrator|ceo|principal|director)\b/i;
45448
+ }
45449
+ });
45450
+
45415
45451
  // src/staff-first.ts
45416
45452
  function staffWaitingLine(name) {
45417
45453
  return `${name} is still being set up in GoHighLevel. Add them for me will not add them again for up to 10 minutes.`;
@@ -45433,11 +45469,16 @@ function settleResults(results, check) {
45433
45469
  const onTeam = new Set(check.listed.map((m) => normEmail(m.email)));
45434
45470
  for (const r of results) {
45435
45471
  const e = normEmail(r.email);
45436
- if (!unreadable && !onTeam.has(e)) continue;
45472
+ if (!unreadable && !onTeam.has(e)) {
45473
+ if (!r.ok && !r.refused) r.said = staffWaitingLine(r.name);
45474
+ continue;
45475
+ }
45437
45476
  if (unreadable && r.status === "existing") continue;
45438
45477
  if (!unreadable && listed.has(e)) {
45439
45478
  r.ok = true;
45440
- if (r.status !== "existing") r.status = "created";
45479
+ if (r.refused) r.status = "existing";
45480
+ else if (r.status !== "existing") r.status = "created";
45481
+ delete r.passwordUnconfirmed;
45441
45482
  r.said = r.status === "existing" ? `${r.name} already had a login. Nothing was created.` : `${r.name} was added. GoHighLevel should email them an invitation.`;
45442
45483
  } else if (r.refused) {
45443
45484
  r.ok = false;
@@ -45446,6 +45487,7 @@ function settleResults(results, check) {
45446
45487
  } else {
45447
45488
  r.ok = false;
45448
45489
  r.said = staffWaitingLine(r.name);
45490
+ if (r.generatedPassword) r.passwordUnconfirmed = true;
45449
45491
  }
45450
45492
  }
45451
45493
  return results;
@@ -45499,9 +45541,13 @@ function listedStaff(roster, ...intakeStaffTexts) {
45499
45541
  return { people, skipped };
45500
45542
  }
45501
45543
  function staffCreateOffered(opts) {
45502
- if (!opts.hasAgencyKey || opts.profile.canCreateSubAccounts === false) return false;
45503
- if (!opts.refusedAt) return true;
45504
- return opts.profile.canCreateSubAccounts === true && !!opts.profile.updated_at && opts.profile.updated_at > opts.refusedAt;
45544
+ return staffAddBlocker(opts) === void 0;
45545
+ }
45546
+ function staffAddBlocker(opts) {
45547
+ if (!opts.hasAgencyKey) return "no_key";
45548
+ if (opts.profile.canCreateSubAccounts === false) return "plan";
45549
+ if (!opts.refusedAt) return void 0;
45550
+ return opts.profile.canCreateSubAccounts === true && !!opts.profile.updated_at && opts.profile.updated_at > opts.refusedAt ? void 0 : "refused";
45505
45551
  }
45506
45552
  function intakeAnswer(answers, key2, label2) {
45507
45553
  if (!answers) return void 0;
@@ -45526,7 +45572,7 @@ function teamAnswersFromRoster(answers, roster, questions) {
45526
45572
  }
45527
45573
  function checkStaff(listed, users, opts = { canAddForYou: false }) {
45528
45574
  const checkedAt = (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
45529
- const base = { listed: listed.people, skipped: listed.skipped, canAddForYou: opts.canAddForYou, checkedAt };
45575
+ const base = { listed: listed.people, skipped: listed.skipped, canAddForYou: opts.canAddForYou, checkedAt, ...!opts.canAddForYou && opts.addBlocked ? { addBlocked: opts.addBlocked } : {} };
45530
45576
  if (!Array.isArray(users)) {
45531
45577
  return {
45532
45578
  ...base,
@@ -45557,7 +45603,7 @@ function checkStaff(listed, users, opts = { canAddForYou: false }) {
45557
45603
  const u = findUserByEmail(users, p.email);
45558
45604
  if (u) {
45559
45605
  matched.push({ ...p, userId: u.id });
45560
- const listedRole = personToCreateInput(p).role;
45606
+ const listedRole = personToCreateInput(p, listed.people).role;
45561
45607
  if (u.role && u.role !== listedRole) {
45562
45608
  const d = { name: p.name, email: p.email, ...p.role ? { job: p.role } : {}, listed: listedRole, account: u.role };
45563
45609
  levelDiffs.push({ ...d, line: staffLevelDiffLine(d) });
@@ -45565,7 +45611,7 @@ function checkStaff(listed, users, opts = { canAddForYou: false }) {
45565
45611
  } else missing.push(p);
45566
45612
  }
45567
45613
  if (levelDiffs.length) Object.assign(base, { levelDiffs });
45568
- Object.assign(base, { creations: creationsFor(missing) });
45614
+ Object.assign(base, { creations: creationsFor(missing, listed.people) });
45569
45615
  if (missing.length === 0) {
45570
45616
  return {
45571
45617
  ...base,
@@ -45574,7 +45620,7 @@ function checkStaff(listed, users, opts = { canAddForYou: false }) {
45574
45620
  missing,
45575
45621
  accountUserCount: users.length,
45576
45622
  headline: "Your staff are in place",
45577
- body: `We found ${matched.length === 1 ? "the 1 person" : `all ${matched.length} people`} you listed in this client's account: ${listSentence(matched.map((m) => m.name))}. Calendars will be given to them and lead alerts will go to them.`,
45623
+ body: `We found ${matched.length === 1 ? "the 1 person" : `all ${matched.length} people`} you listed in this client's account: ${listSentence(matched.map((m) => m.name))}.`,
45578
45624
  steps: []
45579
45625
  };
45580
45626
  }
@@ -45588,7 +45634,7 @@ function checkStaff(listed, users, opts = { canAddForYou: false }) {
45588
45634
  missing,
45589
45635
  accountUserCount: users.length,
45590
45636
  headline: missing.length === 1 ? "1 person is not in this account yet" : `${missing.length} people are not in this account yet`,
45591
- body: `${matched.length ? `We found ${listSentence(matched.map((m) => m.name))}. ` : ""}Intake and Build wait until everyone you listed is in the account, so no calendar or alert is built with a blank name.`,
45637
+ body: `${matched.length ? `We found ${listSentence(matched.map((m) => m.name))}. ` : ""}Build waits until everyone you listed is in the account.`,
45592
45638
  steps,
45593
45639
  ...opts.canAddForYou ? {} : { howToAdd: STAFF_HOW_TO_ADD }
45594
45640
  };
@@ -45603,10 +45649,10 @@ function staffGateReason(check, stageName) {
45603
45649
  }
45604
45650
  return `${stageName} is waiting: ${listSentence(check.missing.map(personLabel))} ${check.missing.length === 1 ? "is" : "are"} not in this client's GoHighLevel account yet. Add ${check.missing.length === 1 ? "them" : "everyone"} in step 7, Staff. Nothing is built with a blank name.`;
45605
45651
  }
45606
- function staffPreflightLine(check, waiting = "Intake") {
45652
+ function staffPreflightLine(check) {
45607
45653
  if (!check) return { key: "staff", ok: false, text: "We could not check this client's staff. Use Check again in step 7, Staff." };
45608
45654
  if (check.status === "ready") return { key: "staff", ok: true, text: `Staff in this account: ${listSentence(check.matched.map((m) => m.name))}.` };
45609
- return { key: "staff", ok: false, text: `${check.headline}. Finish step 7, Staff, before ${waiting}.` };
45655
+ return { key: "staff", ok: false, text: `${check.headline}. Finish step 7, Staff, before Build.` };
45610
45656
  }
45611
45657
  async function readStaffCheck(deps, listed, now) {
45612
45658
  let users;
@@ -45615,15 +45661,21 @@ async function readStaffCheck(deps, listed, now) {
45615
45661
  } catch (e) {
45616
45662
  users = { error: e instanceof Error ? e.message : String(e) };
45617
45663
  }
45618
- return checkStaff(listed, users, { canAddForYou: deps.canCreateUsers(), now });
45664
+ return checkStaff(listed, users, { canAddForYou: deps.canCreateUsers(), addBlocked: deps.addBlocked?.(), now });
45665
+ }
45666
+ function teamLevels(people) {
45667
+ const levels = teamLoginLevels(people.map((p) => p.role));
45668
+ return new Map(people.map((p, i) => [normEmail(p.email), levels[i]]));
45619
45669
  }
45620
- function personToCreateInput(p) {
45670
+ function personToCreateInput(p, team) {
45621
45671
  const words2 = p.name.trim().split(/\s+/);
45672
+ const role = teamLevels(team).get(normEmail(p.email));
45673
+ if (!role) throw new Error(`${p.name} is not on the team this login was worked out from.`);
45622
45674
  return {
45623
45675
  firstName: words2[0],
45624
45676
  lastName: words2.length > 1 ? words2.slice(1).join(" ") : "-",
45625
45677
  email: p.email.trim(),
45626
- role: p.role && ADMIN_ROLE_RE.test(p.role) ? "admin" : "user",
45678
+ role,
45627
45679
  ...p.mobile ? { phone: p.mobile } : {}
45628
45680
  };
45629
45681
  }
@@ -45648,7 +45700,7 @@ async function addMissingStaff(deps, listed, now, compared) {
45648
45700
  }
45649
45701
  let outcome;
45650
45702
  try {
45651
- outcome = await deps.createUser(personToCreateInput(p));
45703
+ outcome = await deps.createUser(personToCreateInput(p, listed.people));
45652
45704
  } catch (e) {
45653
45705
  outcome = { ok: false, stage: "create", error: e instanceof Error ? e.message : String(e) };
45654
45706
  }
@@ -45669,11 +45721,17 @@ async function addMissingStaff(deps, listed, now, compared) {
45669
45721
  email: p.email,
45670
45722
  ok: false,
45671
45723
  ...createMadeNothing(outcome) ? { refused: true, refusedSaid: said } : {},
45724
+ ...outcome.generatedPassword ? { generatedPassword: outcome.generatedPassword, passwordUnconfirmed: true } : {},
45672
45725
  said
45673
45726
  });
45674
45727
  }
45675
45728
  }
45676
- const after = await readStaffCheck(refused ? { listUsers: () => deps.listUsers(), canCreateUsers: () => false } : deps, listed, now);
45729
+ let after;
45730
+ try {
45731
+ after = await readStaffCheck(refused ? { listUsers: () => deps.listUsers(), canCreateUsers: () => false, addBlocked: () => "refused" } : deps, listed, now);
45732
+ } catch (e) {
45733
+ after = checkStaff(listed, { error: e instanceof Error ? e.message : String(e) }, { canAddForYou: false, now });
45734
+ }
45677
45735
  if (refused) after.createRefused = true;
45678
45736
  return { results: settleResults(results, after), check: after };
45679
45737
  }
@@ -45683,9 +45741,9 @@ function effectiveStaffList(roster, intake, draftText) {
45683
45741
  if (draftText && draftText.trim()) return { ...listedStaff(void 0, draftText), source: "draft" };
45684
45742
  return { people: [], skipped: [] };
45685
45743
  }
45686
- function creationsFor(people) {
45744
+ function creationsFor(people, team) {
45687
45745
  return people.map((p) => {
45688
- const c = personToCreateInput(p);
45746
+ const c = personToCreateInput(p, team);
45689
45747
  return { email: normEmail(c.email), firstName: c.firstName, lastName: c.lastName, role: c.role, ...p.role ? { job: p.role } : {} };
45690
45748
  });
45691
45749
  }
@@ -45729,12 +45787,13 @@ function planTeamMatches(planUsers2, people) {
45729
45787
  const b = [...new Set(people.map((p) => normEmail(p.email)))].sort();
45730
45788
  return a.length === b.length && a.every((x, i) => x === b[i]);
45731
45789
  }
45732
- var STAFF_PENDING_MS, STAFF_INTAKE_KEYS, STAFF_STEP_ABOUT, STAFF_LIST_HELP, STAFF_HOW_TO_ADD, STAFF_REVIEW_WARNING, STAFF_REVIEW_UNCHECKED, STAFF_GATED_STAGES, ADMIN_ROLE_RE, fullName, STAFF_ADMIN_MARK, STAFF_SOURCE_WORDS;
45790
+ var STAFF_PENDING_MS, STAFF_INTAKE_KEYS, STAFF_STEP_ABOUT, STAFF_LIST_HELP, STAFF_HOW_TO_ADD, STAFF_REVIEW_WARNING, STAFF_REVIEW_UNCHECKED, STAFF_GATED_STAGES, fullName, STAFF_ADMIN_MARK, STAFF_SOURCE_WORDS;
45733
45791
  var init_staff_first = __esm({
45734
45792
  "src/staff-first.ts"() {
45735
45793
  "use strict";
45736
45794
  init_normalizer();
45737
45795
  init_user_provisioning();
45796
+ init_login_level();
45738
45797
  init_ghl_click_paths();
45739
45798
  STAFF_PENDING_MS = 10 * 60 * 1e3;
45740
45799
  STAFF_INTAKE_KEYS = { staff: "staff_members", notify: "notify_name", calls: "calls_name" };
@@ -45744,7 +45803,6 @@ var init_staff_first = __esm({
45744
45803
  STAFF_REVIEW_WARNING = "Your staff are not all in this client's account yet. Build will not start until they are: without them the calendars cannot be built, and the workflows that start from a booking would be left as drafts with no calendar chosen. Finish step 7, Staff, first.";
45745
45804
  STAFF_REVIEW_UNCHECKED = "We have not checked this client's staff yet. Build will not start until everyone who takes appointments is in the account. Step 7, Staff, checks them.";
45746
45805
  STAFF_GATED_STAGES = /* @__PURE__ */ new Set([3]);
45747
- ADMIN_ROLE_RE = /\b(owner|founder|admin|administrator|ceo|principal|director)\b/i;
45748
45806
  fullName = (c) => [c.firstName, c.lastName === "-" ? "" : c.lastName].filter(Boolean).join(" ");
45749
45807
  STAFF_ADMIN_MARK = "Admin login";
45750
45808
  STAFF_SOURCE_WORDS = {
@@ -47147,6 +47205,8 @@ var init_cockpit_routes = __esm({
47147
47205
  get("/api/intake-documents", "client", BOTH),
47148
47206
  get("/api/review", "client", BOTH),
47149
47207
  via(get("/api/run/status", "client", BOTH), { optional: true }),
47208
+ via(get("/api/run/secrets", "client", BOTH), { optional: true }),
47209
+ // seat-guarded: a Build's one-time passwords (S2)
47150
47210
  get("/api/staff", "client", BOTH),
47151
47211
  post("/api/staff/check", "client", BOTH),
47152
47212
  // the same check as GET /api/staff, and keeps it (safety R7)
@@ -48558,6 +48618,43 @@ var init_jobs = __esm({
48558
48618
  }
48559
48619
  });
48560
48620
 
48621
+ // src/build-secrets.ts
48622
+ function cockpitBuildMarker() {
48623
+ return { [COCKPIT_BUILD_ENV]: "1" };
48624
+ }
48625
+ function closeAllSecretChannels() {
48626
+ for (const c of [...openChannels]) c();
48627
+ }
48628
+ function withheldNote(name) {
48629
+ return `GoHighLevel set a password for ${name}. It is not shown anywhere. Reset their password in GoHighLevel.`;
48630
+ }
48631
+ function withholdOneTimePasswords(staff) {
48632
+ for (const s of staff) {
48633
+ if (!s.generatedPassword) continue;
48634
+ delete s.generatedPassword;
48635
+ delete s.passwordLine;
48636
+ if (s.warnings) {
48637
+ s.warnings = s.warnings.filter((w) => w !== PASSWORD_HANDOVER_WARNING);
48638
+ if (!s.warnings.length) delete s.warnings;
48639
+ }
48640
+ s.passwordNote = withheldNote(s.name);
48641
+ }
48642
+ }
48643
+ function inCockpitBuild(env2 = process.env) {
48644
+ return env2[COCKPIT_BUILD_ENV] === "1" || Boolean(env2[SECRET_SOCKET_ENV] && env2[SECRET_TOKEN_ENV]);
48645
+ }
48646
+ var SECRET_SOCKET_ENV, SECRET_TOKEN_ENV, COCKPIT_BUILD_ENV, openChannels;
48647
+ var init_build_secrets = __esm({
48648
+ "src/build-secrets.ts"() {
48649
+ "use strict";
48650
+ init_user_provisioning();
48651
+ SECRET_SOCKET_ENV = "GHL_COMMAND_SECRET_SOCKET";
48652
+ SECRET_TOKEN_ENV = "GHL_COMMAND_SECRET_TOKEN";
48653
+ COCKPIT_BUILD_ENV = "GHL_COMMAND_COCKPIT_BUILD";
48654
+ openChannels = /* @__PURE__ */ new Set();
48655
+ }
48656
+ });
48657
+
48561
48658
  // src/intake-to-build/customization.ts
48562
48659
  var customization_exports = {};
48563
48660
  __export(customization_exports, {
@@ -49285,6 +49382,7 @@ function asText(v) {
49285
49382
  }
49286
49383
  function usersFromStaff(staff, warnings) {
49287
49384
  const users = [];
49385
+ const jobs2 = [];
49288
49386
  const userRefByName = /* @__PURE__ */ new Map();
49289
49387
  const seenSlugs = /* @__PURE__ */ new Set();
49290
49388
  const EMAIL_RE3 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -49306,16 +49404,19 @@ function usersFromStaff(staff, warnings) {
49306
49404
  if (words2.length === 1) {
49307
49405
  warnings.push(`Staff member "${s.name}" gave only one name \u2014 the account login shows "-" as the last name until it is edited.`);
49308
49406
  }
49309
- const role = s.role && ADMIN_ROLE_RE2.test(s.role) ? "admin" : "user";
49310
- const user = { ref, firstName, lastName, email, role };
49407
+ const user = { ref, firstName, lastName, email, role: "user" };
49408
+ jobs2.push(s.role);
49311
49409
  if (s.mobile && s.mobile.trim()) user.phone = s.mobile.trim();
49312
49410
  users.push(user);
49313
49411
  userRefByName.set(normName(s.name), ref);
49314
49412
  }
49315
- let firstAdminRef = users.find((u) => u.role === "admin")?.ref;
49316
- if (!firstAdminRef && users.length > 0) {
49317
- users[0].role = "admin";
49318
- firstAdminRef = users[0].ref;
49413
+ const levels = teamLoginLevels(jobs2);
49414
+ const byJob = jobs2.some((j) => Boolean(j && ADMIN_ROLE_RE.test(j)));
49415
+ users.forEach((u, i) => {
49416
+ u.role = levels[i];
49417
+ });
49418
+ const firstAdminRef = users.find((u) => u.role === "admin")?.ref;
49419
+ if (!byJob && users.length > 0) {
49319
49420
  warnings.push(
49320
49421
  `No staff member was described as the owner or admin, so ${users[0].firstName} (listed first) gets the admin login. Change it on the review screen if that is wrong.`
49321
49422
  );
@@ -50608,13 +50709,14 @@ function loadPresetChoice(requested, briefPreset) {
50608
50709
  if (generic) return { ok: true, preset: generic, file: "generic-client.preset.json", note: "Using the generic preset." };
50609
50710
  return { ok: false, error: "No usable preset file was found in the library." };
50610
50711
  }
50611
- var fs38, path36, PRESET_TOKEN_ROOTS, GSM7_REPLACEMENTS, FLAG_ALIASES, INLINE_TOKEN_RE, ADMIN_ROLE_RE2, TERMINAL_STAGE_WORDS, BRIEF_CALENDAR_TYPE_MAP, CALENDAR_NUMBER_FIELDS, CUSTOM_VALUE_TOKEN_RE;
50712
+ var fs38, path36, PRESET_TOKEN_ROOTS, GSM7_REPLACEMENTS, FLAG_ALIASES, INLINE_TOKEN_RE, TERMINAL_STAGE_WORDS, BRIEF_CALENDAR_TYPE_MAP, CALENDAR_NUMBER_FIELDS, CUSTOM_VALUE_TOKEN_RE;
50612
50713
  var init_plan_from_preset = __esm({
50613
50714
  "src/intake-to-build/plan-from-preset.ts"() {
50614
50715
  "use strict";
50615
50716
  fs38 = __toESM(require("fs"));
50616
50717
  path36 = __toESM(require("path"));
50617
50718
  init_plan();
50719
+ init_login_level();
50618
50720
  init_brief2();
50619
50721
  init_normalizer();
50620
50722
  init_features();
@@ -50636,7 +50738,6 @@ var init_plan_from_preset = __esm({
50636
50738
  email_domain_needed: ["email_not_ready"]
50637
50739
  };
50638
50740
  INLINE_TOKEN_RE = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z0-9_]+|\[\d+\])*)\s*(?:\|\|([^}]*))?\}\}/g;
50639
- ADMIN_ROLE_RE2 = /\b(owner|founder|admin|administrator|ceo|principal|director)\b/i;
50640
50741
  TERMINAL_STAGE_WORDS = [
50641
50742
  "lost",
50642
50743
  "closed",
@@ -51843,7 +51944,28 @@ var init_build_steps = __esm({
51843
51944
  "Command OS has built and checked the account. You sign off here. The list below says what you can switch on now and what waits until its approval comes through."
51844
51945
  ];
51845
51946
  BUILD_STEPS_SCRIPT = String.raw`
51846
- let bsStaffSaid={},bsChange={},bsErr={},bsBusy={},bsStaff={},bsSeen={},bsDraft={},bsNote={},bsInSteps=false,bsMoreOpen={},bsInfo={},bsAgeAsked={};
51947
+ let bsStaffSaid={},bsChange={},bsErr={},bsBusy={},bsStaff={},bsSeen={},bsDraft={},bsNote={},bsInSteps=false,bsMoreOpen={},bsInfo={},bsAgeAsked={},bsPwHeld={};
51948
+ /* Check again keeps the one-time password lines (they are shown once) and drops the rest (3.90.2). */
51949
+ function bsKeepPasswords(id){const keep=(bsStaffSaid[id]||[]).filter(l=>l.includes(" Their one-time password is "));if(keep.length)bsStaffSaid[id]=keep;else delete bsStaffSaid[id]}
51950
+ /* This account's one-time password lines from its last Builds (S1). A second client in the same sub-account (the same
51951
+ member's) has no card of its own, so its lines show here too, each naming that client (Codex on 50acb7eb). */
51952
+ function bsOneTime(run,c){return ((run&&run.oneTime)||[]).filter(x=>x.locationId===c.locationId&&(!c.clientKey||x.clientKey===c.clientKey)).flatMap(x=>x.clientKey&&!c.clientKey?(x.lines||[]).map(l=>"For "+(x.clientName||x.clientKey)+": "+l):(x.lines||[]))}
51953
+ /* Why step 7 cannot add them, above the how-to list (3.91.1, Jerry's G words): no agency key wins over the plan answer. */
51954
+ function bsHowToAddLead(sc){const b=sc.addBlocked;
51955
+ if(b==="no_key")return "To add people from here, the agency key must be connected first. Until then, do this once for each person marked Not added yet:";
51956
+ if(b==="plan")return "GoHighLevel only lets you add people in its own screens on your plan. Do this once for each person marked Not added yet:";
51957
+ if(b==="refused"||sc.createRefused)return "GoHighLevel does not let this agency add people from outside GoHighLevel. Do this once for each person marked Not added yet:";
51958
+ return "GoHighLevel only lets you add people in its own screens on your plan. Do this once for each person marked Not added yet:"}
51959
+ /* A one-time password whose create never showed up (passwordUnconfirmed) is held in this page only, never sent or
51960
+ saved, and shown the first time a check lists that person, in place of their waiting line (3.91.1). */
51961
+ /* A newer password for someone replaces any older one, held or shown: after a retry only the latest is a credential
51962
+ (3.91.1, Codex on 6560441d). A shown password line starts "<name> was added. ", so "John Smith" never matches
51963
+ "John Smith Jr." (Codex on 610e9378). */
51964
+ function bsSupersede(id,results){for(const x of results||[])if(x.generatedPassword){const e=String(x.email).trim().toLowerCase();if(bsPwHeld[id])bsPwHeld[id]=bsPwHeld[id].filter(h=>h.email!==e);if(bsStaffSaid[id])bsStaffSaid[id]=bsStaffSaid[id].filter(l=>!(l.includes(" Their one-time password is ")&&l.startsWith(x.name+" was added. ")))}}
51965
+ function bsHoldPasswords(id,results){for(const x of results||[])if(x.generatedPassword&&x.passwordUnconfirmed){const e=String(x.email).trim().toLowerCase();bsPwHeld[id]=(bsPwHeld[id]||[]).filter(h=>h.email!==e);bsPwHeld[id].push({email:e,name:x.name,said:x.said,pw:x.generatedPassword})}}
51966
+ function bsRevealPasswords(id,sc){const held=bsPwHeld[id];if(!held||!sc||!sc.matched)return;const inAcct=new Set(sc.matched.map(m=>String(m.email).trim().toLowerCase()));const left=[];
51967
+ for(const h of held){if(!inAcct.has(h.email)){left.push(h);continue}const lines=(bsStaffSaid[id]||[]).filter(l=>l!==h.said);lines.push(h.name+" was added. GoHighLevel should email them an invitation. Their one-time password is "+h.pw+". Give it to them privately.");bsStaffSaid[id]=lines}
51968
+ if(left.length)bsPwHeld[id]=left;else delete bsPwHeld[id]}
51847
51969
  /* A link with ?client=<id>&reconnect=1 (or the older #client=<id>&reconnect=1) opens that client's reconnect box (round-6 review). */
51848
51970
  try{for(const hp of [new URLSearchParams(location.search),new URLSearchParams(location.hash.slice(1))])if(hp.get("reconnect")==="1"&&hp.get("client"))bsMoreOpen[hp.get("client")]=true}catch(e){}
51849
51971
  async function bsPost(url,body){let r,j=null;try{r=await fetch(url,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body)})}catch(e){return {ok:false,error:"Command OS did not answer. Check it is still running, then try again."}}
@@ -51887,6 +52009,7 @@ function bsRadios(name,items,cur,onpick){const g=mk("div","bs-choices");g.setAtt
51887
52009
  return g}
51888
52010
  /* A step run (Brief, Build, Verify): one button, the live progress while it runs, the reason under it when refused. */
51889
52011
  function bsRun(c,state,run,stage,label,box,ghost){const id=c.locationId;const key="run:"+id+":"+stage;
52012
+ /* A password GoHighLevel set during this Build: from the run's own channel, in memory only, never in the report (S1). */
51890
52013
  const live=run&&run.running&&run.locationId===id&&run.stage===stage;const busyElse=run&&run.running&&!live;
51891
52014
  if(live){box.appendChild(bsLive(run,stage));return}
51892
52015
  const b=bsBtn(bsBusy[key]?"Starting…":label,async()=>{bsBusy[key]=true;delete bsErr[key];render(lastState,lastRun);
@@ -52129,7 +52252,7 @@ function bsReconnect(c,pr){const id=c.locationId;const key="reconnect:"+id;const
52129
52252
  bsErrLine(key,box);return box}
52130
52253
  function bsOpenReconnect(id){bsMoreOpen[id]=true;render(lastState,lastRun);const el=document.getElementById("bs-more-"+id);if(el)try{el.scrollIntoView({block:"start"})}catch(e){}}
52131
52254
  /* Step 7: everyone from step 5 must be a user in the client's GoHighLevel account. */
52132
- function bsStaffStep(c,state,box){const id=c.locationId;const sc=(state.staffChecks||{})[id]||bsStaff[id];const key="staff:"+id;
52255
+ function bsStaffStep(c,state,box){const id=c.locationId;const sc=(state.staffChecks||{})[id]||bsStaff[id];const key="staff:"+id;bsRevealPasswords(id,sc);
52133
52256
  /* What the last Add them for me did (3.90.1): who was added, who already had a login, any login made for someone
52134
52257
  no longer on the team. Shown in every staff state (Codex on 516081d8); the list below is the team as it is now. */
52135
52258
  if((bsStaffSaid[id]||[]).length){const said=mk("div","cds-callout info cds-stack bs-staffsaid");said.setAttribute("role","status");for(const line of bsStaffSaid[id])said.appendChild(mk("p","",line));box.appendChild(said)}
@@ -52149,9 +52272,9 @@ function bsStaffStep(c,state,box){const id=c.locationId;const sc=(state.staffChe
52149
52272
 
52150
52273
  if((sc.missing||[]).length){
52151
52274
  if(sc.canAddForYou&&!sc.createRefused){if((sc.creations||[]).length){box.appendChild(mk("p","","We can add them to this client's GoHighLevel account for you. GoHighLevel should email each new person an invitation. Let your client know, and check with them that it arrived."));
52152
- box.appendChild(bsBtn(bsBusy[key+":add"]?"Adding…":"Add them for me",async()=>{bsBusy[key+":add"]=true;delete bsErr[key];render(lastState,lastRun);const j=await bsPost("/api/staff/add",{locationId:id,shown:sc.creations||[]});bsBusy[key+":add"]=false;bsStaffSaid[id]=[...(j.ok?(j.said||[]):[]),...(j.results||[]).filter(x=>x.generatedPassword).map(x=>x.said+" Their one-time password is "+x.generatedPassword+". Give it to them privately; it is not saved anywhere.")];if(!j.ok){bsErr[key]=j.error;if(j.check)bsStaff[id]=j.check}else bsStaff[id]=j.check;load(true)}))}}
52153
- else{const how=mk("ol","bs-how");for(const t of BS_HOW_TO_ADD)how.appendChild(mk("li","",t));box.appendChild(mk("p","","GoHighLevel only lets you add people in its own screens on your plan. Do this once for each person marked Not added yet:"));box.appendChild(how)}}
52154
- const again=bsBtn(bsBusy[key]?"Checking…":"Check again",()=>{bsBusy[key]=true;delete bsStaffSaid[id];bsCheckStaff(id);render(lastState,lastRun)},sc.status==="ready");box.appendChild(again);
52275
+ box.appendChild(bsBtn(bsBusy[key+":add"]?"Adding…":"Add them for me",async()=>{bsBusy[key+":add"]=true;delete bsErr[key];render(lastState,lastRun);const j=await bsPost("/api/staff/add",{locationId:id,shown:sc.creations||[]});bsBusy[key+":add"]=false;bsSupersede(id,j.results);bsStaffSaid[id]=[...(bsStaffSaid[id]||[]).filter(l=>l.includes(" Their one-time password is ")),...(j.ok?(j.said||[]):[]),...(j.results||[]).filter(x=>x.generatedPassword&&!x.passwordUnconfirmed).map(x=>x.said+" Their one-time password is "+x.generatedPassword+". Give it to them privately.")];bsHoldPasswords(id,j.results);if(!j.ok){bsErr[key]=j.error;if(j.check)bsStaff[id]=j.check}else bsStaff[id]=j.check;load(true)}))}}
52276
+ else{const how=mk("ol","bs-how");for(const t of BS_HOW_TO_ADD)how.appendChild(mk("li","",t));box.appendChild(mk("p","",bsHowToAddLead(sc)));box.appendChild(how)}}
52277
+ const again=bsBtn(bsBusy[key]?"Checking…":"Check again",()=>{bsBusy[key]=true;bsKeepPasswords(id);bsCheckStaff(id);render(lastState,lastRun)},sc.status==="ready");box.appendChild(again);
52155
52278
  bsErrLine(key,box)}
52156
52279
  async function bsCheckStaff(id){const key="staff:"+id;let j=null;try{j=await(await fetch("/api/staff/check",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({locationId:id})})).json()}catch(e){}
52157
52280
  bsBusy[key]=false;if(j&&j.ok){bsStaff[id]=j.check;delete bsErr[key]}else bsErr[key]=(j&&j.error)||"We could not check this client's staff. Try Check again.";load(true)}
@@ -52249,11 +52372,16 @@ function stepsPage(c,state,run){const id=c.locationId;const inp=bsInputs(c,state
52249
52372
  {const forced=Boolean(bsMoreOpen[id]);const more=mk("details","cds-details bs-more");more.id="bs-more-"+id;more.setAttribute("data-key","more:"+id+(forced?":open":""));if(forced)more.open=true;
52250
52373
  more.appendChild(mk("summary","","Reconnect this client, or check this computer is ready"));
52251
52374
  more.appendChild(bsReconnect(c,pr));more.appendChild(unnest(preflightCard(c)));page.appendChild(more)}
52375
+ /* A password GoHighLevel set during this client's last Build: from the run's own channel, in memory only, never in the
52376
+ report (S1). At the top of the page, outside every step, so Verify running or a locked or parked account never hides
52377
+ it (Codex sweep M3). */
52378
+ {const otl=bsOneTime(run,c);if(otl.length){const ot=mk("div","cds-callout warn cds-stack bs-onetime");ot.setAttribute("role","status");for(const l of otl)ot.appendChild(mk("p","",l));page.appendChild(ot)}}
52252
52379
  const list=mk("ol","bs-steps");
52253
52380
  for(let i=0;i<n;i++){const ck=id+":"+i;
52254
52381
  /* A Change closes itself once that step is done with a different answer (review of 64da59d, LOW 15). */
52255
52382
  if(bsChange[ck]&&pr.done[i]&&typeof bsChange[ck]==="string"&&bsSummary(i,c,state)!==bsChange[ck])delete bsChange[ck];
52256
52383
  /* Step 7 stays open while what the last Add them for me said, or its refusal, is there to read (3.90.1). */
52384
+ if(i===6)bsRevealPasswords(id,(state.staffChecks||{})[id]||bsStaff[id]);
52257
52385
  const staffNews=i===6&&(((bsStaffSaid[id]||[]).length>0)||Boolean(bsErr["staff:"+id]));
52258
52386
  const li=mk("li","bs-step");li.id="step-"+(i+1);const open=(i===pr.current)||Boolean(bsChange[ck])||staffNews;
52259
52387
  const num=mk("span","bs-num",String(i+1));num.setAttribute("aria-hidden","true");li.appendChild(num);const main=mk("div","bs-main");li.appendChild(main);
@@ -58264,6 +58392,7 @@ __export(dashboard_exports, {
58264
58392
  stateSaveFs: () => stateSaveFs,
58265
58393
  statusFromThisRun: () => statusFromThisRun,
58266
58394
  syncVerifyVerdict: () => syncVerifyVerdict,
58395
+ takeOneTimeLine: () => takeOneTimeLine,
58267
58396
  teamAnswersFromRoster: () => teamAnswersFromRoster,
58268
58397
  uniqueDocName: () => uniqueDocName,
58269
58398
  unknownCapabilityHtml: () => unknownCapabilityHtml,
@@ -58663,6 +58792,13 @@ function assertCockpitLicenseHolds(now = Date.now()) {
58663
58792
  const refusal2 = licenseSessionRefusal(now, "cockpit");
58664
58793
  if (refusal2) throw new Error(refusal2);
58665
58794
  }
58795
+ function takeOneTimeLine(entry, runLocationId, l) {
58796
+ if (l.locationId !== runLocationId) return false;
58797
+ const k = l.email.trim().toLowerCase();
58798
+ delete entry.byEmail[k];
58799
+ entry.byEmail[k] = l.line;
58800
+ return true;
58801
+ }
58666
58802
  function unverifiedStaffNames(check) {
58667
58803
  return (check?.missing ?? []).map((m) => String(m.name ?? "").trim()).filter(Boolean);
58668
58804
  }
@@ -58736,12 +58872,15 @@ function startRun(locationId2, stage, onDone, meta = {}) {
58736
58872
  context2.readback = plannedReadbacks(verificationPlan).map(({ ref, type, name }) => ({ ref, type, name, status: "unreadable", detail: e instanceof Error ? e.message : String(e) }));
58737
58873
  }
58738
58874
  }
58875
+ const channel = stage === 3 && !meta.spec ? { env: cockpitBuildMarker(), close: () => {
58876
+ } } : void 0;
58877
+ if (channel) buildOneTime.delete(rk);
58739
58878
  return runStage(locationId2, stage, (e) => {
58740
58879
  run.lines.push(e.line);
58741
58880
  if (run.lines.length > 200) run.lines.shift();
58742
58881
  }, { context: context2, name: meta.name, approvedAt: meta.approvedAt, onProgress: (p) => {
58743
58882
  run.progress = p;
58744
- }, spec: meta.spec, ...secondClient ? { clientKey: meta.clientKey } : {} }).catch((error) => {
58883
+ }, spec: meta.spec, ...channel ? { secretEnv: channel.env } : {}, ...secondClient ? { clientKey: meta.clientKey } : {} }).finally(() => channel?.close()).catch((error) => {
58745
58884
  if (stage !== 4) throw error;
58746
58885
  return { ok: false, error: error instanceof Error ? error.message : String(error) };
58747
58886
  });
@@ -59820,6 +59959,8 @@ async function load(quiet,freshQueue){const my=++loadSeq,q=clientQ(),tok=clientT
59820
59959
  on the scan of every client: the GET answers from the saved board and queues the stale reads behind it. */
59821
59960
  const paintFirst=quiet!==true;
59822
59961
  await loadQueue(quiet===true?(freshQueue===true?"pending":"get"):paintFirst?"get":undefined);const get=async u=>quiet===true?(await bgJSON(u)).j:await(await fetch(u)).json();const s=await get("/api/state"+q);const r=await get("/api/run/status"+q);
59962
+ /* A reload keeps a Build's one-time lines on screen: they come from the seat-guarded read (S2; Codex on a375520b). */
59963
+ try{const s2=await get("/api/run/secrets"+q);if(r&&s2&&Array.isArray(s2.oneTime)&&s2.oneTime.length)r.oneTime=s2.oneTime}catch(e){}
59823
59964
  /* One client for the whole load (Codex, 2026-10-20): both reads asked for q, and nothing is drawn once another client is open. */
59824
59965
  if(my<loadDrawn||!stillFor(tok))return;loadDrawn=my;lastStatusOkAt=Date.now();if(SCREEN==="new"&&!snapshots){try{const sr=await(await fetch("/api/snapshots")).json();snapshots=sr.ok?sr.snapshots:[];if(sr.ok&&sr.cache){ageViews.snapshots=sr.cache;forClient(tok,()=>setTimeout(()=>askStale("snapshots"),0))}snapshotError=sr.ok?"":clientPageError(sr.error,"Check the agency key in GoHighLevel, then refresh the snapshot list.")}catch(e){snapshots=[];snapshotError="Could not load snapshots; check the connection and refresh this page."}}/* A background refresh (quiet) draws only when the data changed (2026-09-29: an unchanged redraw closed what Jerry had open). */
59825
59966
  /* Checked again after the snapshot read: no draw, no finish-note change and no poller for a client no longer open. */
@@ -59873,7 +60014,10 @@ function buildClientOpen(){return Boolean(newClientId&&ROSTER.some(r=>r.location
59873
60014
  function keyTyped(){try{return [...document.querySelectorAll('input[type="password"]')].some(i=>i.value&&!i.closest("details:not([open])"))}catch(e){return false}}
59874
60015
  function userIsBusy(){if(SCREEN==="new"&&!buildClientOpen())return true;return inputFocused()||intakeWorkBusy()||icBusy()||keyTyped()}
59875
60016
  function intakeIsOpen(){try{return Object.values(iOpen||{}).some(Boolean)}catch(e){return false}}
59876
- async function fetchRunStatus(q,tok){const asked=q===undefined?clientQ():q,t0=tok===undefined?clientToken():tok;const rr=await(await fetch("/api/run/status"+asked)).json();if(stillFor(t0))lastStatusOkAt=Date.now();return rr}
60017
+ async function fetchRunStatus(q,tok){const asked=q===undefined?clientQ():q,t0=tok===undefined?clientToken():tok;const rr=await(await fetch("/api/run/status"+asked)).json();if(stillFor(t0))lastStatusOkAt=Date.now();
60018
+ /* A Build's one-time password lines come from the seat-guarded read, never the run status (S2). */
60019
+ try{const s=await(await fetch("/api/run/secrets"+asked)).json();if(s&&Array.isArray(s.oneTime)&&s.oneTime.length)rr.oneTime=s.oneTime}catch(e){}
60020
+ return rr}
59877
60021
  function statusIsStale(){return !lastStatusOkAt||Date.now()-lastStatusOkAt>15000}
59878
60022
  function runKey(r){return r?[r.running?1:0,r.locationId||"",r.stage==null?"":r.stage,r.capability||"",r.startedAt||"",r.reviewing?1:0,r.ok==null?"":r.ok?1:0].join("|"):""}
59879
60023
  function runJustFinished(prev,next){return Boolean(prev&&prev.running&&next&&!next.running)}
@@ -60422,7 +60566,7 @@ function escT(s){return String(s==null?"":s).replace(/&/g,"&amp;").replace(/</g,
60422
60566
  function reviewPanel(id,name,state,run){const r=rData[id];const d=document.createElement("div");d.className="cds-panel review";
60423
60567
  const h3=document.createElement("h3");h3.className="cds-panel-title";h3.textContent="Blueprint Review for "+name;d.appendChild(h3);
60424
60568
  /* Staff first: before Approve, say plainly that Build waits for the staff (the run gate enforces it). */
60425
- {const sc=(state.staffChecks||{})[id];if(!sc||sc.status!=="ready"){const w=document.createElement("p");w.className="cds-callout warn";w.setAttribute("role","alert");w.textContent=sc?STAFF_REVIEW_WARNING:STAFF_REVIEW_UNCHECKED;d.appendChild(w)}}
60569
+ {const sc=(state.staffChecks||{})[id];if(!sc||sc.status!=="ready"){const w=document.createElement("p");w.className="cds-callout warn";w.setAttribute("role","alert");w.textContent=sc&&sc.status!=="unreadable"?STAFF_REVIEW_WARNING:STAFF_REVIEW_UNCHECKED;d.appendChild(w)}}
60426
60570
  /* Codex F1 (2026-09-06): the review is the model's output and the names are other businesses' records; both are TEXT. */
60427
60571
  const bt=(el,label,text)=>{const b=document.createElement("b");b.textContent=label;el.appendChild(b);el.appendChild(document.createTextNode(moneyText(text)))};
60428
60572
  const busy=reviewBusy(id,run);
@@ -60676,11 +60820,23 @@ a.textContent="Open Your agency";box.appendChild(a);return box}
60676
60820
  connected and before Intake and Build, and Intake and Build refuse to start until every
60677
60821
  person listed here is found in the account by email. The server reads the account every
60678
60822
  time; nothing on this panel is taken as proof on its own. */
60679
- let staffBusy={},staffSaid={},staffErr={};
60823
+ let staffBusy={},staffSaid={},staffErr={},staffPwHeld={};
60824
+ /* This account's one-time password lines from its last Builds (S1). A second client in the same sub-account (the same
60825
+ member's) has no card of its own, so its lines show here too, each naming that client (Codex on 50acb7eb). */
60826
+ function oneTimeFor(run,c){return ((run&&run.oneTime)||[]).filter(x=>x.locationId===c.locationId&&(!c.clientKey||x.clientKey===c.clientKey)).flatMap(x=>x.clientKey&&!c.clientKey?(x.lines||[]).map(l=>"For "+(x.clientName||x.clientKey)+": "+l):(x.lines||[]))}
60827
+ /* A one-time password whose create never showed up (passwordUnconfirmed) is held in this page only, never sent or
60828
+ saved, and shown the first time a check lists that person, in place of their waiting line (3.91.1). */
60829
+ /* A newer password for someone replaces any older one, held or shown (3.91.1, Codex on 6560441d); a shown password line
60830
+ starts "<name> was added. ", so "John Smith" never matches "John Smith Jr." (Codex on 610e9378). */
60831
+ function staffSupersede(id,results){for(const x of results||[])if(x.generatedPassword){const e=String(x.email).trim().toLowerCase();if(staffPwHeld[id])staffPwHeld[id]=staffPwHeld[id].filter(h=>h.email!==e);if(staffSaid[id])staffSaid[id]=staffSaid[id].filter(l=>!(l.includes(" Their one-time password is ")&&l.startsWith(x.name+" was added. ")))}}
60832
+ function staffHoldPasswords(id,results){for(const x of results||[])if(x.generatedPassword&&x.passwordUnconfirmed){const e=String(x.email).trim().toLowerCase();staffPwHeld[id]=(staffPwHeld[id]||[]).filter(h=>h.email!==e);staffPwHeld[id].push({email:e,name:x.name,said:x.said,pw:x.generatedPassword})}}
60833
+ function staffRevealPasswords(id,check){const held=staffPwHeld[id];if(!held||!check||!check.matched)return;const inAcct=new Set(check.matched.map(m=>String(m.email).trim().toLowerCase()));const left=[];
60834
+ for(const h of held){if(!inAcct.has(h.email)){left.push(h);continue}const lines=(staffSaid[id]||[]).filter(l=>l!==h.said);lines.push(h.name+" was added. GoHighLevel should email them an invitation. Their one-time password is "+h.pw+". Give it to them privately.");staffSaid[id]=lines}
60835
+ if(left.length)staffPwHeld[id]=left;else delete staffPwHeld[id]}
60680
60836
  /* Whether Add them for me would make this person an admin (the check's own creations, never guessed here). */
60681
60837
  function staffAdminFor(check,m){const cr=((check&&check.creations)||[]).find(x=>x.email===String(m.email).trim().toLowerCase());return Boolean(cr&&cr.role==="admin")}
60682
60838
  function staffCard(c,state){const box=document.createElement("section");box.className="cds-panel ipanel staffstep";box.id="staff-"+c.locationId;
60683
- const check=(state.staffChecks||{})[c.locationId];const roster=(state.staffRoster||{})[c.locationId]||{};
60839
+ const check=(state.staffChecks||{})[c.locationId];const roster=(state.staffRoster||{})[c.locationId]||{};staffRevealPasswords(c.locationId,check);
60684
60840
  const h=document.createElement("h3");h.className="cds-panel-title";h.textContent=check&&check.status==="ready"?"Your staff are in place":"Add your staff";box.appendChild(h);
60685
60841
  box.appendChild(mk("p","",check?check.headline+". "+check.body:"Calendars and lead alerts need real people. List everyone who takes appointments and everyone who should hear about new leads. We check this client's account for each email address before anything is built."));
60686
60842
  /* One listing in every state (3.90.1, Codex on 516081d8): where the list came from, then each person with their
@@ -60690,6 +60846,8 @@ function staffCard(c,state){const box=document.createElement("section");box.clas
60690
60846
  for(const m of check.matched||[]){const li=mk("li");li.appendChild(mk("span","ttl",m.name+" ("+m.email+")"+(m.role?" \xB7 "+m.role:"")));const end=mk("span","end");end.appendChild(chip("ok","In the account"));li.appendChild(end);ul.appendChild(li)}
60691
60847
  for(const m of check.missing||[]){const li=mk("li");li.appendChild(mk("span","ttl",m.name+" ("+m.email+")"+(m.role?" \xB7 "+m.role:"")));const end=mk("span","end");const w=(check.waiting||[]).find(x=>x.email===m.email);if(staffAdminFor(check,m))end.appendChild(chip("info",BS_ADMIN_MARK));end.appendChild(w?mk("span","small",w.line):chip("warn","Not in the account yet"));li.appendChild(end);ul.appendChild(li)}
60692
60848
  box.appendChild(ul)}
60849
+ /* A listed person already in the account at another level: said here too, as step 7 does (3.90.2). */
60850
+ for(const d of (check&&check.levelDiffs)||[])box.appendChild(mk("p","cds-callout warn",d.line));
60693
60851
  for(const line of (check&&check.steps)||[])box.appendChild(mk("p","small",line));
60694
60852
  if(check&&check.createRefused)box.appendChild(mk("p","small","GoHighLevel does not let this agency add people from outside GoHighLevel, so please add them yourself."));
60695
60853
  if(check&&check.howToAdd)box.appendChild(mk("p","small",check.howToAdd));
@@ -60713,8 +60871,11 @@ function staffCard(c,state){const box=document.createElement("section");box.clas
60713
60871
  run:async()=>{r=await(await clientPageRequest("/api/staff/add",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({locationId:c.locationId,shown:check.creations||[]})})).json();
60714
60872
  /* Any answer closes this confirmation (it showed the list as it was) and its lines go to the panel's own box,
60715
60873
  which stays through the redraw until Done (3.90.1, Codex on e4734bc4); a refusal's team is drawn at once. */
60716
- const lines=!r?[clientPageError("That did not go through. Click Check again to see where things stand.")]:[...(r.ok===false?[clientPageError(r.error||"That did not go through. Click Check again to see where things stand.")]:[]),...(r.said||[]),...(r.results||[]).filter(x=>x.generatedPassword).map(x=>x.said+" Their one-time password is "+x.generatedPassword+". Give it to them privately; it is not saved anywhere.")];
60717
- if(lines.length)staffSaid[c.locationId]=lines;
60874
+ const lines=!r?[clientPageError("That did not go through. Click Check again to see where things stand.")]:[...(r.ok===false?[clientPageError(r.error||"That did not go through. Click Check again to see where things stand.")]:[]),...(r.said||[]),...(r.results||[]).filter(x=>x.generatedPassword&&!x.passwordUnconfirmed).map(x=>x.said+" Their one-time password is "+x.generatedPassword+". Give it to them privately.")];
60875
+ /* A second press keeps the first press's one-time passwords: they are shown once (3.91.1). */
60876
+ staffSupersede(c.locationId,r&&r.results);
60877
+ if(lines.length)staffSaid[c.locationId]=[...(staffSaid[c.locationId]||[]).filter(l=>l.includes(" Their one-time password is ")),...lines];
60878
+ staffHoldPasswords(c.locationId,r&&r.results);
60718
60879
  if(r&&r.check&&lastState)lastState.staffChecks=Object.assign({},lastState.staffChecks,{[c.locationId]:r.check});
60719
60880
  return ""},
60720
60881
  after:async()=>{delete icOpen[addKey];load()}})};
@@ -60778,7 +60939,7 @@ title.textContent="Claude usage for this client";details.appendChild(title);for(
60778
60939
  if(parkedType){const pn=document.createElement("p");pn.className="muted";pn.textContent="This is a "+typeOf(state,c.locationId)+" account, so nothing is built into it from here. You can lock or unlock it below, or change its type to Client to build it.";div.appendChild(pn)}
60779
60940
  else if(!st.some(s=>s==="done"))div.appendChild(unnest(preflightCard(c)));
60780
60941
  /* Kept reachable after a finished Build while a re-run is held for staff (the gate saves its check). */
60781
- {const sc=(state.staffChecks||{})[c.locationId];if(!parkedType&&((st[3]||"pending")!=="done"||(sc&&sc.status!=="ready")||(staffSaid[c.locationId]||[]).length))div.appendChild(unnest(staffCard(c,state)))}
60942
+ {const sc=(state.staffChecks||{})[c.locationId];staffRevealPasswords(c.locationId,sc);if(!parkedType&&((st[3]||"pending")!=="done"||(sc&&sc.status!=="ready")||(staffSaid[c.locationId]||[]).length))div.appendChild(unnest(staffCard(c,state)))}
60782
60943
  // The steps, laid out as the owner approved on 2026-09-25: a progress bar, the next step in
60783
60944
  // full, then one row per step. Only the layout changed. Every button below keeps the condition
60784
60945
  // and the handler it had in the seven-column layout; the next-step panel draws the SAME
@@ -61069,6 +61230,8 @@ title.textContent="Claude usage for this client";details.appendChild(title);for(
61069
61230
  rr.appendChild(rb);div.appendChild(rr);
61070
61231
  if(rOpen[c.locationId])div.appendChild(unnest(reviewPanel(c.locationId,c.name,state,run)))}
61071
61232
  if(st[0]==="done"&&!intakeUp)intakeEntry(c,div,false);
61233
+ /* A password GoHighLevel set during this Build: from the run's own channel, in memory only, never in the report (S1). */
61234
+ {const otl=oneTimeFor(run,c);if(otl.length){const ot=mk("div","cds-callout warn cds-stack buildonetime");ot.setAttribute("role","status");for(const l of otl)ot.appendChild(mk("p","",l));div.appendChild(ot)}}
61072
61235
  if(run&&run.locationId===c.locationId&&(run.running||run.lines&&run.lines.length)){
61073
61236
  const bp=buildPanel(run);
61074
61237
  if(bp){div.appendChild(unnest(bp));
@@ -62450,6 +62613,7 @@ async function runDashboard(argv) {
62450
62613
  };
62451
62614
  const staffDepsFor = (locationId2) => {
62452
62615
  const c = clientFor(locationId2);
62616
+ const offerInputs = () => ({ hasAgencyKey: Boolean(c && registry2.getAgencyKey()), profile: readAgencyProfile(), refusedAt: readCockpitState().staffAddRefused?.[locationId2] });
62453
62617
  return {
62454
62618
  listUsers: async () => {
62455
62619
  if (!c) throw new Error("This client is not connected yet.");
@@ -62458,7 +62622,8 @@ async function runDashboard(argv) {
62458
62622
  // Offered only with an agency key, when the operator has not said their
62459
62623
  // plan cannot create accounts, and until GoHighLevel has refused it here
62460
62624
  // ("The company does not have access to this feature" below Agency Pro).
62461
- canCreateUsers: () => staffCreateOffered({ hasAgencyKey: Boolean(c && registry2.getAgencyKey()), profile: readAgencyProfile(), refusedAt: readCockpitState().staffAddRefused?.[locationId2] }),
62625
+ canCreateUsers: () => staffCreateOffered(offerInputs()),
62626
+ addBlocked: () => staffAddBlocker(offerInputs()),
62462
62627
  createUser: async (u) => {
62463
62628
  if (!c) throw new Error("This client is not connected yet.");
62464
62629
  return createOrBindUser(c, registry2, { locationId: locationId2, ...u });
@@ -63000,7 +63165,6 @@ async function runDashboard(argv) {
63000
63165
  source: clientFor,
63001
63166
  questions: (id) => composeQuestionSet({ industry: readCockpitState().accounts?.[id]?.industry, agency: readOverlay() })
63002
63167
  };
63003
- const staffWaitingStage = (id) => readCockpitState().clients[id]?.stages?.[0] === "done" ? "Build" : "Intake";
63004
63168
  const staffAnswer = (req, res, id, save) => {
63005
63169
  const seat = checkSeatToken({ method: "POST", headers: req.headers }, seatToken);
63006
63170
  if (seat) {
@@ -63043,7 +63207,7 @@ async function runDashboard(argv) {
63043
63207
  void clientPreflight(registry2, id, save ? {} : { persist: false, savedBuilder: readCockpitState().preflightBuilder?.[id] }).then(async (result) => {
63044
63208
  const line3 = result.lines.find((l) => l.key === "builder");
63045
63209
  const staffNow = await staffCheckRead(id).catch(() => void 0);
63046
- result.lines.push(staffPreflightLine(staffNow, staffWaitingStage(id)));
63210
+ result.lines.push(staffPreflightLine(staffNow));
63047
63211
  if (pfTicket && !cockpitCache.commitIfCurrent(pfTicket, () => {
63048
63212
  if (line3) {
63049
63213
  const st = readCockpitState();
@@ -63499,6 +63663,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
63499
63663
  } catch {
63500
63664
  }
63501
63665
  await oneStaffAddAtATime(queued, async () => {
63666
+ let made;
63502
63667
  try {
63503
63668
  const { locationId: locationId2, shown: shownRaw } = JSON.parse(body);
63504
63669
  if (typeof locationId2 !== "string" || !readableAccount(locationId2)) throw new Error("Choose a registered client.");
@@ -63523,10 +63688,14 @@ ${opportunityNoteFailure?.detail ?? ""}`);
63523
63688
  const name = [u.firstName, u.lastName === "-" ? "" : u.lastName].filter(Boolean).join(" ");
63524
63689
  holdStaff(locationId2, u.email, { name, at, phase: "creating" });
63525
63690
  const outcome = await deps.createUser(u);
63526
- if (outcome.ok && outcome.status === "created") holdStaff(locationId2, u.email, { name, at, phase: "created" });
63527
- else if (outcome.ok || createMadeNothing(outcome)) holdStaff(locationId2, u.email, void 0);
63691
+ try {
63692
+ if (outcome.ok && outcome.status === "created") holdStaff(locationId2, u.email, { name, at, phase: "created" });
63693
+ else if (outcome.ok || createMadeNothing(outcome)) holdStaff(locationId2, u.email, void 0);
63694
+ } catch {
63695
+ }
63528
63696
  return outcome;
63529
63697
  } }, listed, void 0, live);
63698
+ made = out.results;
63530
63699
  out.check = applyStaffPending(out.check, readCockpitState().staffPending?.[locationId2], Date.now());
63531
63700
  if (out.check.createRefused) {
63532
63701
  const latest = readCockpitState();
@@ -63563,8 +63732,9 @@ ${opportunityNoteFailure?.detail ?? ""}`);
63563
63732
  for (const r of out.results) if (r.status === "created" && !listedNow.has(normEmail(r.email))) said.push(staffNotOnTeamLine(r.name));
63564
63733
  reply(200, { ok: true, said, results: out.results, created, check: fresh });
63565
63734
  } catch (e) {
63566
- res.writeHead(400, { "Content-Type": "application/json" });
63567
- res.end(JSON.stringify({ ok: false, error: e instanceof Error ? e.message : "bad request" }));
63735
+ if (res.writableEnded) return;
63736
+ res.writeHead(made ? 500 : 400, { "Content-Type": "application/json" });
63737
+ res.end(JSON.stringify({ ok: false, error: e instanceof Error ? e.message : "bad request", ...made ? { said: staffAddSaid(made), results: made } : {} }));
63568
63738
  }
63569
63739
  });
63570
63740
  });
@@ -63782,6 +63952,14 @@ ${opportunityNoteFailure?.detail ?? ""}`);
63782
63952
  res.end(JSON.stringify(mine && activeRun2 ? { ...activeRun2, lines: memberProgress(activeRun2.lines, activeRun2.running, activeRun2.ok) } : activeRun2?.running ? { running: true, elsewhere: true, lines: [] } : { running: false, lines: [] }));
63783
63953
  return;
63784
63954
  }
63955
+ if (req.method === "GET" && url === "/api/run/secrets") {
63956
+ const q3 = new URL(req.url || "/", "http://x").searchParams;
63957
+ const askedLoc = gate2.plan === "pro" ? q3.get("locationId") ?? "" : q3.get("locationId");
63958
+ const askedKey = q3.get("clientKey");
63959
+ res.writeHead(200, { "Content-Type": "application/json" });
63960
+ res.end(JSON.stringify({ oneTime: [...buildOneTime.values()].filter((e) => Object.keys(e.byEmail).length && (askedLoc === null || e.locationId === askedLoc) && (askedKey === null || (e.clientKey ?? "") === askedKey)).map(oneTimeView) }));
63961
+ return;
63962
+ }
63785
63963
  if (req.method === "POST" && url === "/api/run") {
63786
63964
  readBody(req, res, 1e4, async (body) => {
63787
63965
  try {
@@ -63899,7 +64077,7 @@ ${opportunityNoteFailure?.detail ?? ""}`);
63899
64077
  startRun(locationId2, stage, (ok) => {
63900
64078
  writeCockpitState(setStage(readCockpitState(), locationId2, stage, ok ? "done" : "pending", who.clientKey));
63901
64079
  if (!who.clientKey) pushShared(locationId2);
63902
- }, { ...who.clientKey ? { clientKey: who.clientKey } : {}, name: currentRoster().find((r) => r.locationId === locationId2)?.name, approvedAt: stage <= 1 ? (/* @__PURE__ */ new Date()).toISOString() : st0.buildApproved?.[rk], writeReview: writeReviewFor, inputHash, normalizedAnswers, statusWriter: () => approvedStatusWriter(registry2, locationId2), verifyAccount: async (saved) => {
64080
+ }, { ...who.clientKey ? { clientKey: who.clientKey, clientName: who.name } : {}, name: currentRoster().find((r) => r.locationId === locationId2)?.name, approvedAt: stage <= 1 ? (/* @__PURE__ */ new Date()).toISOString() : st0.buildApproved?.[rk], writeReview: writeReviewFor, inputHash, normalizedAnswers, statusWriter: () => approvedStatusWriter(registry2, locationId2), verifyAccount: async (saved) => {
63903
64081
  const client = clientFor(locationId2);
63904
64082
  if (!client || !saved) throw new Error("The saved plan or account read connection is unavailable.");
63905
64083
  return readVerifyAccount(saved, client);
@@ -65304,12 +65482,15 @@ ${opportunityNoteFailure?.detail ?? ""}`);
65304
65482
  `);
65305
65483
  });
65306
65484
  return await new Promise((resolve13) => {
65307
- const stop2 = () => server2.close(() => resolve13(0));
65485
+ const stop2 = () => {
65486
+ closeAllSecretChannels();
65487
+ server2.close(() => resolve13(0));
65488
+ };
65308
65489
  process.on("SIGINT", stop2);
65309
65490
  process.on("SIGTERM", stop2);
65310
65491
  });
65311
65492
  }
65312
- var import_zod67, import_node_util5, fs47, path45, http2, import_child_process4, import_child_process5, osmod, VIEWER_COCKPIT_MSG, STAGES2, ACCOUNT_TYPES, SECOND_CLIENT_NO_INTAKE, stateRecovery, stateSaveFs, CHOOSE_WHAT_THEY_BOUGHT, CHOOSE_BEFORE_BUILD, TEXT_SNIPPETS_LOCKED, SCOPE_REFUSAL, APPROVAL_WITHDRAWN_FOR_STAFF, APPROVAL_WITHDRAWN_NO_TEAM, 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, INACTIVE, OFF_LICENSE, LICENSE_RECHECK_MS, activeRun2, runForTests, handoffEmailPreviews, UPGRADE_MSG, DOC_EXTS, DOC_MAX_BYTES, queueScanWatch, queueScanOnce, GATE_BODY, GATE_BODY_MAX, DOC_NOT_SAVED, NO_KEY_ON_FILE, PARKED_NOT_BUILT, NEEDS_AGENCY_KEY, 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, NOT_SIGNED_IN_MSG, SEAT_GUARDED_READS, licenceRefusedHooks, ConnectionRequest, DisconnectRequest, ReportBrandSaveRequest;
65493
+ var import_zod67, import_node_util5, fs47, path45, http2, import_child_process4, import_child_process5, osmod, VIEWER_COCKPIT_MSG, STAGES2, ACCOUNT_TYPES, SECOND_CLIENT_NO_INTAKE, stateRecovery, stateSaveFs, CHOOSE_WHAT_THEY_BOUGHT, CHOOSE_BEFORE_BUILD, TEXT_SNIPPETS_LOCKED, SCOPE_REFUSAL, APPROVAL_WITHDRAWN_FOR_STAFF, APPROVAL_WITHDRAWN_NO_TEAM, 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, INACTIVE, OFF_LICENSE, LICENSE_RECHECK_MS, activeRun2, buildOneTime, oneTimeView, runForTests, handoffEmailPreviews, UPGRADE_MSG, DOC_EXTS, DOC_MAX_BYTES, queueScanWatch, queueScanOnce, GATE_BODY, GATE_BODY_MAX, DOC_NOT_SAVED, NO_KEY_ON_FILE, PARKED_NOT_BUILT, NEEDS_AGENCY_KEY, 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, NOT_SIGNED_IN_MSG, SEAT_GUARDED_READS, licenceRefusedHooks, ConnectionRequest, DisconnectRequest, ReportBrandSaveRequest;
65313
65494
  var init_dashboard = __esm({
65314
65495
  "src/dashboard.ts"() {
65315
65496
  "use strict";
@@ -65375,6 +65556,7 @@ var init_dashboard = __esm({
65375
65556
  init_manual_tasks();
65376
65557
  init_jobs();
65377
65558
  init_go_live();
65559
+ init_build_secrets();
65378
65560
  init_sub_account_errors();
65379
65561
  init_provisioning();
65380
65562
  init_location_switcher();
@@ -65481,9 +65663,21 @@ var init_dashboard = __esm({
65481
65663
  OFF_LICENSE = "This computer is no longer on your license. Free a device on your license, then restart Command OS.";
65482
65664
  LICENSE_RECHECK_MS = 4 * 60 * 60 * 1e3;
65483
65665
  activeRun2 = null;
65484
- runForTests = { set(r) {
65485
- activeRun2 = r;
65486
- } };
65666
+ buildOneTime = /* @__PURE__ */ new Map();
65667
+ oneTimeView = (e) => ({ locationId: e.locationId, ...e.clientKey ? { clientKey: e.clientKey } : {}, ...e.clientName ? { clientName: e.clientName } : {}, lines: Object.values(e.byEmail) });
65668
+ runForTests = {
65669
+ set(r) {
65670
+ activeRun2 = r;
65671
+ },
65672
+ /** A Build's one-time lines for a client, as its channel would have kept them; null clears every client's. */
65673
+ oneTime(locationId2, lines = [], clientKey) {
65674
+ if (locationId2 === null) {
65675
+ buildOneTime.clear();
65676
+ return;
65677
+ }
65678
+ buildOneTime.set(clientRecordKey(locationId2, clientKey), { locationId: locationId2, ...clientKey ? { clientKey } : {}, byEmail: Object.fromEntries(lines.map((l, i) => [`p${i}@test`, l])) });
65679
+ }
65680
+ };
65487
65681
  handoffEmailPreviews = /* @__PURE__ */ new Map();
65488
65682
  UPGRADE_MSG = "\n The cockpit comes with Command OS, and this license does not include it, so it did not start.\n Nothing was changed. Your GHL Command tools still work in Claude as usual.\n\n";
65489
65683
  DOC_EXTS = /* @__PURE__ */ new Set([".md", ".txt", ".vtt", ".srt", ".csv"]);
@@ -65497,7 +65691,7 @@ var init_dashboard = __esm({
65497
65691
  PARKED_NOT_BUILT = "Template and hidden accounts are not built from Command OS. Change the account's type to Client first.";
65498
65692
  NEEDS_AGENCY_KEY = "No agency key on file. Run register_agency_key in GHL Command first.";
65499
65693
  INTAKE_UNREADABLE = "Command OS could not read this client's account. Paste its key again under Reconnect this client, at the top of its steps on Build a client account.";
65500
- INTAKE_READ_TRANSIENT = "Command OS could not reach this client's account just now. Try again in a moment.";
65694
+ INTAKE_READ_TRANSIENT = "We could not reach this client's account just now. Try again in a moment.";
65501
65695
  INTAKE_REFRESH_RETRYING = "Could not refresh just now; retrying.";
65502
65696
  ACCOUNT_CHANGED_DURING_READ = "The account's connection changed while it was being read, so nothing was kept or changed. Try again.";
65503
65697
  commitWithoutRead = (fn) => {
@@ -65556,7 +65750,7 @@ var init_dashboard = __esm({
65556
65750
  anthropicKeyWrites = Promise.resolve();
65557
65751
  SEAT_TOKEN_HEADER = "x-command-os-seat";
65558
65752
  NOT_SIGNED_IN_MSG = "This browser isn't signed in to Command OS on this computer. Open Command OS on the machine it runs on, and use the page it opens for you.";
65559
- SEAT_GUARDED_READS = ["/api/brief", "/api/brief/panel", "/api/connections", "/api/capability", "/api/client-intake", "/api/intake-documents", "/api/snapshots", "/api/agency-connection", "/api/client-preflight", "/api/staff"];
65753
+ SEAT_GUARDED_READS = ["/api/run/secrets", "/api/brief", "/api/brief/panel", "/api/connections", "/api/capability", "/api/client-intake", "/api/intake-documents", "/api/snapshots", "/api/agency-connection", "/api/client-preflight", "/api/staff"];
65560
65754
  licenceRefusedHooks = /* @__PURE__ */ new Set();
65561
65755
  ConnectionRequest = import_zod67.z.discriminatedUnion("system", [
65562
65756
  import_zod67.z.object({ system: import_zod67.z.literal("stripe"), key: import_zod67.z.string().min(8).max(4096), label: import_zod67.z.string().optional() }).strict(),
@@ -77144,7 +77338,8 @@ var init_build_reply = __esm({
77144
77338
  { name: "staff", apply: (r) => {
77145
77339
  const all = arr(r.staff);
77146
77340
  if (!all.length) return false;
77147
- r.staff = { counts: templateCounts(all), notReady: all.filter((m) => m.status !== "created" && m.status !== "existing").map((m) => ({ name: m.name, status: m.status })) };
77341
+ const passwordNotes = all.map((m) => m.passwordNote).filter((n) => typeof n === "string");
77342
+ r.staff = { counts: templateCounts(all), notReady: all.filter((m) => m.status !== "created" && m.status !== "existing").map((m) => ({ name: m.name, status: m.status })), ...passwordNotes.length ? { passwordNotes } : {} };
77148
77343
  return true;
77149
77344
  } },
77150
77345
  { name: "composed.warnings", apply: (r) => shrinkWarnings(r) }
@@ -77802,6 +77997,9 @@ var init_text_snippets = __esm({
77802
77997
  });
77803
77998
 
77804
77999
  // src/intake-to-build/execute.ts
78000
+ function pendingPasswordLine(name, password) {
78001
+ return `${name} was sent to GoHighLevel but is not on this account's user list yet. If GoHighLevel made the login, the password set for them is ${password}. Give it to them privately and ask them to change it when they sign in.`;
78002
+ }
77805
78003
  function staffPauseReason(calendars, people) {
77806
78004
  const list4 = (xs) => xs.length <= 1 ? xs[0] ?? "" : `${xs.slice(0, -1).join(", ")} and ${xs[xs.length - 1]}`;
77807
78005
  const cals = calendars.map((c) => `"${c}"`);
@@ -78030,7 +78228,7 @@ async function executeBackbone(plan, rawDeps, opts = {}) {
78030
78228
  for (const u of userList ?? []) accountUserIds.add(u.id);
78031
78229
  const canCreate = deps.canCreateUsers();
78032
78230
  let deferredUsers = 0;
78033
- const pendStaff = (u, reason3) => {
78231
+ const pendStaff = (u, reason3, password) => {
78034
78232
  const priorId = idMap[u.ref] || opts.priorIdMap?.[u.ref];
78035
78233
  if (readError && priorId && !priorId.includes("__pending__")) {
78036
78234
  const name = userDisplayName(u);
@@ -78038,11 +78236,11 @@ async function executeBackbone(plan, rawDeps, opts = {}) {
78038
78236
  idMap[u.ref] = priorId;
78039
78237
  accountUserIds.add(priorId);
78040
78238
  built.push({ ref: u.ref, type: "user", name, status: "existing", realId: priorId, warning });
78041
- staff.push({ ref: u.ref, name, email: u.email, role: u.role, status: "existing", userId: priorId, warnings: [warning] });
78239
+ staff.push({ ref: u.ref, name, email: u.email, role: u.role, status: "existing", userId: priorId, warnings: [warning], ...password ? { generatedPassword: password, passwordLine: pendingPasswordLine(name, password) } : {} });
78042
78240
  return;
78043
78241
  }
78044
78242
  idMap[u.ref] = PENDING_USER_REF;
78045
- staff.push({ ref: u.ref, name: userDisplayName(u), email: u.email, role: u.role, status: "pending", reason: reason3 });
78243
+ staff.push({ ref: u.ref, name: userDisplayName(u), email: u.email, role: u.role, status: "pending", reason: reason3, ...password ? { generatedPassword: password, passwordLine: pendingPasswordLine(userDisplayName(u), password) } : {} });
78046
78244
  manual.push({ ref: u.ref, type: "staff_pending", name: userDisplayName(u), reason: reason3 });
78047
78245
  };
78048
78246
  for (const u of users) {
@@ -78067,26 +78265,28 @@ async function executeBackbone(plan, rawDeps, opts = {}) {
78067
78265
  }
78068
78266
  let outcome = void 0;
78069
78267
  let recoveredUser;
78268
+ let sentPassword;
78070
78269
  try {
78071
78270
  outcome = await deps.createUser({ firstName: u.firstName, lastName: u.lastName, email: u.email, role: u.role, phone: u.phone });
78072
78271
  } catch (e) {
78272
+ if (e instanceof CreateUserError) sentPassword = e.generatedPassword;
78073
78273
  if (/read failed after \d attempts?\./.test(msg2(e))) {
78074
78274
  readError = msg2(e);
78075
- pendStaff(u, `Staff creation is unfinished because its account read failed. ${readError}`);
78275
+ pendStaff(u, `Staff creation is unfinished because its account read failed. ${readError}`, sentPassword);
78076
78276
  continue;
78077
78277
  }
78078
78278
  if (isRetryableAccountError(e)) {
78079
78279
  try {
78080
78280
  recoveredUser = findUserByEmail(await deps.listUsers(), u.email);
78081
- if (recoveredUser) outcome = { status: "uncertain", warnings: [`This person was created or found after a failed create; left in place; check it by hand. This build may have created the person. ${msg2(e)}`] };
78281
+ if (recoveredUser) outcome = { status: "uncertain", ...sentPassword ? { generatedPassword: sentPassword } : {}, warnings: [`This person was created or found after a failed create; left in place; check it by hand. This build may have created the person. ${msg2(e)}`] };
78082
78282
  } catch (readFailure) {
78083
78283
  readError = msg2(readFailure);
78084
- pendStaff(u, `Staff creation is unfinished. ${msg2(e)} The account could not be re-checked. ${readError}`);
78284
+ pendStaff(u, `Staff creation is unfinished. ${msg2(e)} The account could not be re-checked. ${readError}`, sentPassword);
78085
78285
  continue;
78086
78286
  }
78087
78287
  }
78088
78288
  if (!recoveredUser) {
78089
- pendStaff(u, explainStaffCreateFailure(name, u.email, msg2(e)));
78289
+ pendStaff(u, explainStaffCreateFailure(name, u.email, msg2(e)), sentPassword);
78090
78290
  continue;
78091
78291
  }
78092
78292
  }
@@ -78106,10 +78306,10 @@ async function executeBackbone(plan, rawDeps, opts = {}) {
78106
78306
  }
78107
78307
  if (!verified) {
78108
78308
  if (readError) {
78109
- pendStaff(u, `Staff member "${name}" could not be re-checked after creation. ${readError}`);
78309
+ pendStaff(u, `Staff member "${name}" could not be re-checked after creation. ${readError}`, outcome?.generatedPassword);
78110
78310
  continue;
78111
78311
  }
78112
- pendStaff(u, `Staff member "${name}" (${u.email}) was submitted to GoHighLevel but has not appeared on this account's user list yet (read-after-write lag, or the invite is still pending). Check ${GHL_STAFF_PAGE}, then re-run to bind them by email.`);
78312
+ pendStaff(u, `Staff member "${name}" (${u.email}) was submitted to GoHighLevel but has not appeared on this account's user list yet (read-after-write lag, or the invite is still pending). Check ${GHL_STAFF_PAGE}, then re-run to bind them by email.`, outcome?.generatedPassword);
78113
78313
  continue;
78114
78314
  }
78115
78315
  built.push({ ref: u.ref, type: "user", name, status: outcome?.status ?? "created", realId: verified.id, ...outcome?.status === "uncertain" ? { warning: (outcome.warnings ?? []).join(" ") || "Found after a failed create; confirm this person belongs to the account." } : {} });
@@ -79246,7 +79446,7 @@ function publishVerdict(outcome) {
79246
79446
  const status = typeof c.status_returned === "string" ? c.status_returned : "(GHL returned no status)";
79247
79447
  return { live: false, kind: "not_live", reason: warning || `NOT LIVE. The workflow read back as ${status} rather than "published", so it is not running.` };
79248
79448
  }
79249
- var norm5, sleep6, PREVIEW_SNIPPET_LENGTH, JournalHookError, WRITE_RETRIES, RUN_STOP_AFTER;
79449
+ var norm5, sleep6, CreateUserError, PREVIEW_SNIPPET_LENGTH, JournalHookError, WRITE_RETRIES, RUN_STOP_AFTER;
79250
79450
  var init_execute = __esm({
79251
79451
  "src/intake-to-build/execute.ts"() {
79252
79452
  "use strict";
@@ -79258,6 +79458,13 @@ var init_execute = __esm({
79258
79458
  init_build_progress();
79259
79459
  norm5 = (s) => s.trim().toLowerCase();
79260
79460
  sleep6 = (ms) => new Promise((r) => setTimeout(r, ms));
79461
+ CreateUserError = class extends Error {
79462
+ constructor(message, generatedPassword) {
79463
+ super(message);
79464
+ this.generatedPassword = generatedPassword;
79465
+ }
79466
+ generatedPassword;
79467
+ };
79261
79468
  PREVIEW_SNIPPET_LENGTH = 30;
79262
79469
  JournalHookError = class extends Error {
79263
79470
  constructor(cause) {
@@ -79655,7 +79862,7 @@ function makeExecuteDeps(client, builderClient, locationId2, registry2) {
79655
79862
  // the id via listUsers before binding.
79656
79863
  createUser: async (u) => {
79657
79864
  const outcome = await createOrBindUser(client, registry2, { ...u, locationId: locationId2 });
79658
- if (!outcome.ok) throw new Error(outcome.error);
79865
+ if (!outcome.ok) throw new CreateUserError(outcome.error, outcome.generatedPassword);
79659
79866
  return { userId: outcome.userId, status: outcome.status, warnings: outcome.warnings, generatedPassword: outcome.generatedPassword };
79660
79867
  },
79661
79868
  createCalendar: async (cal) => {
@@ -80212,6 +80419,8 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
80212
80419
  return jsonResponse({ ok: false, phase: "client", error: "This run was started for a different client in this sub-account, so nothing was built." });
80213
80420
  }
80214
80421
  clientKey = fromEnv.data;
80422
+ } else if (inCockpitBuild() && (clientKeyArg !== void 0 || requestedPrefix)) {
80423
+ return jsonResponse({ ok: false, phase: "client", error: "This run was started for this sub-account's own client, so nothing was built. Start the other client's Build from its own page." });
80215
80424
  } else if (clientKeyArg !== void 0) {
80216
80425
  const parsed2 = clientKeySchema.safeParse(clientKeyArg);
80217
80426
  if (!parsed2.success) return jsonResponse({ ok: false, phase: "client", error: parsed2.error.issues.map((i) => i.message).join(" ") });
@@ -80664,6 +80873,7 @@ function registerIntakeToBuildTools(server2, client, builderClient, registry2) {
80664
80873
  ...result.workflows.flatMap((w) => [...w.manual, ...w.needsContent].map((m) => ({ ref: w.ref, type: "workflow-manual", name: w.name, reason: m.reason }))),
80665
80874
  ...result.handoffs.filter((h) => !h.met).map((h, i) => ({ ref: `handoff:${i}`, type: "handoff", name: h.title, reason: h.instruction }))
80666
80875
  ]);
80876
+ if (inCockpitBuild()) withholdOneTimePasswords(exec.staff);
80667
80877
  const execManualLines = exec.manual.map((m) => `[${m.type}] ${m.reason}`);
80668
80878
  const manualLines = result.workflows.flatMap((w) => [
80669
80879
  ...w.manual.map((m) => `[${w.name}] ${m.reason}`),
@@ -81364,6 +81574,7 @@ var init_intake_to_build = __esm({
81364
81574
  init_features();
81365
81575
  init_executor();
81366
81576
  init_build_confirm();
81577
+ init_build_secrets();
81367
81578
  init_execute();
81368
81579
  init_build_facts();
81369
81580
  init_net_guard();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elitedcs/ghl-mcp",
3
- "version": "3.91.0",
3
+ "version": "3.91.1",
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",