- Introduction
- Installation
- Configure Morelogin
- Using CLI Tools
- CDP Connection Details
- Automation Examples
- FAQ
- Advanced Usage
Morelogin is a fingerprint browser that supports multi-account management and browser environment isolation. This Skill provides the ability to connect to and control Morelogin browsers via CDP (Chrome DevTools Protocol), and works with automation tools such as Puppeteer and Playwright.
- Native support: Morelogin is Chromium-based and natively supports the CDP protocol
- Powerful features: Control almost all browser capabilities
- Rich ecosystem: Use mature tools like Puppeteer and Playwright
- No WebDriver needed: Connect directly to the browser without WebDriver
macOS:
# Download the installer
open https://morelogin.com/download
# Or use Homebrew (if available)
brew install --cask moreloginWindows:
# Download the installer from official website
# https://morelogin.com/download# Navigate to the skill directory
cd ~/.openclaw/workspace/skills/morelogin
# Install dependencies
npm install# Using Puppeteer
npm install puppeteer-core
# Or using Playwright
npm install playwrightOpen the Morelogin app and log in with your account.
- Click "Create Profile"
- Select browser type (Chrome/Edge/Firefox)
- Configure proxy (optional)
- Configure fingerprint info (optional; Morelogin auto-generates)
- Save the profile
Morelogin supports CDP by default, but ensure:
- CDP is enabled when the profile starts
- CDP port is not in use (default: 9222)
- Local API service is running
Add to ~/.openclaw/workspace/TOOLS.md:
### Morelogin
- API Token: your_api_token_here (if needed)
- Install Path: /Applications/Morelogin.app (macOS)
- Default CDP Port: 9222
- Local API: http://localhost:40000# Navigate to the skill directory
cd ~/.openclaw/workspace/skills/morelogin
# View all commands
node bin/morelogin.js help
# Or use npm script
npm start -- helpThe CLI is organized by the official Local API into five namespaces:
browser/cloudphone/proxy/group/tag.
Legacy commandslist/start/stop/info/connectstill work, but the new command format is recommended.
# List profiles
node bin/morelogin.js browser list --page 1 --page-size 20
# Start a profile
node bin/morelogin.js browser start --env-id abc123def
# Note: start may occasionally timeout at 10s while profile still launches.
# Always re-check with status before retrying.
# View running status (debugPort can be used directly for CDP connection when present)
node bin/morelogin.js browser status --env-id abc123def
# View details
node bin/morelogin.js browser detail --env-id abc123def
# Clear local cache (select cleanup items by official fields)
node bin/morelogin.js browser clear-cache --env-id abc123def --cookie true --local-storage true
# Clear cloud cache (cookie / other cache)
node bin/morelogin.js browser clean-cloud-cache --env-id abc123def --cookie true --others true
# Close profile
node bin/morelogin.js browser close --env-id abc123def# List cloud phones
node bin/morelogin.js cloudphone list --page 1 --page-size 20
# Start/Stop
node bin/morelogin.js cloudphone start --id <cloudPhoneId>
node bin/morelogin.js cloudphone stop --id <cloudPhoneId>
# Get details (includes ADB info)
node bin/morelogin.js cloudphone info --id <cloudPhoneId>
# Execute cloud phone command
node bin/morelogin.js cloudphone exec --id <cloudPhoneId> --command "ls /sdcard"
# Query ADB connection info (for identifying connection method)
node bin/morelogin.js cloudphone adb-info --id <cloudPhoneId>
# Enable ADB (official fields: ids + enableAdb)
node bin/morelogin.js cloudphone update-adb --id <cloudPhoneId> --enable true
# Auto-connect ADB by device model:
# - Android 12 / 15: Direct connect to adbIp:adbPort
# - Android 13 / 14 / 15A: Auto-create SSH tunnel, then connect to localhost:adbPort
node bin/morelogin.js cloudphone adb-connect --id <cloudPhoneId> --wait-seconds 90
# View current ADB devices
node bin/morelogin.js cloudphone adb-devices
# Disconnect ADB (including tunnel cleanup)
node bin/morelogin.js cloudphone adb-disconnect --id <cloudPhoneId># Query proxy list
node bin/morelogin.js proxy list --page 1 --page-size 20
# Add proxy (recommend using --payload for full parameters)
node bin/morelogin.js proxy add --payload '{"proxyIp":"1.2.3.4","proxyPort":8000,"proxyType":0,"proxyProvider":"0"}'
# Update proxy
node bin/morelogin.js proxy update --payload '{"id":"<proxyId>","proxyIp":"5.6.7.8","proxyPort":9000}'
# Delete proxy
node bin/morelogin.js proxy delete --ids "<proxyId1>,<proxyId2>"# Query groups
node bin/morelogin.js group list --page 1 --page-size 20
# Create group
node bin/morelogin.js group create --name "US-Group"
# Edit group
node bin/morelogin.js group edit --id "<groupId>" --name "US-Group-New"
# Delete group
node bin/morelogin.js group delete --ids "<groupId1>,<groupId2>"# Query tags (GET /api/envtag/all)
node bin/morelogin.js tag list
# Create tag
node bin/morelogin.js tag create --name "vip"
# Edit tag
node bin/morelogin.js tag edit --id "<tagId>" --name "vip-new"
# Delete tag
node bin/morelogin.js tag delete --ids "<tagId1>,<tagId2>"After Morelogin starts, the following endpoints are exposed:
HTTP: http://127.0.0.1:<debugPort>/json/version
HTTP: http://127.0.0.1:<debugPort>/json/list
WS: ws://127.0.0.1:<debugPort>/devtools/browser/<browser_id>
WS: ws://127.0.0.1:<debugPort>/devtools/page/<page_id>
- After starting the profile, open Chrome browser
- Visit:
chrome://inspect/#devices - Morelogin instances appear under "Remote Target"
- Click "inspect" to open DevTools
const puppeteer = require('puppeteer-core');
// Get debugPort from:
// node bin/morelogin.js browser status --env-id <envId>
const browser = await puppeteer.connect({
browserURL: 'http://127.0.0.1:<debugPort>',
defaultViewport: null
});
const pages = await browser.pages();
const page = pages[0];
await page.goto('https://example.com');const { chromium } = require('playwright');
// Get debugPort from:
// node bin/morelogin.js browser status --env-id <envId>
const browser = await chromium.connectOverCDP('http://127.0.0.1:<debugPort>');
const context = browser.contexts()[0];
const page = context.pages()[0];
await page.goto('https://example.com');const WebSocket = require('ws');
const ws = new WebSocket('ws://127.0.0.1:<debugPort>/devtools/browser/<browser_id>');
ws.on('open', () => {
ws.send(JSON.stringify({
id: 1,
method: 'Page.navigate',
params: { url: 'https://example.com' }
}));
});
ws.on('message', (data) => {
console.log(JSON.parse(data));
});// examples/basic-example.js
const puppeteer = require('puppeteer-core');
async function main() {
// Get debugPort from:
// node bin/morelogin.js browser status --env-id <envId>
const browser = await puppeteer.connect({
browserURL: 'http://127.0.0.1:<debugPort>'
});
const page = await browser.newPage();
// Navigate
await page.goto('https://example.com');
// Screenshot
await page.screenshot({ path: 'screenshot.png' });
// Get content
const title = await page.title();
console.log('Title:', title);
await browser.close();
}
main();// examples/form-example.js
const puppeteer = require('puppeteer-core');
async function main() {
// Get debugPort from:
// node bin/morelogin.js browser status --env-id <envId>
const browser = await puppeteer.connect({
browserURL: 'http://127.0.0.1:<debugPort>'
});
const page = await browser.newPage();
await page.goto('https://example.com/login');
// Fill form
await page.type('#username', 'myuser');
await page.type('#password', 'mypassword');
// Click submit
await page.click('button[type="submit"]');
// Wait for navigation
await page.waitForNavigation();
// Verify login
const isLoggedIn = await page.$('.user-profile');
console.log('Login successful:', !!isLoggedIn);
await browser.close();
}
main();// examples/scraping-example.js
const puppeteer = require('puppeteer-core');
const fs = require('fs');
async function main() {
// Get debugPort from:
// node bin/morelogin.js browser status --env-id <envId>
const browser = await puppeteer.connect({
browserURL: 'http://127.0.0.1:<debugPort>'
});
const page = await browser.newPage();
await page.goto('https://example.com/products');
// Scrape data
const products = await page.evaluate(() => {
return Array.from(document.querySelectorAll('.product')).map(el => ({
name: el.querySelector('.name')?.textContent,
price: el.querySelector('.price')?.textContent,
url: el.querySelector('a')?.href
}));
});
// Save to file
fs.writeFileSync('products.json', JSON.stringify(products, null, 2));
console.log(`Scraped ${products.length} products`);
await browser.close();
}
main();// examples/multi-tab-example.js
const puppeteer = require('puppeteer-core');
async function main() {
// Get debugPort from:
// node bin/morelogin.js browser status --env-id <envId>
const browser = await puppeteer.connect({
browserURL: 'http://127.0.0.1:<debugPort>'
});
// Open multiple tabs
const page1 = await browser.newPage();
const page2 = await browser.newPage();
await page1.goto('https://example.com');
await page2.goto('https://github.com');
// Parallel operations
await Promise.all([
page1.screenshot({ path: 'page1.png' }),
page2.screenshot({ path: 'page2.png' })
]);
console.log('Screenshots completed');
await browser.close();
}
main();// examples/playwright-example.js
const { chromium } = require('playwright');
async function main() {
// Get debugPort from:
// node bin/morelogin.js browser status --env-id <envId>
const browser = await chromium.connectOverCDP('http://127.0.0.1:<debugPort>');
const context = browser.contexts()[0];
const page = context.pages()[0] || await context.newPage();
await page.goto('https://example.com');
// Use Playwright features
await page.pdf({ path: 'page.pdf', format: 'A4' });
console.log('PDF generated');
// Keep browser open
await new Promise(() => {});
}
main();Issue: Error: connect ECONNREFUSED
Solutions:
- Ensure the Morelogin app is running
- Check that the profile is running
- Verify CDP port is not in use:
lsof -i :9222 - Try a different CDP port:
--cdp-port 9223
Issue: Browser closes immediately after connecting
Solutions:
- Use
puppeteer-coreinstead ofpuppeteer - Set
defaultViewport: null - Do not call
browser.close()unless you want to close the browser
Issue: Error when starting the profile
Solutions:
- Check if Morelogin is logged in
- Verify the profile ID is correct
- If
browser starttimes out at 10s, runbrowser statusto confirm actual running state - Check Morelogin log files
- Try restarting the Morelogin app
Issue: Script execution is very slow
Solutions:
- Reduce unnecessary wait times
- Use
waitForSelectorinstead of fixedsetTimeout - Disable image loading:
page.setRequestInterception(true) - Use headless mode (if Morelogin supports it)
Method 1: List via CLI
node bin/morelogin.js browser listMethod 2: From Morelogin app
- Right-click the profile in the Morelogin app
- Select "Copy ID" or view profile details
Method 3: From Morelogin data files
# macOS
cat ~/Library/Application\ Support/Morelogin/profiles.json
# Windows
cat %APPDATA%\Morelogin\profiles.jsonUsing Morelogin in OpenClaw:
# Invoke via OpenClaw
openclaw morelogin browser start --env-id <envId>
openclaw morelogin cloudphone info --id <cloudPhoneId>
openclaw morelogin proxy list --page 1 --page-size 20// Batch start multiple profiles
const profileIds = ['id1', 'id2', 'id3'];
for (const id of profileIds) {
await callApi('/api/env/start', { envId: id });
await sleep(2000); // Wait 2 seconds
}const browser = await puppeteer.connect({
browserURL: 'http://127.0.0.1:<debugPort>'
});
const client = await browser.target().createCDPSession();
// Send raw CDP commands
await client.send('Network.setCookies', {
cookies: [{ name: 'test', value: '123', domain: 'example.com' }]
});
await client.send('Emulation.setDeviceMetricsOverride', {
width: 1920,
height: 1080,
deviceScaleFactor: 1,
mobile: false
});const browser = await puppeteer.connect({
browserURL: 'http://127.0.0.1:<debugPort>'
});
const page = await browser.newPage();
// Listen for performance metrics
page.on('metrics', ({ metrics }) => {
console.log('JS Heap Size:', metrics.JSHeapUsedSize);
});
// Enable performance monitoring
await page.setCacheEnabled(false);
await page.setViewport({ width: 1920, height: 1080 });const browser = await puppeteer.connect({
browserURL: 'http://127.0.0.1:<debugPort>'
});
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request', request => {
// Block ads
if (request.url().includes('ads')) {
request.abort();
}
// Modify request headers
else {
const headers = request.headers();
headers['x-custom-header'] = 'my-value';
request.continue({ headers });
}
});
page.on('response', response => {
console.log(`${response.status()} ${response.url()}`);
});- Morelogin Official Site
- Morelogin API Document
- Chrome DevTools Protocol
- Puppeteer Docs
- Playwright Docs
- OpenClaw Docs
- Initial release
- Profile management support
- CDP connection support
- Puppeteer and Playwright examples
- OpenClaw integration
Enjoy! 🎉
For issues, see the FAQ section or contact support.