npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@athree/create-project

v1.4.0

Published

Create a new A3 automation project

Readme

A3 Project Template

Create and scaffold A3 automation workflows.

Note: Always use the latest versions of @athree/runner and @athree/module-loader. You can find the latest versions on npm:

Or check the latest release commits at https://github.com/ubio/athree/commits/main/

Create a New Project

npx @athree/create-project a3-project-starter
cd a3-project-starter
npm i && npm run compile

Naming convention: Projects should be named a3-project-{name} (e.g., a3-project-hotel-rates).

Note: You must run npm run compile for the project to appear in your local athree runner.

Or clone manually:

cp -r a3-project-template a3-project-starter
cd a3-project-starter
npm i && npm run compile

Adding Project to Runner

Use the Add Project button in the A3 runner UI to add your project directory. The runner will automatically detect workflows in compiled projects.

Environment Setup

Configure your environment variables before running workflows:

cp .env.example .env
# Edit .env and add your API keys

Required variables:

  • At least one LLM API key: GOOGLEAI_API_KEY, OPENAI_API_KEY, or ANTHROPIC_API_KEY

Optional variables:

  • CHROME_HEADLESS — Run browser in headless mode (default: true)
  • CHROME_KEEP_OPEN — Keep browser open between runs (default: true)
  • CHROME_PERSISTENT — Use persistent browser profile (default: false). Required for extensions. See Chrome Extensions.
  • EXTENSIONS_CONFIG_PATH — Path to your extensions.json file. See Chrome Extensions.

Chrome Extensions

Browser extensions (e.g. captcha solvers, cookie consent handlers) can be automatically installed into the Chrome profile before your workflow runs. This requires four steps:

Step 1: Install the dependency

npm install @athree/runner-extensions

Step 2: Create the extensions directory and config

This template ships with the idcac (I don't care about cookies) extension pre-installed in chrome-extensions/. To add more extensions, copy unpacked extension directories from a3-rate-shopper/chrome-extensions/ or other projects and add entries to extensions.json.

├── chrome-extensions/
│   ├── extensions.json          # Extension manifest
│   ├── idcac/                   # Pre-installed: cookie consent automation
│   └── {your-extension}/        # Add more as needed

Each extension must be an unpacked Chrome extension (a directory containing a manifest.json). You can get these by:

  • Copying from an existing project (e.g. a3-rate-shopper/chrome-extensions/)
  • Downloading a .crx file and extracting it
  • Cloning the extension source from GitHub

The extensions.json file lists which extensions to load:

{
    "extensions": [
        {
            "name": "idcac",
            "path": "./chrome-extensions/idcac",
            "description": "I don't care about cookies - Cookie consent automation",
            "enabled": true,
            "loadInPersistent": true,
            "loadInTransient": true
        }
    ]
}

To add a captcha solver, copy the directory and add an entry:

{
    "name": "capsolver",
    "path": "./chrome-extensions/capsolver",
    "description": "CapSolver - Automated captcha solving extension",
    "enabled": true,
    "loadInPersistent": true,
    "loadInTransient": false
}

Fields:

  • name — Human-readable identifier
  • path — Relative path from the project root to the unpacked extension directory
  • description — What the extension does
  • enabled — Set to false to skip without removing the entry
  • loadInPersistent — Load when the browser runs in persistent mode (CHROME_PERSISTENT=true)
  • loadInTransient — Load when the browser runs in transient mode (default)

Step 3: Set environment variables

Add these to your .env file:

CHROME_PERSISTENT=true
EXTENSIONS_CONFIG_PATH=./chrome-extensions/extensions.json

Important: Most captcha solver extensions set loadInPersistent: true and loadInTransient: false, so CHROME_PERSISTENT=true is required for them to load.

Step 4: Call installExtensions() in your workflow

In your workflow's run() method, register the service and install extensions before calling getPage():

import { BrowserService, workflow } from '@athree/runner';
import { BrowserExtensionsService } from '@athree/runner-extensions';
import { dep, Mesh } from 'mesh-ioc';

@workflow({ title: 'My Workflow' })
export class MyWorkflow {
    @dep() private browserService!: BrowserService;
    @dep() private browserExtensionsService!: BrowserExtensionsService;
    @dep() private mesh!: Mesh;

    async run() {
        // 1. Register and install extensions (BEFORE getPage)
        this.mesh.service(BrowserExtensionsService);
        await this.browserExtensionsService.installExtensions(
            true,
            this.browserService.userDataDir,
        );

        // 2. Now get the page — extensions will be active
        const page = await this.browserService.getPage();
        await page.goto('https://example.com');

        // ... rest of your workflow
    }
}

Key points:

  • this.mesh.service(BrowserExtensionsService) registers the service in the DI container
  • installExtensions(true, ...) — first argument is isPersistent (matches against loadInPersistent/loadInTransient in config)
  • Extensions are installed into the Chrome user data directory, so they persist across runs
  • The service uses Puppeteer internally to enable Chrome developer mode and load unpacked extensions
  • Extensions only get installed once — subsequent runs detect they are already present and skip installation

Project Structure

├── chrome-extensions/           # Browser extensions (see Chrome Extensions)
│   ├── extensions.json          # Extension manifest
│   └── idcac/                   # Cookie consent handler (pre-installed)
├── datasets/                    # Input YAML files (one per service)
├── services/                    # Output directory (gitignored)
│   └── {service-id}/
│       ├── screenshot.png       # Page screenshot
│       └── result.json          # Workflow result
├── src/
│   ├── instructions/            # LLM instructions (markdown files)
│   ├── schema/                  # Zod schemas (Dataset, Result)
│   └── workflows/               # Workflow classes

Included Workflows

SimpleScreenshot

A minimal workflow demonstrating basic browser automation:

  1. Navigates to a URL
  2. Waits for page load
  3. Takes a full-page screenshot

Useful as a starting point or for testing browser connectivity.

SimpleGenericWebAgent

Demonstrates the GenericWebAgent pattern:

  1. Navigates to a URL
  2. Creates a GenericWebAgent via AgentsService
  3. Runs the agent with a simple text objective

This is the simplest way to use LLM-powered web automation - no stages, no schemas, just a one-shot LLM call with browser tools.

SimpleLearningAgentExtract

Demonstrates the learning agent pattern with stages:

  1. Loads instructions from a markdown file
  2. Sets up automation context with input/output schemas
  3. Uses AutomationRunner.learn() to execute with LLM guidance
  4. The LLM creates reusable stages to navigate and extract data

Use this pattern for complex, multi-step automations that benefit from reusable stages.

Getting Started

  1. Set up environment variables (see Environment Setup)
  2. Run npm run compile to build the project
  3. Update the dataset in the workflow (or load from datasets/)
  4. Define instructions in src/instructions/ for LLM-powered workflows
  5. Update schemas in src/schema/ to match your data

Key Services

  • ChatService — Send messages to the UI
  • BrowserService — Access the browser page for screenshots, navigation
  • AgentsService — Create LLM agents (createGenericWebAgent(), createPageExtractor())
  • AutomationRunner — Run automations with run() (existing stages) or learn() (LLM-guided)
  • AutomationContext — Manage automation state, inputs, outputs, and schemas

GenericWebAgent Pattern

For simple one-shot LLM tasks, use GenericWebAgent:

import { AgentsService, BrowserService, workflow } from '@athree/runner';
import { dep } from 'mesh-ioc';

@workflow({ title: 'My Workflow' })
export class MyWorkflow {
    @dep() private browserService!: BrowserService;
    @dep() private agentsService!: AgentsService;

    async run() {
        const page = await this.browserService.getPage();
        await page.goto('https://example.com');

        const agent = this.agentsService.createGenericWebAgent();
        agent.addUserContext({ currentUrl: page.url() });

        await agent.run('Extract the main heading and description from this page.');
    }
}

Learning Agent Pattern

For complex multi-step automations with reusable stages, use the learning agent:

import { AutomationRunner, AutomationContext, workflow } from '@athree/runner';
import { dep } from 'mesh-ioc';
import { z } from 'zod';

@workflow({ title: 'My Workflow' })
export class MyWorkflow {
    @dep() private automationRunner!: AutomationRunner;
    @dep() private automation!: AutomationContext;

    async run() {
        const result = await this.automationRunner.learn({
            automationId: 'my-automation',
            instructions,           // Loaded from .md file
            inputs: { url },        // Input data
            inputsSchema,           // Zod schema for inputs
            outputsSchema,          // Zod schema for expected outputs
            learningModel: 'gemini-2.5-flash',  // Optional: specify LLM model
        });

        const outputs = this.automation.outputs;
    }
}

Instructions Files

Instructions tell the LLM how to handle different page states. Place them in src/instructions/:

# Goal
Extract product details from the page.

# Output Fields
- `ctx.outputs.title` - Product title
- `ctx.outputs.price` - Product price
- `ctx.outputs.status` - "success" or "failure"

# Instructions
## Product page
1. Extract the title from h1
2. Extract the price
3. Set status to "success"

Tips

  • Define clear output fields in your instructions
  • Use Zod schemas to validate inputs and outputs
  • The LLM creates reusable stages that can be re-run without LLM
  • Save results to the services/ folder