aitomator
v0.2.1
Published
A tiny, agent-friendly TypeScript workflow daemon
Maintainers
Readme
AItomator

A tiny, agent-friendly TypeScript workflow daemon for automation without a workflow platform.
AItomator lets you build local automations as ordinary TypeScript files. It runs continuously on a workstation, mini PC, home server, or VPS and responds to HTTP requests, cron schedules, polling results, and manual commands.
It uses Bun and SQLite and does not require Redis, RabbitMQ, Postgres, Docker, Kubernetes, or a hosted control plane.
What is it for?
AItomator is useful when you want to:
- receive a webhook and run local TypeScript code;
- run backups, reports, or maintenance tasks on a schedule;
- poll an API that does not provide webhooks;
- start a coding agent when a project item changes;
- chain API calls, scripts, and command-line programs;
- keep automations as reviewable source code instead of configuring them through a visual editor;
- give an AI agent a deterministic CLI and JSON interface for creating and operating workflows.
Workflows execute trusted local code. AItomator is not a sandbox.
How it works
HTTP / cron / poll / manual trigger
│
v
AItomator daemon
│ │
│ └── SQLite state and durable run queue
│
v
fresh Bun runner process
│
v
TypeScript node → TypeScript node → TypeScript node
│
v
result + logsThe daemon owns trigger registration, scheduling, persistence, and concurrency. Each workflow run gets a fresh Bun child process. This isolates crashes and memory leaks, and it means node edits and newly installed dependencies are picked up on the next run without restarting the daemon.
Node output becomes the next node's input. Workflow source remains in TypeScript files; only runtime state and history are stored in SQLite.
Requirements
- Bun 1.1 or newer
- Linux, macOS, or another platform supported by Bun
Installation
Once the package is published, install the CLI globally:
bun add --global aitomator
aitomator --versionMake sure Bun's global binary directory is on your PATH:
export PATH="$HOME/.bun/bin:$PATH"To work from a local source checkout instead:
git clone https://github.com/miguelangarano/aitomator.git
cd aitomator
bun install
bun linkQuick start
Create a workspace for your automations:
mkdir my-automations
cd my-automations
aitomator init
bun installaitomator init creates:
my-automations/
├── aitomator.config.ts # daemon and runtime settings
├── package.json # dependencies shared by every node
├── .env.example # environment variable template
├── .gitignore
├── workflows/ # workflow definitions
├── nodes/ # executable TypeScript nodes
├── data/ # SQLite runtime state
└── executions/
└── <workflow-id>/
└── <execution-id>/
└── execution.logCreate and run a manual workflow:
aitomator workflow create hello --trigger manual --non-interactive
aitomator validate
aitomator graph hello
aitomator run hello --input '{"name":"world"}' --jsonInspect its history:
aitomator runs list
aitomator runs get <run-id> --json
aitomator logs --run <run-id>Writing workflows
A workflow declares a trigger and a sequential list of nodes:
// workflows/greet.workflow.ts
import { defineWorkflow } from "aitomator"
export default defineWorkflow({
id: "greet",
name: "Greeting endpoint",
trigger: {
type: "http",
method: "POST",
path: "/greet/:name",
},
concurrency: {
maxRuns: 1,
overflow: "queue",
},
steps: [
{
id: "normalize",
node: "../nodes/normalize.ts",
},
{
id: "greet",
node: "../nodes/greet.ts",
params: { punctuation: "!" },
},
],
})Node paths are resolved relative to the workflow file.
Writing nodes
A node is an ordinary TypeScript module with a run function:
// nodes/greet.ts
import { defineNode } from "aitomator"
export default defineNode({
async run(ctx) {
const input = ctx.input as { name: string }
const params = ctx.params as { punctuation: string }
ctx.log.info("Creating greeting for", input.name)
return {
message: `Hello, ${input.name}${params.punctuation}`,
}
},
})The context provides:
ctx.input: trigger data or the previous node's output;ctx.params: parameters declared on the workflow step;ctx.workflow: workflow ID and run ID;ctx.node: current node ID;ctx.trigger: normalized trigger type and data;ctx.env: environment variables;ctx.log: run-associated debug, info, warning, and error logging.
Returning undefined intentionally passes undefined to the next node. Return ctx.input when a node should preserve its input.
Nodes can use normal Bun and Node-compatible APIs, including fetch, filesystem access, and Bun.spawn.
Input and output validation
Schemas are optional. AItomator supports objects with a parse() method, including Zod, and Standard Schema-compatible validators.
aitomator deps add zodimport { z } from "zod"
import { defineNode } from "aitomator"
const input = z.object({ issueNumber: z.number() })
const output = z.object({ accepted: z.boolean() })
export default defineNode({
input,
output,
async run(ctx) {
return { accepted: ctx.input.issueNumber > 0 }
},
})Trigger types
Manual
trigger: { type: "manual" }aitomator run my-workflow --input '{"issueNumber":42}'
cat input.json | aitomator run my-workflow --stdinManual execution works with or without the daemon. User code still runs in a separate Bun process.
HTTP
trigger: {
type: "http",
method: "POST",
path: "/deploy/:environment",
auth: { type: "bearer", env: "DEPLOY_WEBHOOK_SECRET" },
}Start the daemon and send a request:
aitomator startcurl -X POST http://127.0.0.1:8787/deploy/staging \
-H "Authorization: Bearer $DEPLOY_WEBHOOK_SECRET" \
-H "Content-Type: application/json" \
-d '{"revision":"abc123"}'The first node receives the method, path, route parameters, query parameters, headers, and body.
Cron
trigger: {
type: "cron",
expression: "0 2 * * *",
timezone: "America/Guayaquil",
}Cron uses the standard five-field order: minute, hour, day of month, month, and day of week. The daemon must be running for scheduled workflows.
Poll
trigger: {
type: "poll",
every: "60s",
node: "../nodes/check-project.ts",
}A polling node returns state and can optionally emit explicit events:
export async function poll() {
const response = await fetch("https://example.com/api/items")
const items = await response.json()
return {
state: items,
events: items
.filter((item) => item.ready)
.map((item) => ({ id: item.id, title: item.title })),
}
}State is persisted in SQLite. Without events, AItomator starts a run when the state changes after the initial baseline. With events, each emitted event creates a separate workflow run.
Running the daemon
Run it in the foreground:
aitomator startOr install and start it as an always-on background service:
aitomator start --backgroundOn Linux, AItomator also enables user lingering when permitted so the service starts at boot and continues after logout. If the operating system requires administrator authorization, the command reports the exact limitation instead of hiding it.
Control it from another terminal in the same workspace:
aitomator status
aitomator workflow reload
aitomator stop
aitomator restartView daemon output without calling journalctl or log directly:
aitomator logs --daemon
aitomator logs --daemon --followFollow one workflow across its current and future runs, or follow one specific run:
aitomator logs --workflow github-agent --follow
aitomator logs --run <run-id> --followUse --lines N to control the initial tail and press Ctrl+C to stop following.
Every run creates a permanent execution log immediately at:
executions/<workflowId>/<executionId>/execution.logThe file combines daemon lifecycle messages, runner and step lifecycle messages, ctx.log.* output, and ordinary console.* calls made by nodes. Runs with no node output still receive an execution log. Existing installations can continue reading legacy data/logs/<executionId>.log files through the same CLI commands.
Workflow definitions are watched and hot-reloaded when valid. Invalid updates are rejected, leaving the previous valid registry active. Node files and dependency changes are naturally refreshed because every run starts a new process.
Background service
Install and start a per-user systemd service on Linux or a launchd service on macOS:
cd /path/to/my-automations
aitomator service installaitomator start --background is the shorter equivalent.
All service operations are available through the CLI:
aitomator service status
aitomator service restart
aitomator service stop
aitomator service start
aitomator service logs --followRemove it with:
aitomator service uninstallIf the workspace is moved, reinstall the service because its definition contains an absolute working-directory path.
Configuration
aitomator.config.ts controls the daemon:
import { defineConfig } from "aitomator"
export default defineConfig({
database: "./data/aitomator.db",
http: {
host: "127.0.0.1",
port: 8787,
},
concurrency: {
maxRuns: 4,
defaultWorkflowMaxRuns: 1,
},
logging: {
level: "info",
},
env: {
PATH: `/path/to/custom/bin:${process.env.PATH}`,
},
})Environment overrides:
| Variable | Purpose |
| --- | --- |
| AITOMATOR_WORKSPACE | Override workspace discovery |
| AITOMATOR_DATABASE_PATH | Override the SQLite path |
| AITOMATOR_HTTP_HOST | Override the HTTP bind address |
| AITOMATOR_HTTP_PORT | Override the HTTP port |
| AITOMATOR_MAX_RUNS | Override global concurrency |
| AITOMATOR_LOG_LEVEL | Set debug, info, warn, or error |
Bun loads .env files automatically. Values in the config's env block override inherited environment variables and are propagated to workflow nodes and child processes. Nodes can read variables through process.env, Bun.env, or ctx.env.
Secrets are not automatically copied to SQLite, but trigger payloads, node inputs and outputs, errors, and logs are persisted. Do not return or log secrets.
Concurrency and persistence
Global concurrency limits the number of runner processes across the workspace. Each workflow can set its own limit and overflow policy:
concurrency: {
maxRuns: 1,
overflow: "queue", // or "drop"
}Queued runs and run history survive daemon restarts. A failed node marks its step and workflow run as failed without crashing the daemon.
Dependencies
All workflows share one workspace dependency graph:
aitomator deps add octokit zod
aitomator deps remove octokit
aitomator deps list --json
aitomator deps syncNodes can import any package installed in the workspace.
CLI reference
aitomator init
aitomator start [--background]
aitomator stop
aitomator restart
aitomator status
aitomator logs [--workflow ID | --run ID] [--follow] [--lines N]
aitomator logs --daemon [--follow] [--lines N]
aitomator workflow create <id> --trigger manual|http|cron|poll
aitomator workflow list
aitomator workflow describe <id>
aitomator workflow enable <id>
aitomator workflow disable <id>
aitomator workflow remove <id> --force
aitomator workflow reload [id]
aitomator node create <id>
aitomator node inspect <id>
aitomator run <workflow> [--input JSON | --stdin] [--wait]
aitomator runs list [--workflow ID] [--limit N]
aitomator runs get <run-id>
aitomator runs retry <run-id>
aitomator validate [workflow] [--json]
aitomator graph <workflow> [--format ascii|compact|mermaid|json]
aitomator doctor
aitomator deps add|remove|list|sync
aitomator service install|uninstall|start|stop|restart|status|logs
aitomator capabilities --json
aitomator skill [--json]Commands support deterministic JSON output where applicable. Exit codes are stable for automation:
| Code | Meaning |
| ---: | --- |
| 0 | Success |
| 1 | General failure |
| 2 | Invalid usage or configuration |
| 3 | Workflow, node, or run not found |
| 5 | Validation failure |
| 6 | Daemon unavailable |
| 7 | Workflow run failed |
Agent usage
AItomator exposes its capabilities and a bundled agent guide:
aitomator capabilities --json
aitomator skillA recommended automation loop is:
discover capabilities → scaffold files → edit TypeScript → add dependencies
→ validate → reload → inspect graph → run → inspect historySecurity
Installing or executing an AItomator workflow is equivalent to executing local TypeScript code. Nodes can access the filesystem, environment, processes, and network.
Recommended precautions:
- run the daemon as a dedicated, unprivileged user;
- use scoped API keys and access tokens;
- bind HTTP to loopback unless remote access is intentional;
- authenticate sensitive HTTP workflows;
- review third-party workflow and node code before running it;
- never run the daemon as root unless absolutely necessary.
See SECURITY.md for the complete security model.
Development
git clone https://github.com/miguelangarano/aitomator.git
cd aitomator
bun install
bun test
bun run typecheck
bun run buildRelease automation and npm publishing are documented in RELEASING.md.
License
AItomator is open-source software released under the MIT License.
