@elitedcs/ghl-mcp 3.48.2 → 3.49.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 +38 -0
- package/README.md +15 -6
- package/dist/capture-helper.js +288 -0
- package/dist/index.js +777 -502
- package/package.json +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.49.1 — Docs patch: one-click capture is now the primary Milestone-2 path
|
|
4
|
+
|
|
5
|
+
No behavior changes; text only.
|
|
6
|
+
|
|
7
|
+
- README "Enable the Workflow Builder": `capture_firebase_interactive` (log into a
|
|
8
|
+
Chrome window, zero pasting) is the supported path; the console-paste script is
|
|
9
|
+
demoted to a collapsed fallback (still the path for locked-down machines and for
|
|
10
|
+
capturing CLIENT accounts in multi-tenant). Rotation note now points at the
|
|
11
|
+
silent re-capture.
|
|
12
|
+
- `enable_workflow_builder` and `auto_capture_firebase_script` tool descriptions
|
|
13
|
+
point to `capture_firebase_interactive` first.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## 3.49.0 — One-click Workflow Builder unlock (`capture_firebase_interactive`)
|
|
17
|
+
|
|
18
|
+
The buyer's whole job is now: log into GHL in a Chrome window the tool opens.
|
|
19
|
+
No DevTools, no console paste, no JSON shuttling.
|
|
20
|
+
|
|
21
|
+
- **New tool `capture_firebase_interactive`** (normal mode). Spawns a detached
|
|
22
|
+
helper (`dist/capture-helper.js`) that launches the buyer's own installed
|
|
23
|
+
Chrome (playwright-core channel discovery — Edge fallback, zero browser
|
|
24
|
+
downloads) into a dedicated persistent profile, waits for a GHL login, reads
|
|
25
|
+
the three Firebase values straight out of IndexedDB (same selection logic as
|
|
26
|
+
the console script), validates them against Firebase, and saves them via the
|
|
27
|
+
same path as `enable_workflow_builder`.
|
|
28
|
+
- **Silent re-capture for token rotations.** The persistent profile keeps the
|
|
29
|
+
GHL session cookie, so every capture after the first tries HEADLESS first —
|
|
30
|
+
refresh-token rotation becomes "run the tool again, no window appears".
|
|
31
|
+
- **Timeout-proof by design.** The helper is fully detached and communicates
|
|
32
|
+
through a state file; the tool call polls with a generous window and tells
|
|
33
|
+
the buyer to re-run if they're still mid-login. An MCP client timeout can
|
|
34
|
+
never strand the flow.
|
|
35
|
+
- **Fallbacks intact.** No Chrome/Edge, closed window, or login timeout all
|
|
36
|
+
return clear instructions pointing at `auto_capture_firebase_script` (the
|
|
37
|
+
console-paste path, unchanged).
|
|
38
|
+
- Tool count: 228 → 229 (177 without Firebase).
|
|
39
|
+
|
|
40
|
+
|
|
3
41
|
## 3.48.2 — Docs patch: tool-count claims corrected (the real number is 228)
|
|
4
42
|
|
|
5
43
|
No behavior changes; text only, plus one new test.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# GHL Command — GoHighLevel MCP Server
|
|
2
2
|
|
|
3
|
-
**Full GoHighLevel API access for Claude.**
|
|
3
|
+
**Full GoHighLevel API access for Claude.** 229 tools across 48 modules — manage contacts, conversations, pipelines, calendars, funnels, workflows, invoices, custom objects, webhooks, and more. **Includes full workflow builder, funnel/page editor, form builder, pipeline builder, bulk operations, account export, and workflow cloning** — capabilities no other GHL tool offers. **Multi-tenant:** one install can run the workflow builder across multiple clients' GHL accounts.
|
|
4
4
|
|
|
5
5
|
**Distributed via npm as [`@elitedcs/ghl-mcp`](https://www.npmjs.com/package/@elitedcs/ghl-mcp).** Buyers install with one config block — no git, no Node.js setup, no terminal commands. Updates flow automatically (`npx @latest` re-resolves on every Claude restart).
|
|
6
6
|
|
|
@@ -116,7 +116,7 @@ Run setup_ghl_mcp to activate GHL Command:
|
|
|
116
116
|
ghl_location_id: YOUR_LOCATION_ID
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
Approve the tool call. Server validates your license, verifies your GHL credentials, writes them to a per-user config file. **Quit Claude one more time and reopen** — the full core toolset is now unlocked (
|
|
119
|
+
Approve the tool call. Server validates your license, verifies your GHL credentials, writes them to a per-user config file. **Quit Claude one more time and reopen** — the full core toolset is now unlocked (229 tools total with the optional Workflow Builder Firebase add-on).
|
|
120
120
|
|
|
121
121
|
### 4. Try it
|
|
122
122
|
|
|
@@ -156,9 +156,18 @@ https://app.gohighlevel.com/v2/location/YOUR_LOCATION_ID/dashboard
|
|
|
156
156
|
|
|
157
157
|
## Enable the Workflow Builder
|
|
158
158
|
|
|
159
|
-
The builder + cloner + validator tools (workflow builder, funnel builder, form builder, pipeline builder, workflow cloner, validate_workflow) use GHL's internal API and require Firebase credentials. Without them, the other
|
|
159
|
+
The builder + cloner + validator tools (workflow builder, funnel builder, form builder, pipeline builder, workflow cloner, validate_workflow) use GHL's internal API and require Firebase credentials. Without them, the other 177 tools work fine — you just won't have workflow/funnel/form/pipeline editing. This is the flagship feature: do this step.
|
|
160
160
|
|
|
161
|
-
**One-
|
|
161
|
+
**One-click capture (v3.49.0+, the supported path):**
|
|
162
|
+
|
|
163
|
+
1. In Claude, ask: `Run capture_firebase_interactive`. Your installed Chrome opens a dedicated window on the GHL login page (no browser download; it never touches your daily browsing profile).
|
|
164
|
+
2. Log into your GHL account in that window. That's the whole job — the tool reads the three Firebase values itself, validates them against Firebase, and saves them. If the call says it's still waiting, finish logging in and run the same tool again.
|
|
165
|
+
3. Restart Claude. The Firebase-gated tools are live.
|
|
166
|
+
|
|
167
|
+
> Firebase refresh tokens can rotate. If workflow tools stop working after a few weeks (`health_check` shows Firebase auth: FAIL), just re-run `capture_firebase_interactive` — after your first login it re-captures silently, no window at all.
|
|
168
|
+
|
|
169
|
+
<details>
|
|
170
|
+
<summary><strong>Console-paste fallback</strong> (no Chrome/Edge installed, the window path fails, or you're capturing a CLIENT account for multi-tenant)</summary>
|
|
162
171
|
|
|
163
172
|
1. In Claude, ask: `Run auto_capture_firebase_script`. It returns a small browser-console script.
|
|
164
173
|
2. In Chrome, open a tab logged into your GHL account. Press **F12** (Windows) or **Cmd+Option+I** (Mac), click the **Console** tab, paste the script, press Enter. If Chrome blocks the paste, type `allow pasting` in the Console first, then paste again.
|
|
@@ -168,7 +177,7 @@ The builder + cloner + validator tools (workflow builder, funnel builder, form b
|
|
|
168
177
|
|
|
169
178
|
Screenshots and the same script behind a copy button: [elitedcs.com/ghl-mcp-firebase](https://elitedcs.com/ghl-mcp-firebase)
|
|
170
179
|
|
|
171
|
-
>
|
|
180
|
+
</details>
|
|
172
181
|
|
|
173
182
|
<details>
|
|
174
183
|
<summary><strong>Manual fallback</strong> (locked-down browsers that block console pasting or clipboard access)</summary>
|
|
@@ -736,7 +745,7 @@ Source repo is private. Contributors need an invitation from `drjerryrelth`. The
|
|
|
736
745
|
|
|
737
746
|
### Reducing context / token usage
|
|
738
747
|
|
|
739
|
-
Every registered MCP tool's schema is shipped to the model on every message. With
|
|
748
|
+
Every registered MCP tool's schema is shipped to the model on every message. With 229 tools that's a meaningful per-message context cost even in chats that never touch GHL. If you only use a slice of GHL Command, restrict the tool surface with `GHL_ENABLED_MODULES` and/or `GHL_ENABLED_TOOLS`:
|
|
740
749
|
|
|
741
750
|
```jsonc
|
|
742
751
|
// Claude Desktop config — enable whole modules
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
|
|
25
|
+
// src/interactive-capture.ts
|
|
26
|
+
var fs = __toESM(require("fs"));
|
|
27
|
+
var path2 = __toESM(require("path"));
|
|
28
|
+
var import_zod2 = require("zod");
|
|
29
|
+
|
|
30
|
+
// src/credentials-store.ts
|
|
31
|
+
var path = __toESM(require("path"));
|
|
32
|
+
var os = __toESM(require("os"));
|
|
33
|
+
var import_zod = require("zod");
|
|
34
|
+
var APP_NAME = "elitedcs-ghl-mcp";
|
|
35
|
+
var CredentialsSchema = import_zod.z.object({
|
|
36
|
+
license_key: import_zod.z.string().min(1),
|
|
37
|
+
email: import_zod.z.string().email(),
|
|
38
|
+
verified_at: import_zod.z.string().min(1),
|
|
39
|
+
ghl_api_key: import_zod.z.string().min(1),
|
|
40
|
+
ghl_location_id: import_zod.z.string().min(1),
|
|
41
|
+
ghl_company_id: import_zod.z.string().optional(),
|
|
42
|
+
ghl_user_id: import_zod.z.string().optional(),
|
|
43
|
+
ghl_firebase_api_key: import_zod.z.string().optional(),
|
|
44
|
+
ghl_firebase_refresh_token: import_zod.z.string().optional(),
|
|
45
|
+
// Ed25519-signed attestation from elitedcs.com/api/validate-license.
|
|
46
|
+
// v3.24.0+ writes this on every successful online validation; index.ts
|
|
47
|
+
// verifies on startup and refuses to load tools without it (closing the
|
|
48
|
+
// hand-crafted-credentials.json bypass that existed through v3.23.0).
|
|
49
|
+
// Optional in the schema only so files written by earlier versions still
|
|
50
|
+
// parse — index.ts's gate forces a re-validate when the field is missing.
|
|
51
|
+
signed_attestation: import_zod.z.string().optional()
|
|
52
|
+
});
|
|
53
|
+
function appDataDir() {
|
|
54
|
+
const override = process.env.GHL_MCP_CONFIG_DIR?.trim();
|
|
55
|
+
if (override) {
|
|
56
|
+
if (!path.isAbsolute(override)) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`GHL_MCP_CONFIG_DIR must be an absolute path (got "${override}"). Use e.g. /data/ghl-mcp in a container, with a volume mounted at /data.`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return override;
|
|
62
|
+
}
|
|
63
|
+
const home = os.homedir();
|
|
64
|
+
if (process.platform === "darwin") {
|
|
65
|
+
return path.join(home, "Library", "Application Support", APP_NAME);
|
|
66
|
+
}
|
|
67
|
+
if (process.platform === "win32") {
|
|
68
|
+
const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
69
|
+
return path.join(appData, APP_NAME);
|
|
70
|
+
}
|
|
71
|
+
const xdg = process.env.XDG_CONFIG_HOME || path.join(home, ".config");
|
|
72
|
+
return path.join(xdg, APP_NAME);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/interactive-capture.ts
|
|
76
|
+
function chromeProfileDir() {
|
|
77
|
+
return path2.join(appDataDir(), "chrome-profile");
|
|
78
|
+
}
|
|
79
|
+
function captureStatePath() {
|
|
80
|
+
return path2.join(appDataDir(), "interactive-capture.json");
|
|
81
|
+
}
|
|
82
|
+
var CaptureStateSchema = import_zod2.z.object({
|
|
83
|
+
status: import_zod2.z.enum(["waiting_login", "captured", "timeout", "error"]),
|
|
84
|
+
startedAt: import_zod2.z.string(),
|
|
85
|
+
updatedAt: import_zod2.z.string(),
|
|
86
|
+
// Present when status === "captured"
|
|
87
|
+
ghl_firebase_api_key: import_zod2.z.string().optional(),
|
|
88
|
+
ghl_user_id: import_zod2.z.string().optional(),
|
|
89
|
+
ghl_firebase_refresh_token: import_zod2.z.string().optional(),
|
|
90
|
+
account_email: import_zod2.z.string().optional(),
|
|
91
|
+
// Present when status === "error"
|
|
92
|
+
error: import_zod2.z.string().optional()
|
|
93
|
+
});
|
|
94
|
+
function writeCaptureState(state) {
|
|
95
|
+
const dir = appDataDir();
|
|
96
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
97
|
+
fs.writeFileSync(captureStatePath(), JSON.stringify(state, null, 2), {
|
|
98
|
+
mode: 384
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
var EXTRACT_FIREBASE_PAGE_FN = `async () => {
|
|
102
|
+
const pick = (rows) => {
|
|
103
|
+
const candidates = rows.filter(r => typeof r?.fbase_key === 'string' && r.fbase_key.startsWith('firebase:authUser:AIza') && r?.value?.apiKey && r?.value?.uid && r?.value?.stsTokenManager?.refreshToken);
|
|
104
|
+
if (!candidates.length) return null;
|
|
105
|
+
const v = candidates[0].value;
|
|
106
|
+
return {
|
|
107
|
+
ghl_firebase_api_key: v.apiKey,
|
|
108
|
+
ghl_user_id: v.uid,
|
|
109
|
+
ghl_firebase_refresh_token: v.stsTokenManager.refreshToken,
|
|
110
|
+
account_email: v.email || null,
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
try {
|
|
114
|
+
let poisoned = false;
|
|
115
|
+
if (typeof indexedDB.databases === 'function') {
|
|
116
|
+
const dbs = await indexedDB.databases();
|
|
117
|
+
if (dbs.some(d => d.name === 'firebaseLocalStorageDb')) {
|
|
118
|
+
const db = await new Promise((res, rej) => {
|
|
119
|
+
const r = indexedDB.open('firebaseLocalStorageDb');
|
|
120
|
+
r.onsuccess = () => res(r.result);
|
|
121
|
+
r.onerror = () => rej(r.error);
|
|
122
|
+
});
|
|
123
|
+
if (db.objectStoreNames.contains('firebaseLocalStorage')) {
|
|
124
|
+
const tx = db.transaction('firebaseLocalStorage', 'readonly');
|
|
125
|
+
const store = tx.objectStore('firebaseLocalStorage');
|
|
126
|
+
const rows = await new Promise((res, rej) => {
|
|
127
|
+
const r = store.getAll();
|
|
128
|
+
r.onsuccess = () => res(r.result);
|
|
129
|
+
r.onerror = () => rej(r.error);
|
|
130
|
+
});
|
|
131
|
+
db.close();
|
|
132
|
+
const found = pick(rows);
|
|
133
|
+
if (found) return { found };
|
|
134
|
+
} else {
|
|
135
|
+
db.close();
|
|
136
|
+
poisoned = true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const lsRows = [];
|
|
141
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
142
|
+
const k = localStorage.key(i);
|
|
143
|
+
if (k && k.startsWith('firebase:authUser:AIza')) {
|
|
144
|
+
try { lsRows.push({ fbase_key: k, value: JSON.parse(localStorage.getItem(k)) }); } catch (e) {}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const fromLs = pick(lsRows);
|
|
148
|
+
if (fromLs) return { found: fromLs };
|
|
149
|
+
return poisoned ? { poisoned: true } : null;
|
|
150
|
+
} catch (e) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}`;
|
|
154
|
+
var HEAL_POISONED_DB_PAGE_FN = `() => new Promise((resolve) => {
|
|
155
|
+
const r = indexedDB.deleteDatabase('firebaseLocalStorageDb');
|
|
156
|
+
const t = setTimeout(() => resolve('timeout'), 5000);
|
|
157
|
+
r.onsuccess = () => { clearTimeout(t); resolve('deleted'); };
|
|
158
|
+
r.onblocked = () => { clearTimeout(t); resolve('blocked'); };
|
|
159
|
+
r.onerror = () => { clearTimeout(t); resolve('error'); };
|
|
160
|
+
})`;
|
|
161
|
+
var EXTRACT_FIREBASE_EXPRESSION = `(${EXTRACT_FIREBASE_PAGE_FN})()`;
|
|
162
|
+
var HEAL_POISONED_DB_EXPRESSION = `(${HEAL_POISONED_DB_PAGE_FN})()`;
|
|
163
|
+
var GHL_APP_URL = "https://app.gohighlevel.com/";
|
|
164
|
+
var HELPER_LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
165
|
+
var HEADLESS_PROBE_TIMEOUT_MS = 30 * 1e3;
|
|
166
|
+
function profileExists() {
|
|
167
|
+
return fs.existsSync(chromeProfileDir());
|
|
168
|
+
}
|
|
169
|
+
var CAPTURED_FRESH_MS = 10 * 60 * 1e3;
|
|
170
|
+
var WAITING_FRESH_MS = HELPER_LOGIN_TIMEOUT_MS + 30 * 1e3;
|
|
171
|
+
|
|
172
|
+
// src/capture-helper.ts
|
|
173
|
+
var now = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
174
|
+
function writeState(partial, startedAt) {
|
|
175
|
+
writeCaptureState({ ...partial, startedAt, updatedAt: now() });
|
|
176
|
+
}
|
|
177
|
+
async function launchContext(headless) {
|
|
178
|
+
const { chromium } = await import("playwright-core");
|
|
179
|
+
const channels = ["chrome", "msedge"];
|
|
180
|
+
let lastError;
|
|
181
|
+
for (const channel of channels) {
|
|
182
|
+
try {
|
|
183
|
+
return await chromium.launchPersistentContext(chromeProfileDir(), {
|
|
184
|
+
channel,
|
|
185
|
+
headless,
|
|
186
|
+
viewport: null
|
|
187
|
+
});
|
|
188
|
+
} catch (e) {
|
|
189
|
+
lastError = e;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
throw lastError instanceof Error ? lastError : new Error("No Chrome or Edge installation found");
|
|
193
|
+
}
|
|
194
|
+
async function pollPage(context, timeoutMs) {
|
|
195
|
+
const page = context.pages()[0] ?? await context.newPage();
|
|
196
|
+
await page.goto(GHL_APP_URL, { waitUntil: "domcontentloaded", timeout: 6e4 }).catch(() => {
|
|
197
|
+
});
|
|
198
|
+
const deadline = Date.now() + timeoutMs;
|
|
199
|
+
let healed = false;
|
|
200
|
+
while (Date.now() < deadline) {
|
|
201
|
+
try {
|
|
202
|
+
const target = context.pages().find((p) => p.url().includes("gohighlevel.com")) ?? page;
|
|
203
|
+
const result = await target.evaluate(EXTRACT_FIREBASE_EXPRESSION);
|
|
204
|
+
const found = result?.found;
|
|
205
|
+
if (found?.ghl_firebase_api_key && found.ghl_user_id && found.ghl_firebase_refresh_token) {
|
|
206
|
+
return found;
|
|
207
|
+
}
|
|
208
|
+
if (result?.poisoned && !healed) {
|
|
209
|
+
healed = true;
|
|
210
|
+
await target.evaluate(HEAL_POISONED_DB_EXPRESSION).catch(() => {
|
|
211
|
+
});
|
|
212
|
+
await target.reload({ waitUntil: "domcontentloaded", timeout: 6e4 }).catch(() => {
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
if (context.pages().length === 0) return null;
|
|
218
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
async function main() {
|
|
223
|
+
const startedAt = now();
|
|
224
|
+
writeState({ status: "waiting_login" }, startedAt);
|
|
225
|
+
if (profileExists()) {
|
|
226
|
+
try {
|
|
227
|
+
const ctx2 = await launchContext(true);
|
|
228
|
+
try {
|
|
229
|
+
const captured = await pollPage(ctx2, HEADLESS_PROBE_TIMEOUT_MS);
|
|
230
|
+
if (captured) {
|
|
231
|
+
writeState({ status: "captured", ...toState(captured) }, startedAt);
|
|
232
|
+
await ctx2.close();
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
} finally {
|
|
236
|
+
await ctx2.close().catch(() => {
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
} catch {
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
let ctx;
|
|
243
|
+
try {
|
|
244
|
+
ctx = await launchContext(false);
|
|
245
|
+
} catch (e) {
|
|
246
|
+
writeState(
|
|
247
|
+
{
|
|
248
|
+
status: "error",
|
|
249
|
+
error: "Could not launch Chrome or Edge: " + (e instanceof Error ? e.message : String(e))
|
|
250
|
+
},
|
|
251
|
+
startedAt
|
|
252
|
+
);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
writeState({ status: "waiting_login" }, startedAt);
|
|
257
|
+
const captured = await pollPage(ctx, HELPER_LOGIN_TIMEOUT_MS);
|
|
258
|
+
if (captured) {
|
|
259
|
+
writeState({ status: "captured", ...toState(captured) }, startedAt);
|
|
260
|
+
} else {
|
|
261
|
+
writeState({ status: "timeout" }, startedAt);
|
|
262
|
+
}
|
|
263
|
+
} catch (e) {
|
|
264
|
+
writeState(
|
|
265
|
+
{ status: "error", error: e instanceof Error ? e.message : String(e) },
|
|
266
|
+
startedAt
|
|
267
|
+
);
|
|
268
|
+
} finally {
|
|
269
|
+
await ctx.close().catch(() => {
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function toState(c) {
|
|
274
|
+
return {
|
|
275
|
+
ghl_firebase_api_key: c.ghl_firebase_api_key,
|
|
276
|
+
ghl_user_id: c.ghl_user_id,
|
|
277
|
+
ghl_firebase_refresh_token: c.ghl_firebase_refresh_token,
|
|
278
|
+
...c.account_email ? { account_email: c.account_email } : {}
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
main().catch((e) => {
|
|
282
|
+
writeCaptureState({
|
|
283
|
+
status: "error",
|
|
284
|
+
error: e instanceof Error ? e.message : String(e),
|
|
285
|
+
startedAt: now(),
|
|
286
|
+
updatedAt: now()
|
|
287
|
+
});
|
|
288
|
+
});
|