com um clique
create-connector
// Build, test, validate, and contribute a new data connector for any web platform. Use when: the user wants to create a connector, a requested platform has no connector yet, or a connector needs updating.
// Build, test, validate, and contribute a new data connector for any web platform. Use when: the user wants to create a connector, a requested platform has no connector yet, or a connector needs updating.
Connect personal data from any web platform using browser automation. Use when: (1) user wants to connect a data source like ChatGPT, Instagram, Spotify, or any platform, (2) user says "connect my [platform]", (3) user wants to generate or update their profile from connected data. Also triggers on: "create a connector for [platform]".
Generate and execute the next agent prompt from connected personal data. Use when: the user says "what should I work on", "vana next", "what's next", or when the agent has no pending task. Also triggers after completing a task when autopilot is enabled.
| name | create-connector |
| description | Build, test, validate, and contribute a new data connector for any web platform. Use when: the user wants to create a connector, a requested platform has no connector yet, or a connector needs updating. |
Build a data connector for a platform that isn't in the registry yet.
reference/PAGE-API.md -- full page object APIreference/PATTERNS.md -- data extraction approaches and code examplesAll node scripts/... commands refer to skills/vana-connect/scripts/ in the data-connectors repo. Use the vana CLI to exercise connectors; only fall back to raw scripts when debugging connector internals.
Scripts are plain JavaScript (CJS), no imports, no require. The runner injects a page object. The script body must be an async IIFE preceded by a blank line (the runner matches \n(async).
(async () => {
// connector logic here
await page.setData("result", { "platform.scope": data });
})();
| Platform | Strategy | Rung | Notes |
|---|---|---|---|
| In-page fetch | 1 | OAuth-like endpoints, JSON responses | |
| Twitter/X | Network capture | 2 | GraphQL via captureNetwork |
| In-page fetch | 1 | Cookie auth, pagination | |
| In-page fetch | 1 | Voyager API, CSRF token required | |
| GitHub | DOM extraction | 3 | Server-rendered, no client API |
| Spotify | In-page fetch | 1 | Well-documented public API |
Look at existing connectors in ~/.vana/connectors/ for working examples.
Map the platform's login flow, data APIs, and auth mechanism before writing code.
Navigate to the platform's login page and take a screenshot before writing any login code. List every login option visible on the page (email, Google, Apple, SSO, etc.) and ask the user which one they use. Your training data about a platform's auth flow may be outdated.
"<platform> API endpoints", "<platform> graphql endpoint""<platform> internal API", "<platform> developer API""<platform> data export", "<platform> GDPR data download""<platform> scraper github" -- open-source scrapers reveal known API patternsinput[name="..."], input[type="password"], button[type="submit"]. Note multi-step flows.connectSelector in metadata.platform.scope key (e.g. reddit.profile)Pick the approach with the best user experience. See reference/PATTERNS.md for details and code examples. Max 2 attempts per approach before moving to the next.
node scripts/scaffold.cjs <platform> [company]
Two credential sources: process.env (automated runs) and page.requestInput() (interactive). Try env first, fall back to requestInput. If the platform has multiple login options (discovered via screenshot in Step 1), include a method field listing the options you observed:
let username = process.env.USER_LOGIN_PLATFORMNAME || "";
let password = process.env.USER_PASSWORD_PLATFORMNAME || "";
if (!username || !password) {
const creds = await page.requestInput({
message: "Enter your Platform credentials.",
schema: {
type: "object",
properties: {
method: {
type: "string",
title: "Login method",
description: "List the options you found on the login page",
},
username: { type: "string", title: "Email or username" },
password: { type: "string", title: "Password" },
},
required: ["username", "password"],
},
});
username = creds.username;
password = creds.password;
// Use creds.method to route to the right login flow
}
const loginStr = JSON.stringify(username);
const passStr = JSON.stringify(password);
await page.goto("https://platform.com/login");
await page.sleep(2000);
await page.evaluate(`
(() => {
const u = document.querySelector('input[name="username"], input[type="email"]');
const p = document.querySelector('input[type="password"]');
if (u) { u.focus(); u.value = ${loginStr}; u.dispatchEvent(new Event('input', {bubbles:true})); }
if (p) { p.focus(); p.value = ${passStr}; p.dispatchEvent(new Event('input', {bubbles:true})); }
})()
`);
await page.sleep(500);
await page.evaluate(`document.querySelector('button[type="submit"]')?.click()`);
await page.sleep(3000);
Adaptations:
.value =: use the native setter pattern:
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype, 'value'
).set;
nativeInputValueSetter.call(input, ${loginStr});
input.dispatchEvent(new Event('input', { bubbles: true }));
page.requestInput() to ask for the code.page.evaluate() takes a string, not a function. Pass variables via JSON.stringify().page.sleep(300-1000) between requests.platform.scope format (e.g. spotify.playlists).exportSummary: { count, label, details } in the result.page.goto(url) Navigate
page.evaluate(jsString) Run JS in browser, return result
page.sleep(ms) Wait
page.requestInput({ message, schema }) Ask user for data (credentials, 2FA)
page.setData(key, value) 'result' for data, 'error' for failures
page.setProgress({ phase, message }) Progress reporting
page.closeBrowser() Close browser, extract cookies
page.httpFetch(url, options?) Node.js HTTP (auto-injects cookies)
page.captureNetwork({ key, urlPattern }) Intercept network requests
page.getCapturedResponse(key) Retrieve captured response
page.screenshot() Base64 JPEG screenshot
Full API: reference/PAGE-API.md
Run the connector and validate in one step:
node scripts/validate.cjs <company>/<name>-playwright.js && \
vana connect <platform> && \
node scripts/validate.cjs <company>/<name>-playwright.js --check-result ~/.vana/last-result.json
The validator checks structure, output quality, debug code, data cleanliness, schema descriptions, and login method diversity. Fix all reported issues and re-run.
If an extraction approach fails after 2 attempts, move to the next rung (see reference/PATTERNS.md). Use page.screenshot() to see what the browser shows.
Schemas are an API contract — app developers build against them.
node scripts/generate-schemas.cjs ~/.vana/last-result.json <platform> [output-dir]
description to every field and format hints where applicable (date-time, uri, email). The validator checks description coverage.required only if guaranteed for all users. Use additionalProperties: true.description — not "GitHub profile data" but "GitHub user profile including bio, follower counts, and repository statistics."Before (from generate-schemas.cjs):
{ "type": "string" }
After (enriched):
{
"type": "string",
"format": "date-time",
"description": "When the issue was created (ISO 8601)"
}
node scripts/register.cjs <company>/<name>-playwright.js
node scripts/validate.cjs <company>/<name>-playwright.js --contribute
The validator runs all checks including secret scanning before creating a PR. All checks must pass — the validator is the quality gate.