npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@omnilogin/sdk

v0.1.0

Published

OmniLogin Automation SDK — manage browser profiles and automate with humanized interactions

Readme

@omnilogin/sdk

npm version license

Official SDK for automating OmniLogin browser profiles with humanized interactions.

Manage profiles via REST, launch isolated browser sessions, drive them with a Playwright-style API. Clicks, fills, hovers and key presses are humanized by default to look natural.

📚 Looking for a full type-level reference (every option, every parameter, every example)? See API.md.

Install

npm install @omnilogin/sdk
# Node < 21 also needs:  npm install ws

Prerequisites

  1. OmniLogin desktop running locally — download from omnilogin.net.
  2. Bridge API enabled in Settings → API (default port 35353).
  3. At least one browser profile created.

Quick Start

import { OmniLogin } from '@omnilogin/sdk';

const omni = new OmniLogin(); // defaults to http://localhost:35353

// Pick a profile to drive (any existing profile id from the desktop UI).
const profileId = 1;

// Launch the profile and connect a Session to its bridge.
const { session } = await omni.open(profileId);

// Drive the active tab — Playwright-style API.
await session.page.goto('https://example.com');
await session.page.locator('#username').fill('[email protected]');
await session.page.locator('#password').fill('secret');
await session.page.locator('button[type="submit"]').click();

// Use host services that ride the same bridge.
const titles = ['Result A', 'Result B'];
await session.services.file.export(titles, { path: 'C:/tmp/out.json', format: 'json' });
await session.services.sheets.append('YOUR_SPREADSHEET_ID', 'Sheet1!A:B', [['name', 42]]);

await omni.close(profileId);

Architecture

                ┌─────────────────────────────────────┐
                │             OmniLogin               │  ← top-level
                │  REST profile mgmt + open(id)       │
                └──────────┬──────────────────┬───────┘
              uses         │                  │         returns
                ▼          │                  │           ▼
          ┌──────────┐     │                  │     ┌────────────┐
          │ RestApi  │     │                  │     │  Session   │
          │ profiles │     │                  │     │  one open  │
          │ groups   │     │                  │     │  browser   │
          │ proxies  │     │                  │     │  via WS    │
          │ workflows│     │                  │     └────┬───────┘
          │ aiApps   │     │                  │          │
          │ ipv6     │     │                  │          ▼
          └──────────┘     │                  │   ┌────────────────┐
       (also as omni.rest) │                  │   │ page  · tabs   │
                                                  │ cookies        │
                                                  │ services       │
                                                  └────────────────┘

| Class | What it is | When to use | | --- | --- | --- | | OmniLogin | Top-level facade | Default. Profile mgmt + open() returning Session. | | RestApi | HTTP REST client (also omni.rest) | Standalone profile management without launching a browser. | | Session | One bridge connection to one open browser | Usually returned by omni.open(). Construct directly with a bridge URL to attach to a browser launched elsewhere — see Connecting to an already-running bridge. |

A Session exposes:

  • session.page — the active tab (locators, keyboard, mouse, snapshots)
  • session.tabs — open / close / list / activate tabs
  • session.cookies — cookies + storage state
  • session.services — host-side services (file, sheets, email, telegram, ai, http, imageSearch, profile, extension)

Connecting to an already-running bridge

If the browser was launched elsewhere (another process, the desktop UI, an earlier script) and you have its bridge URL, construct a Session directly:

import { Session } from '@omnilogin/sdk';

const session = new Session('ws://127.0.0.1:55301');
await session.connect();

await session.page.goto('https://example.com');
// ... drive page / tabs / cookies / services ...

await session.disconnect(); // closes the WebSocket; does NOT stop the browser

This skips the REST /open call entirely — useful for sidecars, background workers, or testing against a long-running profile. Use omni.open(profileId) for the standard launch-and-connect flow.

REST — profile & resource management

All REST operations are grouped by resource. Available via omni.profiles (shortcut) or omni.rest.profiles (canonical):

// List, read, create, clone, delete
const page = await omni.profiles.list({ page: 1, pageSize: 20 });
const profile = await omni.profiles.get(1);
const created = await omni.profiles.create({ name: 'My Profile' });
const cloned = await omni.profiles.clone(1, { name: 'Copy' });
await omni.profiles.delete(1);

// Partial update — pass only the fields you want to change.
// Each field maps to its own endpoint; fields are PUT sequentially.
// NOT atomic: if an intermediate PUT fails, earlier fields remain applied.
// Call `.get(id)` afterwards if you need the refreshed Profile object.
await omni.profiles.update(1, { name: 'Renamed' });
await omni.profiles.update(1, { status: 'active' });
await omni.profiles.update(1, { tags: ['vip', 'qa'] });
await omni.profiles.update(1, { proxyDisabled: true });

// Tags & proxies (multi-profile)
await omni.profiles.setTags([1, 2, 3], ['priority'], true); // append
await omni.profiles.assignProxy(7, [1, 2, 3]); // saved proxy → profiles
await omni.profiles.setEmbeddedProxy([1, 2], {
  proxy_type: 'HTTP',
  host: '1.2.3.4',
  port: 8080,
  user_name: 'u',
  password: 'p',
});

Other REST namespaces follow the same shape:

// Groups
await omni.groups.list();
await omni.groups.get(1);
await omni.groups.create('My Group');
await omni.groups.update(1, 'Renamed');
await omni.groups.delete(1);

// Proxies
await omni.proxies.list();
await omni.proxies.get(1);
await omni.proxies.create({ proxy_type: 'HTTP', host: '1.2.3.4', port: 8080 });
await omni.proxies.update(1, { proxy_type: 'HTTP', host: '5.6.7.8', port: 8080 });
await omni.proxies.delete(1);

// Workflows (classic automation)
const { taskId } = await omni.workflows.start('WORKFLOW_ID');
await omni.workflows.status(taskId!);
await omni.workflows.stop('WORKFLOW_ID');
await omni.workflows.stopAll();

// AI Apps (script-based automation)
await omni.aiApps.list();
await omni.aiApps.get('APP_ID');
await omni.aiApps.run('APP_ID', { mode: 'batch' });
await omni.aiApps.stop('APP_ID');

// IPv6 rotation
await omni.ipv6.rotate('PROXY_ID');

// Low-level browser control (usually replaced by omni.open / omni.close)
await omni.rest.browser.open(1, { launchBridge: true });
await omni.rest.browser.stop(1);
const isOpen = await omni.rest.browser.isActive(1);

Browser automation — session.page

// Navigation
await session.page.goto('https://example.com', { waitUntil: 'load' });
await session.page.goBack();
await session.page.goForward();
await session.page.reload();
const url = await session.page.url();
const title = await session.page.title();
const html = await session.page.content();

// Locators (CSS, text=, label=, role=, testid=, xpath=)
await session.page.locator('#btn').click(); // humanized by default
await session.page.locator('input').fill('value');
await session.page.locator('input').pressSequentially('hi'); // char-by-char
await session.page.locator('select').selectOption(['us']);
await session.page.locator('a').hover();
const text = await session.page.locator('h1').textContent();
const src = await session.page.locator('img').getAttribute('src');
const count = await session.page.locator('.row').count();

// Convenience getBy* factories
session.page.getByText('Submit');
session.page.getByRole('button');
session.page.getByLabel('Email');
session.page.getByPlaceholder('Search...');
session.page.getByTestId('login-btn');
session.page.getByAltText('Logo');

// Keyboard / mouse
await session.page.keyboard.type('hello');
await session.page.keyboard.press('Enter');
await session.page.mouse.click(100, 200);
await session.page.mouse.move(300, 400);
await session.page.mouse.wheel(0, -200);

// Wait for state / network / function
await session.page.waitForLoadState('networkidle');
await session.page.waitForURL('https://example.com/dashboard');
await session.page.waitForResponse('/api/me');

// Snapshots (cheap for AI agents)
const ax = await session.page.accessibilitySnapshot({ depth: 10 });
const md = await session.page.markdownSnapshot({ maxLength: 30_000 });

// Screenshot (returns base64; pass options.path to save automatically)
const screenshotBase64 = await session.page.screenshot({ fullPage: true });

// Raw CDP escape hatch
await session.page.rpc.call('DOM.getDocument', { depth: -1, pierce: true });
session.page.rpc.on('Network.responseReceived', event => console.log(event));

File upload (smart)

setInputFiles works on a real <input type="file"> and on any button / drag-zone / custom widget that opens a native file chooser on click. Each entry can be:

  • An absolute local path
  • An http(s)://... URL — downloaded to a temp file
  • A data:<mime>;base64,... — decoded to a temp file
  • <filename>|<url-or-data> — saved with the given filename
await session.page
  .locator('#upload')
  .setInputFiles([
    'C:/path/to/local.pdf',
    'https://example.com/avatar.png',
    'logo.svg|data:image/svg+xml;base64,PHN2Zy4uLg==',
  ]);

await session.page.locator('input[type="file"]').setInputFiles([]); // clear

Tabs & cookies — session-level

// Tabs
const opened = await session.tabs.open('https://x.com', { active: true });
const targetId = opened.targetId;
const { pages } = await session.tabs.list();
await session.tabs.activate(targetId);
await session.tabs.next();
await session.tabs.previous();
await session.tabs.close(targetId);

// Cookies + storage
const cookies = await session.cookies.list();
await session.cookies.add([{ name: 'sid', value: 'abc123', domain: '.example.com', path: '/' }]);
await session.cookies.clear();
const state = await session.cookies.getStorageState();
await session.cookies.setStorageState(state);

Host services — session.services

Multi-method services live as namespaces; single-operation services are direct callables.

const { services } = session;

// Sheets
await services.sheets.get('SPREADSHEET_ID', 'Sheet1!A1:D10', { firstRowAsKey: true });
await services.sheets.update('SPREADSHEET_ID', 'Sheet1!A1', [['hello', 'world']]);
await services.sheets.append('SPREADSHEET_ID', 'Sheet1!A:B', [['name', 42]]);
await services.sheets.clear('SPREADSHEET_ID', 'Sheet1!A:D');

// File I/O
const text = await services.file.read('C:/tmp/in.txt');
await services.file.write('C:/tmp/out.txt', 'hello');
await services.file.export({ a: 1 }, { path: 'C:/tmp/out.json', format: 'json' });
const savedTo = await services.file.download('https://example.com/x.png', { path: 'C:/tmp/x.png' });
const pngBase64 = await services.file.download('https://example.com/x.png');

// Email (IMAP)
const messages = await services.email.read({
  host: 'imap.gmail.com',
  port: 993,
  user: '[email protected]',
  password: 'app-password',
  tls: true,
  folder: 'INBOX',
  limit: 20,
});

// Profile (the running browser's profile — NOT REST profile mgmt)
const info = await services.profile.info();
await services.profile.setTags(['vip']);

// Direct callables
const res = await services.http('GET', 'https://api.example.com/foo', {
  headers: { Authorization: 'Bearer ...' },
  proxy: true,
});

await services.telegram('Job done', {
  botToken: 'BOT_TOKEN',
  chatId: '123456',
  parseMode: 'HTML',
});

const reply = await services.ai('Summarize: ...', {
  provider: 'openai',
  apiKey: 'sk-...',
  model: 'gpt-4o',
});

const matches = await services.imageSearch('C:/templates/button.png', {
  multiple: true,
});

await services.extension.trigger('EXTENSION_ID');

services.file.export() supports format: 'json' | 'csv' | 'text' and onConflict: 'overwrite' | 'append'. services.file.download() returns the saved path when options.path is provided, otherwise it returns the response body as base64.

Defaults & behaviors

  • Humanized by defaultclick, fill, hover, dblclick, check, uncheck, pressSequentially all pass { humanize: true }. Override with { humanize: false }.
  • Async/await everywhere — every method returns a Promise.
  • Selector chainingsession.page.locator('form').locator('input').first().fill('x').

Error handling

import { RpcError } from '@omnilogin/sdk';

try {
  await session.page.locator('#missing').click();
} catch (err) {
  if (err instanceof RpcError) {
    console.error(err.code, err.message, err.data);
  }
}

Method priority — when to use what

When generating or reviewing automation code, escalate in this order:

  1. First-class SDK methodspage.locator(), page.getBy*(), page.goto(), page.waitFor*(), locator text/value/state methods, accessibilitySnapshot(), markdownSnapshot().
  2. Raw CDPsession.page.rpc.call() / .on() when the SDK doesn't expose the capability.
  3. Runtime evaluatepage.evaluate(), locator.evaluate(), locator.evaluateAll(), page.waitForFunction() only as a final fallback.

| ✅ Do | ❌ Avoid | | --- | --- | | await session.page.url() | await session.page.evaluate(() => location.href) | | await session.page.title() | await session.page.evaluate(() => document.title) | | await session.page.locator(s).textContent() | .evaluate(el => el.textContent) | | await session.page.locator(s).count() | .evaluate(() => document.querySelectorAll(s).length) |

Resources

License

MIT © HiveSoft LTD