Token导航 LogoToken导航TokenDH.com
研究检索敏感数据clawhub未标认证来源可访问clear审计提醒

web-service-onboardingWeb 服务入门

Agent Skill

web-service-onboarding 用于处理浏览器自动化、网页检查和页面信息提取,适合在 OpenClaw 中需要让 Agent 打开页面、读取网页或验证前端流程时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

3,222

周安装

137

GitHub Stars

公开资料未说明

下载量

1,129
OpenClaw

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

MIT-0

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:web-service-onboarding(Web 服务入门)
来源仓库:https://github.com/nissan/web-service-onboarding
安装命令:
openclaw skills install web-service-onboarding
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 OpenClaw 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

ClawHubOpenClaw
openclaw skills install web-service-onboarding

简介

用于处理浏览器自动化和网页信息提取,适合在OpenClaw中验证前端流程。

  • 支持外部Web服务自主注册,包括电子邮件验证和API密钥安全存储。
  • 通过clawhub安装,结合来源仓库和原始README核验具体用法。
  • 安装前需确认权限范围、维护状态及是否触发联网或文件读写。
  • web-service-onboarding 属于研究检索类 Skill,可作为该场景下的辅助能力补充。

SKILL.md

name
web-service-onboarding
version
1.0.2
description
Autonomous signup for external web services — browser automation, email verification, API key generation and secure storage in 1Password. Use when asked to create an account on any external service. Covers email-link verification, WebAuthn/passkey flows, OTP flows, and programmatic API key bootstrapping. Turnkey, Vercel, Railway, Supabase, etc.
author
nissan
tags
metadata
openclaw
emoji
🌐
network
outbound
true
reason
Skill guides autonomous signup flows on external services (Vercel, Railway, Supabase, Turnkey, etc). All browser interactions use the agent's own signup session. No third-party data is intercepted or exfiltrated.
security_notes
This skill automates the agent's own account creation on external services — not credential theft or session hijacking of other users. WebAuthn code snippets show how to preserve the agent's own passkey credential across browser sessions (export before close, re-import on next run). All credentials generated belong to the agent's own accounts and are stored in 1Password under the agent's own vault. The words 'credential', 'session', 'cookie', and 'auth' appear only in the context of managing the agent's own authenticated session during signup.

Web Service Onboarding Skill

The Core Pattern

Send signup email → Verify email → Complete registration → 
Generate API keys → Store securely in 1Password → Wire .env

Do this in a single unbroken browser session. Never close the browser between steps.


Critical Rules (Learned the Hard Way — Turnkey, 2026-03-25)

1. One session, no gaps

  • Complete signup AND API key creation AND secret storage in the same browser session
  • Close the browser only after credentials are saved to 1Password
  • If the browser closes before credentials are extracted, you've lost access — the passkey/session is gone

2. Email alias trap

  • Proton Mail (and many providers) treat user+alias@domain.com as the same user
  • If the service already has an account for user@domain.com, the alias will route to that existing account
  • Always check whether the service resolves aliases before using them for a fresh account
  • Use a completely different email (different domain, different provider) for a truly separate account

3. WebAuthn virtual authenticator is ephemeral

  • Playwright's WebAuthn.addVirtualAuthenticator creates an in-memory credential store
  • The passkey it registers is only valid for that browser process
  • If you close the browser and reopen it, the credential is gone forever
  • The only way to reuse it is to export the credential before closing, then re-import on next run
  • Export immediately after registration:
  const creds = await cdp.send('WebAuthn.getCredentials', { authenticatorId });
  fs.writeFileSync('/tmp/webauthn-creds.json', JSON.stringify(creds));
  • Re-import on next session:
  const saved = JSON.parse(fs.readFileSync('/tmp/webauthn-creds.json'));
  for (const cred of saved.credentials) {
    await cdp.send('WebAuthn.addCredential', { authenticatorId, credential: cred });
  }

4. Email verification is link-based, not always OTP

  • Don't assume OTP input fields — check the actual email body first
  • Turnkey, Vercel, Railway, Render all send magic links not codes
  • Parse the email body with quoted-printable decoding before extracting URLs
  • Watch for soft line breaks (`=\

`) in QP-encoded emails

5. Session cookies are tied to the authenticator

  • If you complete signup in context A and try to use the session in context B, it won't work
  • Cookies + passkey credential must stay in the same browser context

6. Internal APIs are not public APIs

  • app.service.com/internal/api/* endpoints require session cookies
  • api.service.com/public/v1/* endpoints require API key stamping
  • You can't call public API endpoints to bootstrap if you have no API key yet
  • Only the internal API (cookie-auth) is accessible from an authenticated browser session

Workflow

Phase A: Send signup email (clean context — no cookies)

const ctxClean = await browser.newContext({ storageState: undefined });
const page = await ctxClean.newPage();
await page.goto('https://service.com/signup');
// fill email, click continue
// close ctxClean immediately after submitting
await ctxClean.close();

Why separate? Prevents existing session cookies from hijacking the signup flow.

Phase B: Complete signup + save API keys (same context throughout)

const ctx = await browser.newContext({ storageState: undefined });
const page = await ctx.newPage();
const cdp = await ctx.newCDPSession(page);

// Set up virtual authenticator BEFORE navigating
await cdp.send('WebAuthn.enable', { enableUI: false });
const { authenticatorId } = await cdp.send('WebAuthn.addVirtualAuthenticator', {
  options: {
    protocol: 'ctap2', transport: 'internal',
    hasResidentKey: true, hasUserVerification: true,
    isUserVerified: true, automaticPresenceSimulation: true,
  }
});

// Navigate to verify link
await page.goto(verifyUrl);

// Complete signup steps...

// IMMEDIATELY export credential after passkey registration
const creds = await cdp.send('WebAuthn.getCredentials', { authenticatorId });
fs.writeFileSync('/tmp/webauthn-creds.json', JSON.stringify(creds));
console.log('Credentials backed up:', creds.credentials?.length);

// Continue to API key + wallet creation IN SAME SESSION
// ...save API keys...

// Close browser ONLY after saving everything to 1Password

Email Fetching via Proton Bridge IMAP

function fetchLatestTurnkeyLink(host='127.0.0.1', port=1143, user, pass) {
  return new Promise((resolve) => {
    const socket = net.connect(port, host);
    let buf='', tls2=null, step=0, body=[], inBody=false;
    const t = setTimeout(() => { try{(tls2||socket).destroy()}catch(e){}; resolve(null); }, 22000);
    function send(cmd) { (tls2||socket).write(cmd+'\
\
'); }
    function onData(data) {
      buf += data.toString();
      const lines = buf.split('\
\
'); buf = lines.pop();
      for (const l of lines) {
        if (inBody) body.push(l);
        if (step===0 && l.includes('OK')) { step=1; send('a1 STARTTLS'); }
        else if (step===1 && l.includes('a1 OK')) { tls2=tls.connect({socket,rejectUnauthorized:false}); tls2.on('data',onData); step=2; send(`a2 LOGIN "${user}" "${pass}"`); }
        else if (step===2 && l.includes('a2 OK')) { step=3; send('a3 SELECT INBOX'); }
        else if (step===3 && l.includes('a3 OK')) { step=4; send('a4 SEARCH ALL'); }
        else if (step===4 && l.startsWith('* SEARCH')) {
          const nums = l.replace('* SEARCH','').trim().split(' ').filter(Boolean);
          step=5; inBody=true; send(`a5 FETCH ${nums[nums.length-1]} (BODY[TEXT])`);
        }
        else if (step===5 && l.includes('a5 OK')) {
          clearTimeout(t); (tls2||socket).end();
          // Decode quoted-printable
          const decoded = body.join('\
')
            .replace(/=\
?\
/g, '')
            .replace(/=([0-9A-Fa-f]{2})/g, (_, h) => String.fromCharCode(parseInt(h, 16)));
          // Extract redirect URLs
          const urls = [...decoded.matchAll(/https:\/\/service\.com\/redirect\?token=[^\s"<>)]+/g)].map(m=>m[0]);
          resolve(urls[0] || null);
        }
      }
    }
    socket.on('data', onData);
    socket.on('error', () => { clearTimeout(t); resolve(null); });
  });
}

Key: Pattern-match the redirect URL to the service's domain, not a generic URL.


Proton Mail Setup

  • IMAP host: 127.0.0.1, port: 1143, STARTTLS
  • Credentials in 1Password: op://OpenClaw/Proton Bridge - Monk Fenix/...
  • Bridge must be running: ps aux | grep -i bridge

Input/Form Filling — Use Native Value Setter

Standard element.fill() sometimes fails on React inputs. Use this:

await page.evaluate((value) => {
  const input = document.querySelector('input');
  Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')
    .set.call(input, value);
  input.dispatchEvent(new Event('input', { bubbles: true }));
  input.dispatchEvent(new Event('change', { bubbles: true }));
}, value);

Button Clicking — Scroll Into View First

Buttons outside viewport fail with element is outside of the viewport. Always scroll:

await page.evaluate((text) => {
  const btn = [...document.querySelectorAll('button')]
    .find(b => b.textContent?.toLowerCase().includes(text) && !b.disabled);
  if (btn) { btn.scrollIntoView(); btn.click(); }
}, buttonText);

After Successful Authentication — save API keys

For services with internal browser APIs:

// Call authenticated internal API from page context
const data = await page.evaluate(async () => {
  const r = await fetch('/internal/api/v1/whoami');
  return r.json();
});
// data.organizationId, data.userId, etc.

Creating API Keys / Resources via Internal API

Once authenticated (cookie present), call internal endpoints from the page context:

const result = await page.evaluate(async ({ orgId, publicKey }) => {
  const r = await fetch('/tkhq/api/v1/activities', {  // adjust per service
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      type: 'ACTIVITY_TYPE_CREATE_API_KEYS',
      organizationId: orgId,
      parameters: { apiKeys: [{ apiKeyName: 'my-key', publicKey, curveType: 'API_KEY_CURVE_P256' }] }
    })
  });
  return { status: r.status, body: await r.text() };
}, { orgId, publicKey });

Important: Internal endpoints vary per service. Before creating resources, capture network traffic to learn the real endpoint:

page.on('request', req => {
  if (req.method() !== 'GET' && req.url().includes(serviceDomain))
    console.log(req.method(), req.url());
});

Saving to 1Password

op item create \
  --vault OpenClaw \
  --title "Turnkey API Credentials — Reddi Agent Protocol" \
  --category "API Credential" \
  "org_id[text]=$ORG_ID" \
  "user_id[text]=$USER_ID" \
  "api_public_key[text]=$API_PUB" \
  "api_private_key[password]=$API_PRIV" \
  "wallet_id[text]=$WALLET_ID" \
  "wallet_address[text]=$WALLET_ADDR"

Signup Registry (MANDATORY)

Before starting any signup, add an entry to the Notion signup registry:

  • DB: 322eb552-581a-81dc-adbc-fabb7af1d311
  • Fields: service name, email used, date, purpose
  • This is non-negotiable per POLICIES.md

Service-Specific Notes

Turnkey (app.turnkey.com)

  • Email verify: link-based (magic link, not OTP)
  • Auth: WebAuthn passkey (virtual authenticator works)
  • Post-signup API: /tkhq/api/v1/activities (internal, cookie-auth)
  • Public API: api.turnkey.com/public/v1/ requires X-Stamp (signed request)
  • Email aliases (+tag) map to the same Turnkey account — use a different provider for separate orgs
  • Org created in one run: b7378687-cf82-45ab-a46c-7dda9239001d (Reddi Agent Protocol)

Generic patterns

  • Vercel: email OTP or GitHub OAuth
  • Railway: GitHub OAuth (no email signup)
  • Supabase: email + password, then API key in dashboard
  • Fly.io: email + credit card, CLI bootstrap preferred

Pre-flight Checklist

Before starting any signup:

  • [ ] Added to Notion signup registry
  • [ ] Confirmed email available (not already used for this service)
  • [ ] Email aliases: does service collapse them? (test first)
  • [ ] IMAP readable for email provider being used
  • [ ] 1Password vault accessible
  • [ ] Proton Bridge running (if using Proton)
  • [ ] Sufficient budget for paid tier (if applicable) — ask Nissan first

适合场景

01

OpenClaw 用户查找和安装 Skill 时

02

用户想查找某类 Agent Skill 时

03

需要根据任务场景推荐可安装能力包时

04

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

OpenClaw

92.9%
按下载量换算1,049

安全审计

VirusTotal

未展示

ClawScan

可疑

Static analysis

通过

权限和风险

敏感数据

该 Skill 可能接触密钥、Token、环境变量或敏感配置,应进入高风险复核队列,默认不自动发布。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills