@gelglx/grok-builder-cli 1.0.0

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/.env.example ADDED
@@ -0,0 +1,11 @@
1
+ # ── Grok Builder CLI ──────────────────────────────────
2
+ # Password for Grok accounts (min 16 chars, mix cases+digits+symbols)
3
+ PASSWORD=YourSecurePassword123!@
4
+
5
+ # ── Temp Mail (CloakMail / glx.web.id) ───────────────
6
+ MAIL_URL=https://glx.web.id
7
+ MAIL_DOMAIN=glx.web.id
8
+
9
+ # ── 9router Gateway ───────────────────────────────────
10
+ ROUTER9_URL=http://localhost:20128
11
+ ROUTER9_PASS=123456
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # 🚀 Grok Builder CLI
2
+
3
+ **Auto-register Grok (x.ai) accounts — multi-threaded, with 9router integration**
4
+
5
+ ```bash
6
+ npx @gelglx/grok-builder-cli
7
+ ```
8
+
9
+ ## Features
10
+
11
+ - ✨ **Auto-register** Grok (x.ai) accounts via Playwright
12
+ - 📧 **Temp mail** via CloakMail (glx.web.id) — no external API needed
13
+ - 🛡️ **Turnstile bypass** — Chrome extension patches Cloudflare detection
14
+ - 🔗 **9router OAuth** — auto-add accounts as Grok CLI providers
15
+ - ⚡ **Multi-threaded** — parallel workers for batch registration
16
+ - 🎨 **Beautiful CLI** — progress bars, spinners, live stats
17
+
18
+ ## Quick Start
19
+
20
+ ```bash
21
+ # Set password (min 16 chars)
22
+ export PASSWORD='MySecurePass123!@'
23
+
24
+ # Run interactive CLI
25
+ npx @gelglx/grok-builder-cli
26
+ ```
27
+
28
+ Or install globally:
29
+
30
+ ```bash
31
+ npm install -g @gelglx/grok-builder-cli
32
+ grok-builder
33
+ ```
34
+
35
+ ## Environment Variables
36
+
37
+ | Variable | Default | Description |
38
+ |----------|---------|-------------|
39
+ | `PASSWORD` | *(required)* | Password for Grok accounts (min 16 chars) |
40
+ | `MAIL_URL` | `https://glx.web.id` | Temp mail base URL |
41
+ | `MAIL_DOMAIN` | `glx.web.id` | Temp mail domain |
42
+ | `ROUTER9_URL` | `http://localhost:20128` | 9router gateway URL |
43
+ | `ROUTER9_PASS` | `123456` | 9router dashboard password |
44
+
45
+ ## Requirements
46
+
47
+ - Node.js 18+
48
+ - Xvfb (Linux) — for headless browser display
49
+ - Google Chrome or Chromium installed
50
+
51
+ ## How It Works
52
+
53
+ ```
54
+ ┌──────────────────────────────────────────┐
55
+ │ 1. Create temp email (glx.web.id) │
56
+ │ 2. Open accounts.x.ai/sign-up │
57
+ │ 3. Enter email → wait for OTP │
58
+ │ 4. Extract code from mail inbox │
59
+ │ 5. Fill name + password │
60
+ │ 6. Turnstile auto-solve │
61
+ │ 7. Submit → redirect to grok.com │
62
+ │ 8. Save SSO cookies │
63
+ │ 9. (optional) OAuth device flow → 9R │
64
+ └──────────────────────────────────────────┘
65
+ ```
66
+
67
+ ## License
68
+
69
+ MIT
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@gelglx/grok-builder-cli",
3
+ "version": "1.0.0",
4
+ "description": "Auto-register Grok (x.ai) accounts with temp mail (CloakMail/glx.web.id) and auto-integrate into 9router — multi-threaded CLI",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "grok-builder": "./src/index.js"
8
+ },
9
+ "files": [
10
+ "src/",
11
+ "turnstilePatch/",
12
+ ".env.example",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "start": "node src/index.js"
17
+ },
18
+ "keywords": [
19
+ "grok",
20
+ "xai",
21
+ "x.ai",
22
+ "signup",
23
+ "registration",
24
+ "9router",
25
+ "temp-mail",
26
+ "cloakmail",
27
+ "playwright"
28
+ ],
29
+ "author": "gelglx",
30
+ "license": "MIT",
31
+ "dependencies": {
32
+ "playwright": "^1.60.0",
33
+ "chalk": "^5.4.0",
34
+ "ora": "^8.1.0",
35
+ "cli-progress": "^3.12.0",
36
+ "uuid": "^11.0.0"
37
+ },
38
+ "type": "module",
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
+ }
42
+ }
package/src/index.js ADDED
@@ -0,0 +1,214 @@
1
+ #!/usr/bin/env node
2
+
3
+ // GrokBuilderCLI — Auto-register Grok (x.ai) accounts + 9router integration
4
+ // Multi-threaded, interactive CLI
5
+
6
+ import { chromium } from 'playwright';
7
+ import chalk from 'chalk';
8
+ import ora from 'ora';
9
+ import cliProgress from 'cli-progress';
10
+ import * as readline from 'node:readline/promises';
11
+ import { stdin as input, stdout as output } from 'node:process';
12
+
13
+ import { runOneSignup, getTurnstilePath } from './signup.js';
14
+ import { login as loginRouter9 } from './router9.js';
15
+
16
+ // ── ANSI helpers ──────────────────────────────────────────────
17
+ const { bold, dim, green, red, yellow, cyan, magenta } = chalk;
18
+ const BAR_W = 74;
19
+
20
+ function barLine(color = cyan) {
21
+ console.log(color('─'.repeat(BAR_W)));
22
+ }
23
+
24
+ function header(text) {
25
+ console.log(`\n${cyan(` ─── [ ${text} ] `)}${cyan('─'.repeat(Math.max(0, BAR_W - text.length - 8)))}`);
26
+ }
27
+
28
+ function row(num, email, status, elapsed, extra = '') {
29
+ const e = (email || '—').padEnd(36).slice(0, 36);
30
+ const s = status.padEnd(10);
31
+ const t = elapsed ? dim(`${elapsed}s`) : '';
32
+ console.log(` ${dim(String(num).padStart(3))} ${cyan('│')} ${e} ${s} ${t} ${extra}`);
33
+ }
34
+
35
+ // ── Interactive prompt ───────────────────────────────────────
36
+ const rl = readline.createInterface({ input, output });
37
+
38
+ async function ask(q, defaultVal) {
39
+ const answer = await rl.question(`${cyan('?')} ${bold(q)} ${defaultVal ? dim(`(${defaultVal}) `) : ''}`);
40
+ return answer.trim() || defaultVal;
41
+ }
42
+
43
+ function closePrompt() {
44
+ rl.close();
45
+ }
46
+
47
+ // ── Main ──────────────────────────────────────────────────────
48
+ async function main() {
49
+ console.clear();
50
+ console.log(`\n${cyan('╔' + '═'.repeat(BAR_W - 2) + '╗')}`);
51
+ console.log(`${cyan('║')} ${bold('🚀 Grok Builder CLI')} ${dim('v1.0.0')}${' '.repeat(Math.max(0, BAR_W - 37))}${cyan('║')}`);
52
+ console.log(`${cyan('║')} ${dim('Auto-register Grok (x.ai) + 9router integration')}${' '.repeat(10)}${cyan('║')}`);
53
+ console.log(`${cyan('╚' + '═'.repeat(BAR_W - 2) + '╝')}\n`);
54
+
55
+ // ── Config checks ───────────────────────────────────────────
56
+ const checks = [
57
+ { label: 'PASSWORD ', ok: !!process.env.PASSWORD, msg: 'env PASSWORD set' },
58
+ { label: 'MAIL_URL ', ok: true, msg: process.env.MAIL_URL || 'https://glx.web.id (default)' },
59
+ { label: 'ROUTER9_URL', ok: true, msg: process.env.ROUTER9_URL || 'http://localhost:20128 (default)' },
60
+ ];
61
+
62
+ console.log(` ${dim('Config:')}`);
63
+ for (const c of checks) {
64
+ const icon = c.ok ? green('✓') : red('✗');
65
+ console.log(` ${icon} ${dim(c.label)} ${c.msg}`);
66
+ }
67
+
68
+ if (!process.env.PASSWORD) {
69
+ console.log(`\n ${yellow('⚠')} ${dim('Set PASSWORD in environment or .env file')}`);
70
+ console.log(` ${dim(' Example:')} PASSWORD='MySecurePass123!@' grok-builder`);
71
+ }
72
+
73
+ // ── Interactive questions ───────────────────────────────────
74
+ console.log();
75
+ const countStr = await ask('How many accounts to create?', '5');
76
+ const count = parseInt(countStr) || 5;
77
+
78
+ const threadsStr = await ask('How many parallel threads?', '2');
79
+ const threads = parseInt(threadsStr) || 2;
80
+
81
+ const addRouter = (await ask('Auto-add to 9router?', 'y')).toLowerCase() === 'y';
82
+
83
+ let routerOk = false;
84
+ if (addRouter) {
85
+ try {
86
+ await loginRouter9();
87
+ routerOk = true;
88
+ console.log(` ${green('✓')} ${dim('9router login OK')}`);
89
+ } catch (err) {
90
+ console.log(` ${red('✗')} ${dim(`9router login: ${err.message}`)}`);
91
+ }
92
+ }
93
+
94
+ closePrompt();
95
+
96
+ // ── Start ───────────────────────────────────────────────────
97
+ console.log();
98
+ header(`GROK BUILDER | ${count} accounts × ${threads} threads`);
99
+ barLine();
100
+
101
+ const tStart = Date.now();
102
+ const results = [];
103
+ let succeeded = 0, failed = 0;
104
+ let active = 0; // track concurrent
105
+
106
+ // Multi-progress bar
107
+ const multibar = new cliProgress.MultiBar({
108
+ clearOnComplete: false,
109
+ hideCursor: true,
110
+ format: ` ${cyan('│')} {bar} | {percentage}% | {value}/{total} accounts | {duration_formatted}`,
111
+ barCompleteChar: '■',
112
+ barIncompleteChar: '□',
113
+ barsize: 32,
114
+ }, cliProgress.Presets.shades_classic);
115
+
116
+ const progressBar = multibar.create(count, 0);
117
+
118
+ // ── Launch persistent browser with turnstile patch ──────────
119
+ const extPath = getTurnstilePath();
120
+ console.log(` ${dim('Turnstile patch:')} ${extPath}\n`);
121
+
122
+ // We'll use one shared browser for all contexts
123
+ const browser = await chromium.launch({
124
+ headless: false,
125
+ args: [
126
+ '--no-sandbox',
127
+ '--disable-dev-shm-usage',
128
+ '--disable-blink-features=AutomationControlled',
129
+ `--disable-extensions-except=${extPath}`,
130
+ `--load-extension=${extPath}`,
131
+ ],
132
+ });
133
+
134
+ // ── Worker pool ─────────────────────────────────────────────
135
+ async function worker(taskIndex) {
136
+ const context = await browser.newContext({
137
+ viewport: { width: 1280, height: 1024 },
138
+ ignoreHTTPSErrors: true,
139
+ });
140
+ try {
141
+ const account = await runOneSignup({
142
+ context,
143
+ addToRouter: addRouter && routerOk,
144
+ });
145
+ results.push(account);
146
+ succeeded++;
147
+ const elapsed = account.elapsed || ((Date.now() - tStart) / 1000).toFixed(1);
148
+ row(succeeded, account.email, green('SUCCESS'), elapsed);
149
+ } catch (err) {
150
+ failed++;
151
+ row(count - failed + 1, '—failed—', red('FAILED'), '', dim(err.message));
152
+ } finally {
153
+ await context.close();
154
+ progressBar.increment();
155
+ active--;
156
+ }
157
+ }
158
+
159
+ // ── Queue processor ─────────────────────────────────────────
160
+ let idx = 0;
161
+ async function processQueue() {
162
+ while (idx < count) {
163
+ if (active < threads) {
164
+ active++;
165
+ const taskIdx = idx++;
166
+ worker(taskIdx); // fire and forget — we track via active counter
167
+ }
168
+ await sleep(100); // small tick
169
+ }
170
+ // Wait for remaining workers
171
+ while (active > 0) {
172
+ await sleep(200);
173
+ }
174
+ }
175
+
176
+ await processQueue();
177
+ multibar.stop();
178
+
179
+ // ── Final report ────────────────────────────────────────────
180
+ console.log();
181
+ header('SYSTEM LOGS');
182
+ for (const r of results) {
183
+ const icon = r.router9 === 'added' ? green('→9R') : r.router9 ? red(`→${r.router9}`) : dim('—');
184
+ console.log(` ${green('DONE')} ${dim(`[${r.email}]`)} ${green('✓')} ${r.elapsed}s ${icon}`);
185
+ }
186
+ barLine();
187
+
188
+ const tElapsed = ((Date.now() - tStart) / 1000).toFixed(1);
189
+ const rate = succeeded / (tElapsed / 60);
190
+ const health = count > 0 ? (succeeded / count * 100).toFixed(1) : '0';
191
+
192
+ console.log(`\n ${dim('[Summary]')}`);
193
+ console.log(` ${green('✓')} ${succeeded} success ${red('✗')} ${failed} failed ${dim(`(${tElapsed}s, ${rate.toFixed(1)}/min)`)}`);
194
+ console.log(` Health: ${health}% | Speed: ${rate.toFixed(1)}/m`);
195
+ console.log();
196
+
197
+ // ── Save results ────────────────────────────────────────────
198
+ const fs = await import('fs');
199
+ const outPath = 'grok-accounts.json';
200
+ fs.writeFileSync(outPath, JSON.stringify(results, null, 2));
201
+ console.log(` ${dim('Saved:')} ${outPath} (${results.length} accounts)`);
202
+
203
+ await browser.close();
204
+ process.exit(0);
205
+ }
206
+
207
+ function sleep(ms) {
208
+ return new Promise(r => setTimeout(r, ms));
209
+ }
210
+
211
+ main().catch(err => {
212
+ console.error(`\n${red('FATAL:')} ${err.message}`);
213
+ process.exit(1);
214
+ });
package/src/mail.js ADDED
@@ -0,0 +1,99 @@
1
+ // glx.web.id (CloakMail) temp email client
2
+ // API: SvelteKit __data.json + SSR page parsing
3
+
4
+ import { randomBytes } from 'crypto';
5
+
6
+ const MAIL_BASE = process.env.MAIL_URL || 'https://glx.web.id';
7
+ const MAIL_DOMAIN = process.env.MAIL_DOMAIN || 'glx.web.id';
8
+
9
+ function randomLocal() {
10
+ const chars = 'abcdefghijklmnopqrstuvwxyz';
11
+ let s = '';
12
+ for (let i = 0; i < 8; i++) s += chars[randomBytes(1)[0] % 26];
13
+ const num = String(Date.now()).slice(-6);
14
+ return s + num;
15
+ }
16
+
17
+ export async function createTempEmail() {
18
+ const local = randomLocal();
19
+ const addr = `${local}@${MAIL_DOMAIN}`;
20
+ const url = `${MAIL_BASE}/confirm/${encodeURIComponent(addr)}/__data.json?x-sveltekit-invalidated=01`;
21
+ const res = await fetch(url);
22
+ if (!res.ok) throw new Error(`Mail confirm failed: ${res.status}`);
23
+ return addr;
24
+ }
25
+
26
+ /**
27
+ * Fetch emails from glx.web.id inbox using __data.json endpoint
28
+ * Returns array of {id, to, from, subject, text, html, receivedAt}
29
+ */
30
+ export async function fetchEmails(addr) {
31
+ const url = `${MAIL_BASE}/inbox/${encodeURIComponent(addr)}/__data.json?x-sveltekit-invalidated=01`;
32
+ const res = await fetch(url, {
33
+ headers: { Accept: 'application/json' }
34
+ });
35
+ if (!res.ok) throw new Error(`Inbox fetch failed: ${res.status}`);
36
+ const json = await res.json();
37
+
38
+ // Parse SvelteKit __data.json format
39
+ for (const node of json.nodes || []) {
40
+ if (node?.type === 'data') {
41
+ const data = node.data;
42
+ // data = [address_string, emails_array, pagination_object]
43
+ if (Array.isArray(data)) {
44
+ if (data.length >= 2 && Array.isArray(data[1])) return data[1];
45
+ if (data.length >= 3 && Array.isArray(data[2])) return data[2];
46
+ }
47
+ // alternative: data = {address, emails, pagination}
48
+ if (data?.emails && Array.isArray(data.emails)) return data.emails;
49
+ }
50
+ }
51
+
52
+ // Fallback: try parsing from page data at top level
53
+ if (json?.data?.emails) return json.data.emails;
54
+ return [];
55
+ }
56
+
57
+ /**
58
+ * Extract OTP code from email subject/text/html
59
+ * Returns 6-char code (without hyphen)
60
+ */
61
+ export function extractOTP(subject, text, html) {
62
+ const sources = [subject || '', text || '', html || ''];
63
+ for (const src of sources) {
64
+ // Pattern 1: "confirmation code: XXX-XXX"
65
+ let m = src.match(/confirmation code:\s*([A-Z0-9]{3}-[A-Z0-9]{3})/i);
66
+ if (m) return m[1].replace('-', '');
67
+ // Pattern 2: "code: XXX-XXX" or "code: XXXXXX"
68
+ m = src.match(/code:\s*([A-Z0-9]{3}-[A-Z0-9]{3})/i);
69
+ if (m) return m[1].replace('-', '');
70
+ m = src.match(/code[:\s]\s*([A-Z0-9]{6})/i);
71
+ if (m) return m[1];
72
+ // Pattern 3: inline HTML like >DQC-UR9<
73
+ m = src.match(/>([A-Z0-9]{3}-[A-Z0-9]{3})</);
74
+ if (m) return m[1].replace('-', '');
75
+ m = src.match(/>([A-Z0-9]{6})</);
76
+ if (m) return m[1];
77
+ }
78
+ return null;
79
+ }
80
+
81
+ /**
82
+ * Poll mailbox for OTP code (poll every 3s, timeout ms)
83
+ */
84
+ export async function waitForOTP(addr, timeout = 120000) {
85
+ const started = Date.now();
86
+ while (Date.now() - started < timeout) {
87
+ const emails = await fetchEmails(addr);
88
+ for (const email of emails) {
89
+ const code = extractOTP(email.subject, email.text, email.html);
90
+ if (code) return { code, email };
91
+ }
92
+ await sleep(3000);
93
+ }
94
+ throw new Error(`OTP timeout after ${timeout / 1000}s`);
95
+ }
96
+
97
+ function sleep(ms) {
98
+ return new Promise(r => setTimeout(r, ms));
99
+ }
package/src/router9.js ADDED
@@ -0,0 +1,118 @@
1
+ // 9router OAuth Device Flow integration
2
+ import { randomBytes } from 'crypto';
3
+
4
+ const ROUTER9_URL = process.env.ROUTER9_URL || 'http://localhost:20128';
5
+ const ROUTER9_PASS = process.env.ROUTER9_PASS || '123456';
6
+
7
+ let _authToken = null;
8
+
9
+ async function api(method, path, body) {
10
+ const headers = { 'Content-Type': 'application/json', Accept: 'application/json' };
11
+ const opts = { method, headers };
12
+ if (_authToken) headers.Cookie = `auth_token=${_authToken}`;
13
+ if (body) opts.body = JSON.stringify(body);
14
+
15
+ const res = await fetch(`${ROUTER9_URL}${path}`, opts);
16
+
17
+ // Handle Set-Cookie for auth
18
+ const setCookie = res.headers.get('set-cookie');
19
+ if (setCookie) {
20
+ const m = setCookie.match(/auth_token=([^;]+)/);
21
+ if (m) _authToken = m[1];
22
+ }
23
+ return res.json();
24
+ }
25
+
26
+ export async function login() {
27
+ const r = await api('POST', '/api/auth/login', { password: ROUTER9_PASS });
28
+ if (!r.success) throw new Error(`9router login failed: ${r.error || 'unknown'}`);
29
+ return true;
30
+ }
31
+
32
+ export async function requestDeviceCode() {
33
+ const r = await api('GET', '/api/oauth/grok-cli/device-code');
34
+ if (r.error) throw new Error(`Device code failed: ${r.error}`);
35
+ return r;
36
+ }
37
+
38
+ export async function pollDevice(deviceCode, codeVerifier) {
39
+ const r = await api('POST', '/api/oauth/grok-cli/poll', {
40
+ deviceCode,
41
+ codeVerifier
42
+ });
43
+ return r;
44
+ }
45
+
46
+ export async function listGrokProviders() {
47
+ const r = await api('GET', '/api/providers');
48
+ const conns = r.connections || [];
49
+ return conns.filter(c => c.provider === 'grok-cli');
50
+ }
51
+
52
+ /**
53
+ * Complete OAuth device flow via browser context (Playwright page)
54
+ * Returns {success, connection} after polling
55
+ */
56
+ export async function doDeviceFlow(page, cookies, { deviceCode, codeVerifier, userCode, verificationUriComplete }) {
57
+ // Inject SSO cookies
58
+ if (cookies && cookies.length > 0) {
59
+ const pwCookies = cookies.map(c => ({
60
+ ...c,
61
+ sameSite: ['Strict', 'Lax', 'None'].includes(c.sameSite) ? c.sameSite : 'Lax'
62
+ }));
63
+ try { await page.context().clearCookies(); } catch {}
64
+ await page.context().addCookies(pwCookies);
65
+ }
66
+
67
+ // Open verification URL
68
+ await page.goto(verificationUriComplete, { waitUntil: 'domcontentloaded', timeout: 45000 });
69
+ await sleep(3000);
70
+
71
+ // Check if SSO expired (login page shown)
72
+ const hasLoginInput = await page.evaluate(
73
+ () => !!document.querySelector('input[type=email], input[type=password]')
74
+ );
75
+ if (hasLoginInput) {
76
+ throw new Error('SSO expired — login required');
77
+ }
78
+
79
+ // Click "Continue" button
80
+ try {
81
+ await page.getByRole('button', { name: /continue/i, exact: false }).click({ timeout: 5000 });
82
+ await sleep(2500);
83
+ } catch {
84
+ // May already be on consent page
85
+ }
86
+
87
+ // Click "Allow" / "Allow All" on consent page
88
+ try {
89
+ await page.getByRole('button', { name: /^Allow$/i, exact: true }).click({ timeout: 8000 });
90
+ } catch {
91
+ try {
92
+ await page.getByRole('button', { name: /Allow All/i, exact: true }).click({ timeout: 3000 });
93
+ } catch {
94
+ try {
95
+ await page.getByRole('button', { name: /allow/i, exact: false }).click({ timeout: 3000 });
96
+ } catch {
97
+ throw new Error('Allow button not found on consent page');
98
+ }
99
+ }
100
+ }
101
+
102
+ await sleep(3000);
103
+
104
+ // Poll until success (max 60 tries = 5 min)
105
+ for (let i = 0; i < 60; i++) {
106
+ const res = await pollDevice(deviceCode, codeVerifier);
107
+ if (res.success) return res;
108
+ if (res.pending !== true) {
109
+ throw new Error(`Poll error: ${res.error || 'unknown'}`);
110
+ }
111
+ await sleep(5000);
112
+ }
113
+ throw new Error('Poll timeout after 5 minutes');
114
+ }
115
+
116
+ function sleep(ms) {
117
+ return new Promise(r => setTimeout(r, ms));
118
+ }
package/src/signup.js ADDED
@@ -0,0 +1,165 @@
1
+ // x.ai signup flow — Playwright automation
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { createTempEmail, waitForOTP } from './mail.js';
5
+ import * as r9 from './router9.js';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const TS_EXT = path.resolve(__dirname, '..', 'turnstilePatch');
9
+ const SIGNUP_URL = 'https://accounts.x.ai/sign-up?redirect=grok-com';
10
+
11
+ const PASSWORD = process.env.PASSWORD || 'GrokBuilder2024!Secure';
12
+
13
+ /**
14
+ * Run one signup flow + optional 9router addition
15
+ * @param {object} opts
16
+ * @param {import('playwright').BrowserContext} opts.context
17
+ * @param {boolean} opts.addToRouter
18
+ * @returns {Promise<object>} account data
19
+ */
20
+ export async function runOneSignup({ context, addToRouter = false }) {
21
+ const page = await context.newPage();
22
+ const startTime = Date.now();
23
+
24
+ try {
25
+ // Set extra headers
26
+ await page.setExtraHTTPHeaders({
27
+ 'sec-ch-ua': '"Chromium";v="128", "Google Chrome";v="128", "Not-A.Brand";v="99"',
28
+ 'sec-ch-ua-mobile': '?0',
29
+ 'sec-ch-ua-platform': '"Linux"',
30
+ 'accept-language': 'en-US,en;q=0.9',
31
+ });
32
+
33
+ // Step 1: Open x.ai signup
34
+ await page.goto(SIGNUP_URL, { waitUntil: 'domcontentloaded', timeout: 45000 });
35
+ await sleep(3000);
36
+ try { await page.getByRole('button', { name: /Accept All Cookies/i }).click({ timeout: 3000 }); } catch {}
37
+ await sleep(500);
38
+
39
+ // Step 2: Click "Sign up with email"
40
+ await page.getByText('Sign up with email').click({ timeout: 8000 });
41
+ await page.waitForSelector('input[type=email]', { timeout: 8000 });
42
+
43
+ // Step 3: Create temp email
44
+ const addr = await createTempEmail();
45
+ await page.locator('input[type=email]').fill(addr);
46
+ await page.locator('input[type=email]').press('Enter');
47
+
48
+ // Wait for code input to appear
49
+ try {
50
+ await page.waitForSelector('input[name=code]', { timeout: 20000 });
51
+ } catch {
52
+ try {
53
+ await page.getByRole('button', { name: /Sign up/i }).click({ timeout: 3000 });
54
+ await page.waitForSelector('input[name=code]', { timeout: 15000 });
55
+ } catch {
56
+ throw new Error('Could not reach OTP input');
57
+ }
58
+ }
59
+
60
+ // Step 4: Wait for OTP
61
+ const { code } = await waitForOTP(addr, 120000);
62
+
63
+ // Step 5: Submit OTP
64
+ await page.locator('input[name=code]').first().fill(code, { timeout: 15000 });
65
+ await sleep(500);
66
+ await page.keyboard.press('Enter');
67
+ await page.waitForSelector('input[name=givenName]', { timeout: 20000 });
68
+
69
+ // Step 6: Fill name & password
70
+ const local = addr.split('@')[0];
71
+ const parts = local.split(/[._\-]/);
72
+ const givenName = parts[0].charAt(0).toUpperCase() + parts[0].slice(1);
73
+ const familyName = (parts[1] || 'Xyz').charAt(0).toUpperCase() + (parts[1] || 'Xyz').slice(1);
74
+
75
+ await page.locator('input[name=givenName]').fill(givenName);
76
+ await page.locator('input[name=familyName]').fill(familyName);
77
+ await page.locator('input[name=password]').fill(PASSWORD);
78
+
79
+ // Step 7: Solve turnstile & submit
80
+ let tok = '';
81
+ for (let i = 0; i < 40; i++) {
82
+ tok = await page.evaluate(
83
+ () => document.querySelector('input[name=cf-turnstile-response]')?.value || ''
84
+ );
85
+ if (tok) break;
86
+ await sleep(1000);
87
+ }
88
+ if (!tok) throw new Error('Turnstile timeout 40s');
89
+
90
+ await page.getByRole('button', { name: /Complete sign up/i }).click();
91
+ await sleep(1000);
92
+
93
+ // Step 8: Wait for redirect to grok.com
94
+ let redirected = false;
95
+ for (let i = 0; i < 30; i++) {
96
+ await sleep(2000);
97
+ try {
98
+ const url = page.url();
99
+ if (url.includes('grok.com')) { redirected = true; break; }
100
+ } catch {}
101
+ }
102
+
103
+ if (!redirected) {
104
+ // Check if we have SSO cookies anyway
105
+ const cookies = await page.context().cookies();
106
+ const sso = cookies.filter(c => c.name?.toLowerCase().includes('sso'));
107
+ if (sso.length === 0) throw new Error(`No redirect to grok.com (last URL: ${page.url()})`);
108
+ }
109
+
110
+ // Step 9: Save credentials
111
+ const allCookies = await page.context().cookies();
112
+ const accountData = {
113
+ email: addr,
114
+ password: PASSWORD,
115
+ code,
116
+ sso_cookies: allCookies,
117
+ final_url: page.url(),
118
+ timestamp: Math.floor(Date.now() / 1000),
119
+ elapsed: ((Date.now() - startTime) / 1000).toFixed(1),
120
+ };
121
+
122
+ // Step 10: Add to 9router if requested
123
+ if (addToRouter) {
124
+ try {
125
+ await addToRouter9(page, accountData);
126
+ accountData.router9 = 'added';
127
+ } catch (err) {
128
+ accountData.router9 = `failed: ${err.message}`;
129
+ }
130
+ }
131
+
132
+ return accountData;
133
+ } finally {
134
+ await page.close();
135
+ }
136
+ }
137
+
138
+ async function addToRouter9(page, account) {
139
+ // Check if already exists
140
+ const existing = await r9.listGrokProviders();
141
+ if (existing.some(c => c.email === account.email)) {
142
+ throw new Error('Already exists in 9router');
143
+ }
144
+
145
+ const dc = await r9.requestDeviceCode();
146
+ const result = await r9.doDeviceFlow(page, account.sso_cookies, {
147
+ deviceCode: dc.device_code,
148
+ codeVerifier: dc.codeVerifier,
149
+ userCode: dc.user_code,
150
+ verificationUriComplete: dc.verification_uri_complete
151
+ });
152
+
153
+ return result;
154
+ }
155
+
156
+ /**
157
+ * Get turnstile patch extension path
158
+ */
159
+ export function getTurnstilePath() {
160
+ return TS_EXT;
161
+ }
162
+
163
+ function sleep(ms) {
164
+ return new Promise(r => setTimeout(r, ms));
165
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "Turnstile Patcher",
4
+ "version": "2.1",
5
+ "content_scripts": [
6
+ {
7
+ "js": [
8
+ "./script.js"
9
+ ],
10
+ "matches": [
11
+ "<all_urls>"
12
+ ],
13
+ "run_at": "document_start",
14
+ "all_frames": true,
15
+ "world": "MAIN"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,12 @@
1
+ function getRandomInt(min, max) {
2
+ return Math.floor(Math.random() * (max - min + 1)) + min;
3
+ }
4
+
5
+ // old method wouldn't work on 4k screens
6
+
7
+ let screenX = getRandomInt(800, 1200);
8
+ let screenY = getRandomInt(400, 600);
9
+
10
+ Object.defineProperty(MouseEvent.prototype, 'screenX', { value: screenX });
11
+
12
+ Object.defineProperty(MouseEvent.prototype, 'screenY', { value: screenY });