@athree/create-project
v1.4.0
Published
Create a new A3 automation project
Keywords
Readme
A3 Project Template
Create and scaffold A3 automation workflows.
Note: Always use the latest versions of
@athree/runnerand@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 compileNaming convention: Projects should be named a3-project-{name} (e.g., a3-project-hotel-rates).
Note: You must run
npm run compilefor 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 compileAdding 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 keysRequired variables:
- At least one LLM API key:
GOOGLEAI_API_KEY,OPENAI_API_KEY, orANTHROPIC_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 yourextensions.jsonfile. 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-extensionsStep 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 neededEach 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
.crxfile 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 identifierpath— Relative path from the project root to the unpacked extension directorydescription— What the extension doesenabled— Set tofalseto skip without removing the entryloadInPersistent— 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.jsonImportant: Most captcha solver extensions set
loadInPersistent: trueandloadInTransient: false, soCHROME_PERSISTENT=trueis 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 containerinstallExtensions(true, ...)— first argument isisPersistent(matches againstloadInPersistent/loadInTransientin 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 classesIncluded Workflows
SimpleScreenshot
A minimal workflow demonstrating basic browser automation:
- Navigates to a URL
- Waits for page load
- Takes a full-page screenshot
Useful as a starting point or for testing browser connectivity.
SimpleGenericWebAgent
Demonstrates the GenericWebAgent pattern:
- Navigates to a URL
- Creates a
GenericWebAgentviaAgentsService - 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:
- Loads instructions from a markdown file
- Sets up automation context with input/output schemas
- Uses
AutomationRunner.learn()to execute with LLM guidance - 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
- Set up environment variables (see Environment Setup)
- Run
npm run compileto build the project - Update the dataset in the workflow (or load from
datasets/) - Define instructions in
src/instructions/for LLM-powered workflows - Update schemas in
src/schema/to match your data
Key Services
ChatService— Send messages to the UIBrowserService— Access the browser page for screenshots, navigationAgentsService— Create LLM agents (createGenericWebAgent(),createPageExtractor())AutomationRunner— Run automations withrun()(existing stages) orlearn()(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
