Skill: offflinerpsy/dolphin-anty (version 1.0.0)

```skill --- name: dolphin-anty description: Dolphin Anty antidetect browser automation — manage profiles, launch browsers, automate tasks via local API + Playwright. Use when user asks to "open browser profile", "automate browsing", "scrape through Dolphin", "launch antidetect profile", "collect data from sites", "register accounts", "check accounts", "warm up profiles", or any browser automation requiring anti-detection and fingerprint spoofing. This skill connects to the Dolphin Anty desktop app running locally. --- # Dolphin Anty — Antidetect Browser Automation > Full-featured integration between your AI agent and [Dolphin Anty](https://dolphin-anty.com/) antidetect browser. > Manage profiles, launch stealth browsers, scrape data, warm up accounts — all hands-free through Playwright + DevTools Protocol. ## What It Does This skill gives your AI agent the ability to: - **List, create, and delete** browser profiles via Dolphin Anty Cloud API - **Launch profiles** and connect via Playwright over Chrome DevTools Protocol - **Automate browsing** — navigate, screenshot, scrape content, execute custom JS - **Warm up profiles** — visit random popular sites to build organic browsing history - **Human-like behavior** — random delays, scrolling, and natural interaction patterns All automation runs through real Dolphin Anty fingerprinted profiles — undetectable, unique, and isolated per session. ## Architecture ``` Agent (OpenClaw / any LLM agent) -> calls scripts from scripts/ -> Cloud API (dolphin-anty-api.com) — profile management -> Local API (localhost:3001) — browser launch/stop -> Dolphin Anty starts a fingerprinted Chromium instance -> Playwright connects via WebSocket (DevTools Protocol) -> Full browser automation with anti-detection ``` ## Requirements | Dependency | Purpose | |---|---| | **Dolphin Anty** (desktop app) | Must be running locally for browser launch | | **Node.js** (v18+) | Script runtime | | **Playwright** | Browser automation (`npm install -g playwright`) | | **API Token** | From https://dolphin-anty.com/panel -> API section | ## Quick Start ### 1. Install Playwright ```bash npm install -g playwright ``` ### 2. Get your API token 1. Go to **https://dolphin-anty.com/panel** 2. Navigate to **API** section (left sidebar) 3. Click **"Generate token"** — set a name and expiration 4. **Copy the token** (it is shown only once!) ### 3. Set up the token ```bash node scripts/dolphin_setup.js --token <YOUR_API_TOKEN> ``` This saves your token locally and authenticates with the Dolphin Anty Local API. ## Commands ### Profile Management ```bash # List all profiles node scripts/dolphin_profiles.js list # Check connection status node scripts/dolphin_profiles.js status # Create a new profile node scripts/dolphin_profiles.js create --name "My Profile" --proxy "[REDACTED_SECRET]" # Stop a running profile node scripts/dolphin_profiles.js stop --profile-id <ID> # Delete a profile node scripts/dolphin_profiles.js delete --profile-id <ID> ``` ### Browser Automation ```bash node scripts/dolphin_automate.js --profile-id <ID> --task <TASK> [--url <URL>] [--code <JS>] ``` | Task | Description | Requires `--url` | |---|---|---| | `screenshot` | Navigate to URL, take a full-page screenshot | Yes | | `scrape` | Extract titles, headings, links, images, text | Yes | | `navigate` | Open URL in the profile (stays open for manual use) | Yes | | `warmup` | Visit 3-5 random popular sites with human-like scrolling | No | | `custom` | Execute arbitrary JavaScript code on the page | Optional | ## Usage Examples ```bash # Take a screenshot of a website node scripts/dolphin_automate.js --profile-id 123456 --task screenshot --url "https://example.com" # Scrape product data node scripts/dolphin_automate.js --profile-id 123456 --task scrape --url "https://shop.com/products" # Warm up a profile with organic browsing node scripts/dolphin_automate.js --profile-id 123456 --task warmup # Run custom JavaScript node scripts/dolphin_automate.js --profile-id 123456 --task custom --url "https://site.com" --code "document.title" ``` ## How the API Works Dolphin Anty exposes two APIs: | API | Base URL | Purpose | |---|---|---| | **Cloud API** | `https://dolphin-anty-api.com` | Profile CRUD, fingerprints, proxies | | **Local API** | `http://localhost:3001` | Browser start/stop, DevTools connection | - **Cloud API** requires a Bearer token in the `Authorization` header - **Local API** requires token registration via `POST /v1.0/auth/login-with-token` - Both are handled automatically by the setup script ## Agent Strategy ### When to use this skill | User Request | Action | |---|---| | "Open a browser" / "Browse this site" | Launch profile -> navigate | | "Scrape data from X" | Launch profile -> scrape -> stop | | "Register accounts" | One profile per account -> automate registration | | "Warm up profiles" | Run warmup task on each profile | | "Check account status" | Launch profile -> navigate to site -> check | | "Take a screenshot of X" | Launch profile -> screenshot -> stop | ### Workflow 1. Run `list` to see available profiles 2. Pick a profile or `create` a new one 3. Run automation via `dolphin_automate.js` 4. When done, `stop` the profile to sync data back to cloud ### Important Notes - Dolphin Anty desktop app **must be running** before using scripts - One profile = one browser = one unique digital identity - Don't launch too many profiles simultaneously (depends on available RAM) - Random delays between actions are built into all scripts automatically - After `stop`, profile data (cookies, localStorage) syncs back to Dolphin cloud --- ## Author & Custom Development **Built by [@Enigma_Vista](https://t.me/Enigma_Vista)** Need a custom solution? I offer professional development services at affordable rates: - **Web Applications** — full-stack development, dashboards, admin panels - **AI Agents & Automation** — intelligent agents, workflow automation, data pipelines - **Telegram Bots & Mini Apps** — bots, inline apps, payment integrations - **Chatbots & LLM Integration** — GPT/Claude-powered assistants for your business - **Web Scraping & Data Collection** — antidetect setups, stealth scrapers - **Product Catalogs & E-commerce** — storefronts, inventory systems, order management **Telegram: [@Enigma_Vista](https://t.me/Enigma_Vista)** — fast response, fair prices. ```

path: scripts/dolphin_automate.js
content: /**
 * Dolphin Anty Browser Automation
 * Запуск профиля + подключение через Playwright для автоматизации
 * 
 * Использование:
 *   node dolphin_automate.js --profile-id <ID> --task screenshot --url "https://..."
 *   node dolphin_automate.js --profile-id <ID> --task scrape --url "https://..."
 *   node dolphin_automate.js --profile-id <ID> --task navigate --url "https://..."
 *   node dolphin_automate.js --profile-id <ID> --task warmup
 *   node dolphin_automate.js --profile-id <ID> --task custom --url "https://..." --code "document.title"
 */

const http = require('http');
const fs = require('fs');
const pathModule = require('path');
const { argv } = process;

const API_BASE = 'http://localhost:3001/v1.0';
const TOKEN_FILE = pathModule.join(__dirname, '..', '.token');

function getToken() {
  if (fs.existsSync(TOKEN_FILE)) return fs.readFileSync(TOKEN_FILE, 'utf8').trim();
  return null;
}

// ─── HTTP Helper ─────────────────────────────────────────────────

function apiRequest(method, path) {
  return new Promise((resolve, reject) => {
    const url = new URL(API_BASE + path);
    const [REDACTED_SECRET]);
    const headers = {};
    if (token) headers['Authorization'] = 'Bearer ' + token;
    
    const options = {
      hostname: url.hostname,
      port: url.port,
      path: url.pathname + url.search,
      method: method,
      headers: headers,
      timeout: 30000,
    };

    const req = http.request(options, (res) => {
      let data = '';
      res.on('data', (chunk) => (data += chunk));
      res.on('end', () => {
        try {
          resolve({ status: res.statusCode, data: JSON.parse(data) });
        } catch {
          resolve({ status: res.statusCode, data: data });
        }
      });
    });

    req.on('error', (err) => {
      if (err.code === 'ECONNREFUSED') {
        reject(new Error('Dolphin Anty не запущен! Откройте приложение.'));
      } else {
        reject(err);
      }
    });

    req.on('timeout', () => {
      req.destroy();
      reject(new Error('Таймаут подключения к Dolphin Anty'));
    });

    req.end();
  });
}

// ─── Случайная задержка (имитация человека) ─────────────────────

function sleep(ms) {
  return new Promise((r) => setTimeout(r, ms));
}

function randomDelay(minMs = 500, maxMs = 2000) {
  return sleep(Math.floor(Math.random() * (maxMs - minMs)) + minMs);
}

// ─── Запуск профиля ─────────────────────────────────────────────

async function startProfile(profileId) {
  console.log(`🚀 Запускаю профиль ${profileId}...`);
  const res = await apiRequest('GET', `/browser_profiles/${profileId}/start?automation=1`);

  if (!res.data || !res.data.automation) {
    throw new Error(
      `Не удалось запустить профиль. Ответ: ${JSON.stringify(res.data)}`
    );
  }

  const { port, wsEndpoint } = res.data.automation;
  console.log(`✅ Профиль запущен. Port: ${port}, wsEndpoint: ${wsEndpoint}`);
  return { port, wsEndpoint };
}

// ─── Остановка профиля ──────────────────────────────────────────

async function stopProfile(profileId) {
  console.log(`⏹️ Останавливаю профиль ${profileId}...`);
  await apiRequest('GET', `/browser_profiles/${profileId}/stop`);
  console.log(`✅ Профиль остановлен.`);
}

// ─── Подключение Playwright ─────────────────────────────────────

async function connectBrowser(port, wsEndpoint) {
  let playwright;
  try {
    playwright = require('playwright');
  } catch {
    // Fallback: try global node_modules
    try {
      const globalPath = require('child_process').execSync('npm root -g', { encoding: 'utf8' }).trim();
      playwright = require(require('path').join(globalPath, 'playwright'));
    } catch {
      console.error('❌ Playwright не установлен. Выполните: npm install -g playwright');
      process.exit(1);
    }
  }

  const wsUrl = `ws://127.0.0.1:${port}${wsEndpoint}`;
  console.log(`🔗 Подключаюсь к браузеру: ${wsUrl}`);
  
  const browser = await playwright.chromium.connectOverCDP(wsUrl);
  const contexts = browser.contexts();
  const context = contexts[0] || await browser.newContext();
  
  return { browser, context };
}

// ─── Задачи ─────────────────────────────────────────────────────

async function taskScreenshot(context, url) {
  const page = await context.newPage();
  console.log(`📸 Открываю ${url}...`);
  await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
  await randomDelay(1000, 3000);
  
  const filename = `screenshot_${Date.now()}.png`;
  await page.screenshot({ path: filename, fullPage: true });
  console.log(`✅ Скриншот сохранён: ${filename}`);
  await page.close();
}

async function taskScrape(context, url) {
  const page = await context.newPage();
  console.log(`🔍 Собираю данные с ${url}...`);
  await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
  await randomDelay(1000, 3000);
  
  const data = await page.evaluate(() => {
    const title = document.title;
    const metaDesc = document.querySelector('meta[name="description"]')?.content || '';
    const h1s = [...document.querySelectorAll('h1')].map(el => el.textContent.trim());
    const h2s = [...document.querySelectorAll('h2')].map(el => el.textContent.trim());
    const links = [...document.querySelectorAll('a[href]')]
      .map(a => ({ text: a.textContent.trim().substring(0, 80), href: a.href }))
      .filter(l => l.text && l.href.startsWith('http'))
      .slice(0, 50);
    const paragraphs = [...document.querySelectorAll('p')]
      .map(p => p.textContent.trim())
      .filter(t => t.length > 20)
      .slice(0, 20);
    const images = [...document.querySelectorAll('img[src]')]
      .map(img => ({ src: img.src, alt: img.alt || '' }))
      .slice(0, 20);
    
    return { title, metaDesc, h1s, h2s, links, paragraphs, images };
  });

  console.log(`\n📄 Результаты скрапинга:\n`);
  console.log(`Заголовок: ${data.title}`);
  console.log(`Описание: ${data.metaDesc}`);
  if (data.h1s.length) console.log(`H1: ${data.h1s.join(', ')}`);
  if (data.h2s.length) console.log(`H2: ${data.h2s.join(' | ')}`);
  console.log(`Ссылок: ${data.links.length}`);
  console.log(`Параграфов: ${data.paragraphs.length}`);
  console.log(`Изображений: ${data.images.length}`);
  
  // Выводим JSON для парсинга агентом
  const outputFile = `scrape_${Date.now()}.json`;
  require('fs').writeFileSync(outputFile, JSON.stringify(data, null, 2), 'utf8');
  console.log(`\n💾 Полные данные сохранены: ${outputFile}`);
  
  await page.close();
}

async function taskNavigate(context, url) {
  const page = await context.newPage();
  console.log(`🌐 Открываю ${url}...`);
  await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
  await randomDelay(1000, 2000);
  
  const title = await page.title();
  console.log(`✅ Страница открыта: "${title}"`);
  console.log(`   URL: ${page.url()}`);
  // Не закрываем страницу — пользователь может продолжить вручную
}

async function taskWarmup(context) {
  const sites = [
    'https://www.google.com',
    'https://www.youtube.com',
    'https://www.wikipedia.org',
    'https://www.reddit.com',
    'https://news.ycombinator.com',
    'https://www.amazon.com',
    'https://www.github.com',
    'https://stackoverflow.com',
    'https://www.twitch.tv',
    'https://www.medium.com',
  ];

  // Случайно выбираем 3-5 сайтов
  const count = Math.floor(Math.random() * 3) + 3;
  const shuffled = sites.sort(() => Math.random() - 0.5).slice(0, count);

  console.log(`🔥 Прогрев профиля: посещу ${count} сайтов...\n`);

  for (const site of shuffled) {
    const page = await context.newPage();
    try {
      console.log(`  → ${site}`);
      await page.goto(site, { waitUntil: 'domcontentloaded', timeout: 20000 });
      
      // Имитация чтения — скроллим
      await randomDelay(2000, 5000);
      await page.evaluate(() => window.scrollBy(0, Math.random() * 500 + 200));
      await randomDelay(1000, 3000);
      await page.evaluate(() => window.scrollBy(0, Math.random() * 300 + 100));
      await randomDelay(1000, 2000);
      
    } catch (err) {
      console.log(`  ⚠️ ${site}: ${err.message.substring(0, 60)}`);
    }
    await page.close();
    await randomDelay(1000, 3000); // Пауза между сайтами
  }

  console.log(`\n✅ Прогрев завершён. Посещено ${count} сайтов.`);
}

async function taskCustom(context, url, code) {
  if (!code) {
    console.error('❌ Для task=custom нужен --code "javascript code"');
    process.exit(1);
  }
  
  const page = await context.newPage();
  if (url) {
    console.log(`🌐 Открываю ${url}...`);
    await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
    await randomDelay(500, 1500);
  }
  
  console.log(`⚡ Выполняю код...`);
  const result = await page.evaluate(code);
  console.log(`✅ Результат:`, result);
  await page.close();
}

// ─── CLI Parser ─────────────────────────────────────────────────

function getArg(name) {
  const idx = argv.indexOf(name);
  if (idx !== -1 && idx + 1 < argv.length) return argv[idx + 1];
  return null;
}

// ─── Main ────────────────────────────────────────────────────────

async function main() {
  const profileId = getArg('--profile-id');
  const task = getArg('--task');
  const url = getArg('--url');
  const code = getArg('--code');

  if (!profileId || !task) {
    console.log(`
Dolphin Anty Browser Automation

Использование:
  node dolphin_automate.js --profile-id <ID> --task <TASK> [--url <URL>] [--code <JS>]

Задачи:
  screenshot  — скриншот страницы
  scrape      — сбор данных (заголовки, ссылки, текст, картинки)
  navigate    — открыть URL в профиле
  warmup      — прогрев (посещение случайных сайтов)
  custom      — выполнить произвольный JS-код
    `);
    process.exit(1);
  }

  let automation;
  try {
    automation = await startProfile(profileId);
  } catch (err) {
    console.error('❌', err.message);
    process.exit(1);
  }

  const { port, wsEndpoint } = automation;
  
  // Даём браузеру полностью загрузиться
  await sleep(2000);

  let browser, context;
  try {
    ({ browser, context } = await connectBrowser(port, wsEndpoint));
  } catch (err) {
    console.error('❌ Не удалось подключиться к браузеру:', err.message);
    await stopProfile(profileId);
    process.exit(1);
  }

  try {
    switch (task) {
      case 'screenshot':
        if (!url) { console.error('❌ Нужен --url'); process.exit(1); }
        await taskScreenshot(context, url);
        break;
      case 'scrape':
        if (!url) { console.error('❌ Нужен --url'); process.exit(1); }
        await taskScrape(context, url);
        break;
      case 'navigate':
        if (!url) { console.error('❌ Нужен --url'); process.exit(1); }
        await taskNavigate(context, url);
        break;
      case 'warmup':
        await taskWarmup(context);
        break;
      case 'custom':
        await taskCustom(context, url, code);
        break;
      default:
        console.error(`❌ Неизвестная задача: ${task}`);
    }
  } catch (err) {
    console.error('❌ Ошибка выполнения:', err.message);
  }

  // Для navigate — не отключаемся, пользователь работает
  if (task !== 'navigate') {
    try {
      await browser.close();
    } catch { /* игнорируем */ }
    await stopProfile(profileId);
  }
}

main();


path: scripts/dolphin_profiles.js
content: /**
 * Dolphin Anty Profile Manager
 * Управление профилями через локальный API (localhost:3001)
 * 
 * Использование:
 *   node dolphin_profiles.js list                                  — список профилей
 *   node dolphin_profiles.js stop --profile-id <ID>                — остановить профиль
 *   node dolphin_profiles.js create --name "Name" [--proxy ...]    — создать профиль
 *   node dolphin_profiles.js delete --profile-id <ID>              — удалить профиль
 *   node dolphin_profiles.js status                                — проверить что Dolphin запущен
 */

const http = require('http');
const https = require('https');
const fs = require('fs');
const path = require('path');
const { argv } = process;

const LOCAL_API = 'http://localhost:3001';
const CLOUD_API = 'https://dolphin-anty-api.com';
const TOKEN_FILE = path.join(__dirname, '..', '.token');

// ─── Token Management ──────────────────────────────────────────

function getToken() {
  if (fs.existsSync(TOKEN_FILE)) {
    return fs.readFileSync(TOKEN_FILE, 'utf8').trim();
  }
  return null;
}

// ─── HTTP Helper ─────────────────────────────────────────────────

function apiRequest(method, apiPath, body = null, useCloud = false) {
  return new Promise((resolve, reject) => {
    const base = useCloud ? CLOUD_API : LOCAL_API;
    const fullPath = useCloud ? apiPath : '/v1.0' + apiPath;
    const url = new URL(base + fullPath);
    const isHttps = url.protocol === 'https:';
    const [REDACTED_SECRET]);
    
    const headers = { 'Content-Type': 'application/json' };
    if (token) headers['Authorization'] = 'Bearer ' + token;
    
    const options = {
      hostname: url.hostname,
      port: url.port || (isHttps ? 443 : 80),
      path: url.pathname + url.search,
      method: method,
      headers: headers,
      timeout: 15000,
    };

    const transport = isHttps ? https : http;
    const req = transport.request(options, (res) => {
      let data = '';
      res.on('data', (chunk) => (data += chunk));
      res.on('end', () => {
        try {
          const parsed = JSON.parse(data);
          if (parsed.success === false && parsed.error === 'invalid session token') {
            reject(new Error(
              'Токен не настроен! Выполните:\n' +
              '  node dolphin_setup.js --token <YOUR_TOKEN>\n\n' +
              'Токен получите на https://dolphin-anty.com/panel → API'
            ));
            return;
          }
          resolve({ status: res.statusCode, data: parsed });
        } catch {
          resolve({ status: res.statusCode, data: data });
        }
      });
    });

    req.on('error', (err) => {
      if (err.code === 'ECONNREFUSED') {
        reject(new Error(
          'Dolphin Anty не запущен! Откройте приложение Dolphin Anty и попробуйте снова.'
        ));
      } else {
        reject(err);
      }
    });

    req.on('timeout', () => {
      req.destroy();
      reject(new Error('Таймаут запроса к Dolphin Anty API'));
    });

    if (body) {
      req.write(JSON.stringify(body));
    }
    req.end();
  });
}

// ─── Commands ─────────────────────────────────────────────────

async function listProfiles() {
  try {
    // Try cloud API which returns profiles list
    const res = await apiRequest('GET', '/browser_profiles?limit=50', null, true);
    const profiles = res.data.data || [];
    
    if (profiles.length === 0) {
      console.log('Профилей не найдено.');
      return;
    }

    console.log(`\n📋 Найдено профилей: ${profiles.length}\n`);
    console.log('ID'.padEnd(12) + 'Имя'.padEnd(30) + 'Статус'.padEnd(12) + 'Прокси');
    console.log('─'.repeat(80));
    
    for (const p of profiles) {
      const id = String(p.id).padEnd(12);
      const name = (p.name || 'Без имени').substring(0, 28).padEnd(30);
      const status = (p.status?.toLowerCase() === 'running' ? '🟢 Running' : '⚪ Stopped').padEnd(12);
      const proxy = p.proxy ? `${p.proxy.type}://${p.proxy.host}:${p.proxy.port}` : 'нет';
      console.log(`${id}${name}${status}${proxy}`);
    }
    console.log();
  } catch (err) {
    console.error('❌', err.message);
    process.exit(1);
  }
}

async function stopProfile(profileId) {
  try {
    console.log(`Останавливаю профиль ${profileId}...`);
    const res = await apiRequest('GET', `/browser_profiles/${profileId}/stop`);
    
    if (res.status === 200) {
      console.log(`✅ Профиль ${profileId} остановлен.`);
    } else {
      console.log(`⚠️ Ответ: ${JSON.stringify(res.data)}`);
    }
  } catch (err) {
    console.error('❌', err.message);
    process.exit(1);
  }
}

async function createProfile(name, proxy) {
  const body = {
    name: name,
    platform: 'windows',
    browserType: 'anty',
    mainWebsite: '',
    useragent: { mode: 'manual' },
    webrtc: { mode: 'altered' },
    canvas: { mode: 'real' },
    webgl: { mode: 'real' },
    webglInfo: { mode: 'manual' },
    timezone: { mode: 'auto' },
    locale: { mode: 'auto' },
    geolocation: { mode: 'auto' },
    cpu: { mode: 'manual', value: 4 },
    memory: { mode: 'manual', value: 8 },
    doNotTrack: false,
  };

  if (proxy) {
    // Формат: type://user:pass@host:port  или  type://host:port
    const proxyMatch = proxy.match(/^(\w+):\/\/(?:(.+):(.+)@)?(.+):(\d+)$/);
    if (proxyMatch) {
      body.proxy = {
        type: proxyMatch[1],
        host: proxyMatch[4],
        port: proxyMatch[5],
        login: proxyMatch[2] || '',
        [REDACTED_SECRET] || '',
      };
    }
  }

  try {
    console.log(`Создаю профиль "${name}"...`);
    const res = await apiRequest('POST', '/browser_profiles', body, true);
    
    if (res.data && res.data.browserProfileId) {
      console.log(`✅ Профиль создан! ID: ${res.data.browserProfileId}`);
    } else {
      console.log(`Ответ API:`, JSON.stringify(res.data, null, 2));
    }
  } catch (err) {
    console.error('❌', err.message);
    process.exit(1);
  }
}

async function deleteProfile(profileId) {
  try {
    console.log(`Удаляю профиль ${profileId}...`);
    const res = await apiRequest('DELETE', `/browser_profiles/${profileId}`, null, true);
    
    if (res.status === 200) {
      console.log(`✅ Профиль ${profileId} удалён.`);
    } else {
      console.log(`⚠️ Ответ: ${JSON.stringify(res.data)}`);
    }
  } catch (err) {
    console.error('❌', err.message);
    process.exit(1);
  }
}

async function checkStatus() {
  try {
    const [REDACTED_SECRET]);
    if (!token) {
      console.log('⚠️ Токен не настроен. Выполните: node dolphin_setup.js --token <YOUR_TOKEN>');
      console.log('   Токен получите на https://dolphin-anty.com/panel → раздел API');
      return;
    }
    // Auth the local API
    await apiRequest('POST', '/auth/login-with-token', { token: token });
    console.log('✅ Токен сохранён в локальном API.');
    
    const res = await apiRequest('GET', '/browser_profiles?limit=1', null, true);
    console.log('✅ Dolphin Anty API доступен.');
    const total = res.data.total || 'неизвестно';
    console.log(`   Всего профилей: ${total}`);
  } catch (err) {
    console.error('❌ Dolphin Anty НЕ доступен:', err.message);
    process.exit(1);
  }
}

// ─── CLI Parser ─────────────────────────────────────────────────

function getArg(name) {
  const idx = argv.indexOf(name);
  if (idx !== -1 && idx + 1 < argv.length) return argv[idx + 1];
  return null;
}

async function main() {
  const command = argv[2];

  switch (command) {
    case 'list':
      await listProfiles();
      break;
    case 'stop':
      const stopId = getArg('--profile-id');
      if (!stopId) { console.error('Нужен --profile-id <ID>'); process.exit(1); }
      await stopProfile(stopId);
      break;
    case 'create':
      const name = getArg('--name');
      if (!name) { console.error('Нужен --name "Имя"'); process.exit(1); }
      const proxy = getArg('--proxy');
      await createProfile(name, proxy);
      break;
    case 'delete':
      const delId = getArg('--profile-id');
      if (!delId) { console.error('Нужен --profile-id <ID>'); process.exit(1); }
      await deleteProfile(delId);
      break;
    case 'status':
      await checkStatus();
      break;
    default:
      console.log(`
Dolphin Anty Profile Manager

Использование:
  node dolphin_profiles.js list                                    — список профилей
  node dolphin_profiles.js stop --profile-id <ID>                  — остановить профиль  
  node dolphin_profiles.js create --name "Name" [--proxy ...]      — создать профиль
  node dolphin_profiles.js delete --profile-id <ID>                — удалить профиль
  node dolphin_profiles.js status                                  — проверить Dolphin Anty
      `);
  }
}

main();


path: scripts/dolphin_setup.js
content: /**
 * Dolphin Anty — настройка API-токена
 * 
 * Использование:
 *   node dolphin_setup.js --token <API_TOKEN>
 * 
 * Где взять токен:
 *   1. Откройте https://dolphin-anty.com/panel
 *   2. Войдите в аккаунт
 *   3. Перейдите в раздел API (левое меню)
 *   4. Нажмите "Generate token", задайте имя и срок действия
 *   5. Скопируйте токен (показывается ОДИН РАЗ!)
 *   6. Выполните: node dolphin_setup.js --token <полученный_токен>
 */

const http = require('http');
const https = require('https');
const fs = require('fs');
const path = require('path');

const TOKEN_FILE = path.join(__dirname, '..', '.token');
const { argv } = process;

function getArg(name) {
  const idx = argv.indexOf(name);
  if (idx !== -1 && idx + 1 < argv.length) return argv[idx + 1];
  return null;
}

function apiCall(method, url, body, headers) {
  return new Promise((resolve, reject) => {
    const u = new URL(url);
    const isHttps = u.protocol === 'https:';
    const transport = isHttps ? https : http;
    
    const opts = {
      hostname: u.hostname,
      port: u.port || (isHttps ? 443 : 80),
      path: u.pathname + u.search,
      method: method,
      headers: headers || {},
      timeout: 15000,
    };
    
    const req = transport.request(opts, (res) => {
      let data = '';
      res.on('data', (c) => (data += c));
      res.on('end', () => {
        try { resolve({ status: res.statusCode, data: JSON.parse(data) }); }
        catch { resolve({ status: res.statusCode, data: data }); }
      });
    });
    
    req.on('error', (err) => reject(err));
    req.on('timeout', () => { req.destroy(); reject(new Error('Таймаут')); });
    if (body) req.write(JSON.stringify(body));
    req.end();
  });
}

async function main() {
  const [REDACTED_SECRET]'--token');
  
  if (!token) {
    console.log(`
╔════════════════════════════════════════════════════════╗
║          Настройка Dolphin Anty API-токена             ║
╚════════════════════════════════════════════════════════╝

Использование:
  node dolphin_setup.js --token <API_TOKEN>

Где взять токен:
  1. Откройте https://dolphin-anty.com/panel
  2. Войдите в аккаунт
  3. Перейдите в раздел «API» (левое меню)
  4. Нажмите «Generate token»
  5. Скопируйте токен (показывается ОДИН РАЗ!)
  6. Вставьте сюда: node dolphin_setup.js --token ваш_токен
    `);
    process.exit(1);
  }

  console.log('🔑 Проверяю токен...');

  // 1. Verify token against cloud API
  try {
    const res = await apiCall('GET', 'https://dolphin-anty-api.com/browser_profiles?limit=1', null, {
      'Authorization': 'Bearer ' + token,
    });
    
    if (res.data.success === false || res.status === 401) {
      console.error('❌ Токен невалидный! Проверьте, правильно ли скопировали.');
      console.error('   Ответ API:', JSON.stringify(res.data));
      process.exit(1);
    }
    
    const total = res.data.total || res.data.data?.length || 0;
    console.log(`✅ Токен валидный! Профилей в аккаунте: ${total}`);
  } catch (err) {
    console.error('⚠️ Не удалось проверить токен онлайн:', err.message);
    console.log('   Сохраняю токен и пробую локально...');
  }

  // 2. Save token to file
  fs.writeFileSync(TOKEN_FILE, token, 'utf8');
  console.log(`💾 Токен сохранён: ${TOKEN_FILE}`);

  // 3. Register token with local API
  try {
    const res = await apiCall('POST', 'http://localhost:3001/v1.0/auth/login-with-token', 
      { token: token },
      { 'Content-Type': 'application/json' }
    );
    
    if (res.data.success !== false) {
      console.log('✅ Локальный API авторизован!');
    } else {
      console.log('⚠️ Локальный API ответ:', JSON.stringify(res.data));
      console.log('   Убедитесь, что Dolphin Anty запущен.');
    }
  } catch (err) {
    console.log('⚠️ Dolphin Anty не запущен (localhost:3001 недоступен).');
    console.log('   Токен сохранён — при следующем запуске скриптов он будет использован.');
  }

  console.log('\n🎉 Готово! Теперь можно использовать:');
  console.log('   node dolphin_profiles.js list');
  console.log('   node dolphin_profiles.js status');
  console.log('   node dolphin_automate.js --profile-id <ID> --task warmup');
}

main();


path: _meta.json
content: slug: dolphin-anty
version: 1.0.0
local: yes

Security scan verdicts — ClawScan: suspicious | static analysis:  | VirusTotal: suspicious | SkillSpector: suspicious (HIGH)