# How to Create Custom Site Skills in ego-browser: A Complete Guide for Website-Specific Automation

> Learn to create custom site skills in ego-browser with our guide. Extend ego harness with domain-specific tools for website automation. Get started now.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: how-to-guide
- Published: 2026-08-06

---

**Custom site skills in ego-browser extend the harness with domain-specific tools by creating a learning folder under `skills/ego-browser/learnings/<site-id>/` with a [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) that declares matching domains and implements node or browser tools.**

The **ego-browser** harness from the citrolabs/ego-lite repository enables you to extend its capabilities through *site-specific learned skills*. These skills automatically activate when the browser visits matching domains, exposing custom tools that agents can invoke for automation tasks like data extraction, form interaction, or API calls.

## Understanding Site Skill Architecture

### Core Components

| Component | Purpose | Location |
|-----------|---------|----------|
| **Learning folder** | Container for all skill assets | `skills/ego-browser/learnings/<site-id>/` |
| **manifest.json** | Declares site identity, domains, and available tools | Root of learning folder |
| **Node tools** | Execute in the Node.js process (network requests, computation) | `tools/` subdirectory |
| **Browser tools** | Execute in browser context via CDP (DOM interaction) | `browser-tools/` subdirectory |
| **Notes** | Markdown documentation for human reference | `notes/` subdirectory |

The harness loads skills dynamically through [`src/learning/learnContext.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/learnContext.ts), which scans for `**/manifest.json` files and registers valid tools into the helper context available to agent scripts.

## Creating a New Site Skill: Step-by-Step

### Step 1: Create the Learning Directory

Choose a short, lowercase identifier and create the folder structure:

```bash
mkdir -p skills/ego-browser/learnings/demo-site/{tools,browser-tools,notes}

```

### Step 2: Define the Manifest

Create [`skills/ego-browser/learnings/demo-site/manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/skills/ego-browser/learnings/demo-site/manifest.json) with this structure:

```json
{
  "id": "demo-site",
  "name": "Demo Site Automation",
  "domains": ["demo.example.com", "*.demo.example.com"],
  "notes": ["notes/overview.md"],
  "nodeTools": {
    "fetchApiData": {
      "description": "Fetch JSON data from the demo API",
      "path": "tools/fetchApi.js",
      "callable": "fetchApiData",
      "args": {
        "endpoint": {
          "type": "string",
          "required": true,
          "description": "API endpoint path (e.g., /users)"
        }
      },
      "returns": {
        "type": "object",
        "description": "Parsed JSON response"
      }
    }
  },
  "browserTools": {
    "extractFormData": {
      "description": "Extract all input values from forms on the page",
      "path": "browser-tools/extractForms.js",
      "callable": "extractFormData",
      "args": {},
      "returns": {
        "type": "array",
        "description": "Array of form data objects"
      }
    }
  }
}

```

**Critical manifest fields:**

- **`id`** — Unique identifier matching the folder name
- **`domains`** — Hostnames that trigger this skill (wildcards supported)
- **`nodeTools`**/**`browserTools`** — Tool declarations with `path` relative to learning folder
- **`callable`** — Must match the exported function name in the source file

### Step 3: Implement Node Tools

Create [`skills/ego-browser/learnings/demo-site/tools/fetchApi.js`](https://github.com/citrolabs/ego-lite/blob/main/skills/ego-browser/learnings/demo-site/tools/fetchApi.js):

```javascript
/** Fetch data from demo API with authentication headers */
export async function fetchApiData(endpoint) {
  const baseUrl = 'https://demo.example.com/api';
  const response = await fetch(`${baseUrl}${endpoint}`, {
    headers: {
      'Accept': 'application/json',
      'Authorization': `Bearer ${process.env.DEMO_API_TOKEN}`
    }
  });
  
  if (!response.ok) {
    throw new Error(`API error: ${response.status} ${response.statusText}`);
  }
  
  return await response.json();
}

```

### Step 4: Implement Browser Tools

Create [`skills/ego-browser/learnings/demo-site/browser-tools/extractForms.js`](https://github.com/citrolabs/ego-lite/blob/main/skills/ego-browser/learnings/demo-site/browser-tools/extractForms.js):

```javascript
/** Extract all form input values from the current page */
export async function extractFormData() {
  return await ego.evaluate(() => {
    const forms = document.querySelectorAll('form');
    return Array.from(forms).map(form => {
      const inputs = form.querySelectorAll('input, select, textarea');
      const data = {};
      inputs.forEach(input => {
        if (input.name) {
          data[input.name] = input.value;
        }
      });
      return {
        formId: form.id || null,
        formAction: form.action || null,
        fields: data
      };
    });
  });
}

```

All browser tools use the `ego.evaluate()` helper to run code in the browser context via Chrome DevTools Protocol (CDP).

### Step 5: Add Documentation

Create [`skills/ego-browser/learnings/demo-site/notes/overview.md`](https://github.com/citrolabs/ego-lite/blob/main/skills/ego-browser/learnings/demo-site/notes/overview.md):

```markdown

# Demo Site Skill

Provides automation tools for demo.example.com.

## Available Tools

- `fetchApiData(endpoint)` — Call the REST API from node context
- `extractFormData()` — Read all form values from the current page

## Usage

Navigate to any demo.example.com page, then invoke tools through the helper context.

```

## Validating and Testing Site Skills

### Run the Validation Script

The repository includes a validator that checks manifest correctness:

```bash
npm run validate:site-skills

# or: npm run validate:learnings

```

This script ([`package/ego-browser/scripts/validate-site-skills.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/scripts/validate-site-skills.ts)) verifies:

- All declared file paths exist
- Exported callable names are present in source files
- Argument and return schemas follow the correct structure
- Required fields are present in the manifest

### Test in Agent Context

Once validated, start the harness and test your skill:

```javascript
// Agent script executed within ego-browser
await navigate('https://demo.example.com/login');

// Browser tool: extract form data from the current page
const forms = await extractFormData();
console.log('Detected forms:', forms);

// Node tool: fetch user data from API
const users = await fetchApiData('/users');
console.log('Users:', users);

```

Tools are only available when the active page matches a declared domain. Attempting to call a skill's tools on a non-matching domain throws an `ElementResolutionError` with transient kind, allowing agents to handle navigation and retry.

## Real-World Example: Google Skill

The repository provides a complete reference implementation in `skills/ego-browser/learnings/google/`. Its manifest demonstrates both node and browser tools:

```json
{
  "id": "google",
  "name": "Google Search",
  "domains": ["google.com", "*.google.com"],
  "notes": ["notes/search.md", "notes/maps.md"],
  "nodeTools": {
    "searchAndExtract": {
      "description": "Perform Google search and extract results",
      "path": "tools/search.js",
      "callable": "searchAndExtract",
      "args": {
        "query": {
          "type": "string",
          "required": true,
          "description": "Search query string"
        },
        "maxResults": {
          "type": "integer",
          "required": false,
          "description": "Maximum results to return (default: 10)"
        }
      },
      "returns": {
        "type": "array",
        "description": "Array of search result objects with title, url, snippet"
      }
    }
  },
  "browserTools": {
    "getSearchResults": {
      "description": "Extract search results from current Google results page",
      "path": "browser-tools/extractResults.js",
      "callable": "getSearchResults",
      "args": {},
      "returns": {
        "type": "array",
        "description": "Array of result objects"
      }
    }
  }
}

```

## Key Implementation Files

| File | Purpose |
|------|---------|
| [`src/learning/learnContext.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/learning/learnContext.ts) | Scans, validates, and registers all site skills at startup |
| [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts) | Generates the helper context exposing registered tools to agents |
| [`scripts/validate-site-skills.ts`](https://github.com/citrolabs/ego-lite/blob/main/scripts/validate-site-skills.ts) | CLI validation for skill manifests |
| [`SKILL.md`](https://github.com/citrolabs/ego-lite/blob/main/SKILL.md) | Documentation for agent authors on harness capabilities |

## Summary

- **Site skills** extend ego-browser with domain-specific automation tools
- Create a folder under `skills/ego-browser/learnings/<id>/` with [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json), tool implementations, and optional notes
- **Node tools** run in the Node.js process for API calls and computation
- **Browser tools** use `ego.evaluate()` for direct DOM manipulation via CDP
- Validate manifests with `npm run validate:site-skills` before deployment
- Tools only activate on pages matching declared `domains` patterns

## Frequently Asked Questions

### How do I debug a site skill that isn't loading?

Run `npm run validate:site-skills` first to catch manifest syntax errors. Then check that your `id` matches the folder name and that `domains` includes the exact hostname you're testing. The harness logs registration status at startup—look for messages from [`learnContext.ts`](https://github.com/citrolabs/ego-lite/blob/main/learnContext.ts) about successfully loaded skills.

### Can I use TypeScript for tool implementations?

The repository uses ESM JavaScript with `"type": "module"` at the root. TypeScript tools should be compiled to JavaScript before deployment. Place the compiled `.js` files in `tools/` or `browser-tools/` and reference them in [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json) with `.js` extensions.

### What happens if two skills match the same domain?

The harness registers all matching skills, but tool names must be unique across the active skill set. If conflicts occur, the later-loaded skill's tools take precedence. Use descriptive tool names prefixed with your site ID (e.g., `demoFetchApi` instead of `fetchApi`) to avoid collisions.

### How do I update an existing site skill?

Modify the tool source files or [`manifest.json`](https://github.com/citrolabs/ego-lite/blob/main/manifest.json), then restart the harness. Changes are not hot-reloaded. Run the validation script after any manifest edits to ensure the skill remains valid before restarting.