Metadata-Version: 2.4
Name: choola
Version: 0.1.0
Summary: A workflow engine for VS Code developers — build, run, and automate workflows with Python nodes.
License-Expression: Apache-2.0
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: anthropic>=0.49.0
Requires-Dist: click>=8.1
Requires-Dist: flask>=3.0
Requires-Dist: google-genai>=1.0.0
Requires-Dist: requests>=2.31.0
Requires-Dist: aiofiles>=23.0
Requires-Dist: google-api-python-client>=2.0
Requires-Dist: google-auth>=2.0
Dynamic: license-file

# Choola

A workflow automation engine where you build multi-step pipelines from self-contained Python nodes. Includes a visual editor (React + React Flow), a Flask API, and a CLI runner.

## Prerequisites

- Python 3.12+
- Node.js 18+ and npm

## Setup

```bash
# Clone the repo
git clone <repo-url>
cd choola

# Create a Python virtual environment and install dependencies
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

# Build the frontend
cd frontend
npm install
npm run build
cd ..
```

## Running

### Web UI + API

```bash
python server.py
```

Opens at [http://localhost:5000](http://localhost:5000). The visual editor lets you create workflows, add nodes, wire them together, and run them with live status streaming (SSE).

### CLI

```bash
# List available workflows
python cli.py list

# Run a workflow with a JSON payload
python cli.py run <workflow_name> --payload '{"key": "value"}'

# Create a new empty workflow
python cli.py create <workflow_name>

# List nodes in a workflow
python cli.py nodes <workflow_name>
```

## Project Structure

```
choola/
├── server.py              # Flask API + serves the React frontend
├── cli.py                 # Headless CLI runner
├── database.py            # SQLite store (globals + run logs)
├── requirements.txt
├── core/
│   └── base_node.py       # Abstract base class for all nodes
├── frontend/              # React + React Flow visual editor (Vite)
└── workflows/
    └── <workflow_name>/
        ├── topology.json  # DAG definition (nodes + edges)
        └── nodes/
            ├── __init__.py
            └── <node>.py  # One file per node
```

## How Workflows Work

A workflow is a folder under `workflows/` containing:

- **`topology.json`** — defines the DAG: which nodes exist and how they connect.
- **`nodes/*.py`** — one Python file per node. Each node is a self-contained class that inherits from `BaseNode`, receives a JSON payload, does one thing, and returns the (possibly modified) payload to the next node.

The engine topologically sorts the nodes and executes them in order, passing the payload through the chain.

## Writing a Node

Every node is a single `.py` file in `workflows/<name>/nodes/`. Here's the template:

```python
"""
@choola-node: MyNodeName
@category: processing
@description: Does one specific thing to the payload.
@input-payload:
  - some_key (str): What this node expects
@output-payload:
  - some_key (str): Same or transformed
  - new_key (int): Something this node adds
@config-fields:
  - threshold (int, default=10): Controls the threshold
@example-input: {"some_key": "hello"}
@example-output: {"some_key": "hello", "new_key": 42}
@side-effects: none
@errors: Raises ValueError if some_key is missing
"""

from typing import Any

from core.base_node import BaseNode


class MyNodeName(BaseNode):
    name = "My Node Name"
    category = "processing"
    description = "Does one specific thing to the payload."
    fields = [
        {"name": "threshold", "type": "number", "default": 10},
    ]

    async def execute(self, payload: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]:
        # Your logic here
        return payload
```

### Node rules

- **Self-contained** — all logic in one file, no cross-node imports.
- **JSON in, JSON out** — communicate only through the `payload` dict.
- **Docstring required** — the `@choola-node` markers make nodes discoverable by grep.

### Categories

| Category | Use for |
|---|---|
| `input` | Data ingestion, entry points |
| `processing` | Transformation, enrichment |
| `routing` | Conditional branching |
| `output` | Sending results, notifications |
| `validation` | Data checks, guards |
| `integration` | External API calls |

### Global variables

Nodes can persist data across runs using SQLite-backed helpers:

```python
value = await self.get_global("my_key")
await self.set_global("my_key", "new_value")
```

## API Endpoints

| Method | Path | Description |
|---|---|---|
| GET | `/api/nodes` | List all registered node types |
| GET | `/api/workflows` | List all workflows |
| POST | `/api/workflows` | Create a new workflow |
| GET | `/api/workflows/<name>/topology` | Get workflow topology |
| PUT | `/api/workflows/<name>/topology` | Update workflow topology |
| POST | `/api/workflows/<name>/run` | Execute a workflow |
| GET | `/api/workflows/<name>/stream/<run_id>` | SSE stream for live run status |

## Using with Claude Code

This project includes slash commands for building workflows with AI assistance:

- **`/choola`** — describe what you want and Claude builds the full workflow (nodes + topology).
- **`/node`** — add a single node to an existing workflow.
