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

aitomator

v0.2.1

Published

A tiny, agent-friendly TypeScript workflow daemon

Readme

AItomator

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 + logs

The 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 --version

Make 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 link

Quick start

Create a workspace for your automations:

mkdir my-automations
cd my-automations
aitomator init
bun install

aitomator 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.log

Create and run a manual workflow:

aitomator workflow create hello --trigger manual --non-interactive
aitomator validate
aitomator graph hello
aitomator run hello --input '{"name":"world"}' --json

Inspect 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 zod
import { 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 --stdin

Manual 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 start
curl -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 start

Or install and start it as an always-on background service:

aitomator start --background

On 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 restart

View daemon output without calling journalctl or log directly:

aitomator logs --daemon
aitomator logs --daemon --follow

Follow one workflow across its current and future runs, or follow one specific run:

aitomator logs --workflow github-agent --follow
aitomator logs --run <run-id> --follow

Use --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.log

The 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 install

aitomator 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 --follow

Remove it with:

aitomator service uninstall

If 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 sync

Nodes 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 skill

A recommended automation loop is:

discover capabilities → scaffold files → edit TypeScript → add dependencies
→ validate → reload → inspect graph → run → inspect history

Security

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 build

Release automation and npm publishing are documented in RELEASING.md.

License

AItomator is open-source software released under the MIT License.