Bỏ qua để đến nội dung
Tài liệu này mô tả bản Veilus sau v0.2.1, sắp phát hành. Nếu bạn đang dùng v0.2.1, một số màn hình và tính năng (Bộ dữ liệu, Thùng rác, panel hồ sơ mới, nhiều tool MCP) chưa có trong bản của bạn.

Use a Profile from Other Tools

Nội dung này hiện chưa có sẵn bằng ngôn ngữ của bạn.

Most browser automation tools and AI browser agents can attach to a Chromium that is already running, over the Chrome DevTools Protocol (CDP). A running Veilus profile exposes that address, so you can keep the tool you already use and point it at a profile — with that profile’s fingerprint, proxy and cookies.

If you write your own Playwright script, the veilus package finds the local API, launches the profile and connects Playwright to it in one call. It needs Veilus 0.2.9 or newer with the local API turned on, and an API token in VEILUS_API_TOKEN — see REST API.

Terminal window
npm install veilus playwright-core
import { Veilus } from 'veilus';
const veilus = new Veilus();
const [profile] = await veilus.listProfiles();
const { page, close } = await veilus.connect(profile.id);
await page.goto('https://example.com');
console.log(await page.title());
await close(); // detaches; the profile keeps running
Terminal window
pip install "veilus[playwright]"
import asyncio
from veilus import Veilus
async def main():
veilus = Veilus()
profile = (await veilus.list_profiles())[0]
session = await veilus.connect(profile["id"])
await session.page.goto("https://example.com")
print(await session.page.title())
await session.close() # detaches; the profile keeps running
asyncio.run(main())

connect() reuses the profile if it is already running, otherwise launches it, and returns the profile’s own context and page. The full API is in the package README on npm and PyPI. For any other tool, read the profile’s CDP address yourself, as below.

Both examples were run on macOS on October 13, 2026, with veilus 0.1.0 from npm (Node 22, playwright-core 1.64.0) and from PyPI (Python 3.14, Playwright 1.63.0), against Veilus 0.2.10. After close(), the profile was still running.

You need the local API turned on — see REST API for the port and token.

Launch the profile; the answer carries its cdpUrl:

Terminal window
curl -X POST \
-H "Authorization: Bearer $VEILUS_API_TOKEN" \
http://127.0.0.1:<port>/v1/profiles/<profile-id>/launch
{ "pid": 56891, "cdpUrl": "http://127.0.0.1:61761" }

A profile you opened from the app also has a cdpUrl while it runs: GET /v1/profiles lists it on each running profile. Over MCP, launch_profile and list_profiles return the same field.

The address changes every time the profile starts. Read it again after each launch.

  • The CDP address has no password. Any program on your machine can use it while the profile runs. It only listens on 127.0.0.1.
  • Use the browser as it is. Work in the profile’s existing window and context. Don’t create a new context with your own user agent, viewport or locale options — those override the profile’s values for that context.
  • Disconnect, don’t close. Ending your script should detach from the browser. The profile stays open until you stop it from the app or with POST /v1/profiles/<profile-id>/stop.
  • A connected tool is still a connected tool. Veilus keeps the profile’s identity; it can’t hide that an automation client is driving the page while one is attached.
import { chromium } from 'playwright';
const browser = await chromium.connectOverCDP(cdpUrl);
const context = browser.contexts()[0];
const page = context.pages()[0] ?? (await context.newPage());
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // detaches; the profile keeps running

Use browser.contexts()[0], not browser.newContext().

import puppeteer from 'puppeteer';
const browser = await puppeteer.connect({ browserURL: cdpUrl, defaultViewport: null });
const [page] = await browser.pages();
await page.goto('https://example.com');
console.log(await page.title());
await browser.disconnect();

Keep defaultViewport: null. Without it, Puppeteer resizes the page to 800×600, and that size stays on the tab after Puppeteer disconnects.

Point the server at the profile instead of letting it start its own browser:

{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["@playwright/mcp", "--cdp-endpoint", "http://127.0.0.1:61761"]
}
}
}

Replace the address with the profile’s current cdpUrl, and restart the MCP client after each profile launch.

agent-browser connect takes the port part of cdpUrl:

Terminal window
agent-browser connect 61761
agent-browser open https://example.com
agent-browser get title
from browser_use import Browser
browser = Browser(cdp_url=cdp_url, keep_alive=True, headless=False, no_viewport=True)

Pass it to your agent as Agent(task=..., llm=..., browser=browser). keep_alive=True leaves the profile running when the agent finishes. headless=False and no_viewport=True stop browser-use from resizing the profile’s tabs when your script runs on a machine without a display. Full example: browser_use_example.py.

The example uses claude-haiku-5-5. browser-use 0.13.11 asks the model for a forced tool choice, and claude-sonnet-5-5 and claude-opus-5-5 reject that request.

import asyncio
from crawl4ai import AsyncWebCrawler, BrowserConfig
async def main():
config = BrowserConfig(
browser_mode="cdp", cdp_url=cdp_url, use_managed_browser=True, user_agent=""
)
config.headers.pop("sec-ch-ua", None)
async with AsyncWebCrawler(config=config) as crawler:
result = await crawler.arun("https://example.com")
print(result.metadata.get("title"))
asyncio.run(main())

Keep all three settings. Without them, Crawl4AI 0.9.4 opens its own context and sends its own User-Agent and sec-ch-ua headers instead of the profile’s. Full example: crawl4ai_example.py.

import { Stagehand } from '@browserbasehq/stagehand';
import { z } from 'zod';
// Stagehand wants the browser's WebSocket endpoint, not the http:// address.
const { webSocketDebuggerUrl } = await (await fetch(`${cdpUrl}/json/version`)).json();
const stagehand = new Stagehand({
env: 'LOCAL',
localBrowserLaunchOptions: { cdpUrl: webSocketDebuggerUrl },
model: 'anthropic/claude-sonnet-5-5',
});
await stagehand.init();
const page = stagehand.context.pages()[0];
await page.goto('https://example.com');
const { title } = await stagehand.extract('the page title', z.object({ title: z.string() }));
console.log(title);
await stagehand.close({ force: false }); // detaches; the profile keeps running

Use stagehand.context.pages()[0] — the profile’s own tab. extract calls the model, so set ANTHROPIC_API_KEY. Full example: stagehand.mjs.

from langchain_community.agent_toolkits import PlayWrightBrowserToolkit
from playwright.async_api import async_playwright
async with async_playwright() as p:
browser = await p.chromium.connect_over_cdp(cdp_url)
toolkit = PlayWrightBrowserToolkit.from_browser(async_browser=browser)
tools = {t.name: t for t in toolkit.get_tools()}
await tools["navigate_browser"].arun({"url": "https://example.com"})
print(await browser.contexts[0].pages[-1].title())

Calling a tool directly needs no LLM; hand toolkit.get_tools() to your agent when you want one to choose. Leaving the async with block disconnects; the profile keeps running. Full example: langchain_example.py.

from playwright.async_api import async_playwright
from scrapling.parser import Selector
async with async_playwright() as pw:
browser = await pw.chromium.connect_over_cdp(cdp_url)
page = await browser.contexts[0].new_page()
await page.goto("https://example.com")
html = await page.content()
await page.close()
print(Selector(html).css("title::text").get())

Playwright drives the profile’s browser and Scrapling parses the page. Scrapling’s own fetchers open a new browser context, which would not carry the profile’s cookies. Full example: scrapling_example.py.

Crawlee starts its own browser by default. Give it a browser plugin that connects to the profile instead, and a controller that opens pages in the profile’s existing context and never closes it — the full plugin is about 30 lines: crawlee_example.py.

crawler = PlaywrightCrawler(
browser_pool=BrowserPool(plugins=[VeilusPlugin(cdp_url)])
)

The example subclasses Crawlee’s PlaywrightBrowserController; check it again when you upgrade Crawlee.

On October 14, 2026, every example in veilus/sdk/examples ran against a Veilus profile on macOS: it opened a page, read its title and left the profile running. Versions: Playwright 1.64.0, Puppeteer 25.13.0, playwright-mcp 0.0.83, agent-browser 0.39.1, Crawl4AI 0.9.4, LangChain (langchain-community) 0.4.2, Scrapling 0.4.6, Crawlee 1.10.4, browser-use 0.13.11, Stagehand 3.7.3. The current table is examples/TESTED.md.

On October 11, 2026, with Playwright, Puppeteer, playwright-mcp, agent-browser and browser-use, the page also reported the profile’s own user agent, screen size, time zone and language, and filled the browser window — no tool resized it, as long as Puppeteer was given defaultViewport: null. Newer releases of these tools may change their defaults — if a page suddenly reports a smaller size than its window, check the tool’s viewport option first.