Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# GitHub workflow guidance

This directory contains maintainer-facing repository automation and notes.

- Keep release-process documentation in `.github/README.md` or another maintainer-only repo doc, not in package `README.md` files that become PyPI project descriptions.
- Python package publish workflows should read package name/version from the package `pyproject.toml`, check PyPI for that exact version, and skip publishing if it already exists.
- Do not add automatic version bumping to publish workflows unless the release process is explicitly redesigned; package version bumps should be reviewed in PRs.
- Use the repository `PYPI_API_TOKEN` Actions secret for token-based PyPI publishing. Never commit token values or package credentials.
- Keep publish workflows scoped by `paths` to the package directory and the workflow file so unrelated merges do not trigger package release jobs.
26 changes: 26 additions & 0 deletions .github/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Maintainer notes

This file is for repository maintainers. Do not copy release-process notes into an integration package `README.md`: package READMEs are used as PyPI project descriptions.

## Python integration releases

Python integration publish workflows do not bump package versions automatically. Each workflow reads the package name and version from that integration's `pyproject.toml`, checks PyPI for that exact version, and publishes only when the version does not already exist.

To publish a new version:

1. Update the package version in the relevant `pyproject.toml`.
2. Commit the version bump with the code change.
3. Merge to `main`.
4. Let the push-to-`main` publish workflow run.

Current Python package version files:

| Integration | Package | Version file |
| --- | --- | --- |
| Google ADK | `tinyfish-adk` | `google-adk/pyproject.toml` |
| CrewAI | `tinyfish-web-agent` | `crew-ai/pyproject.toml` |
| LangChain | `langchain-tinyfish` | `langchain/pyproject.toml` |

PyPI versions are immutable. If a version has already been published, bump to a new version such as a patch release before merging.

The publish workflows use the repository `PYPI_API_TOKEN` Actions secret provisioned through `github-control`; never commit token values or package credentials.
41 changes: 41 additions & 0 deletions .github/workflows/crew-ai-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: CrewAI CI

on:
pull_request:
branches: [main]
paths:
- ".github/workflows/crew-ai-ci.yml"
- "crew-ai/**"
push:
branches: [main]
paths:
- ".github/workflows/crew-ai-ci.yml"
- "crew-ai/**"

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest

defaults:
run:
working-directory: crew-ai

steps:
- uses: actions/checkout@v6

- uses: actions/setup-python@v6
with:
python-version: "3.12"

- run: python -m pip install --upgrade pip

- run: python -m pip install -e . -r requirements-dev.txt

- run: make lint

- run: make test

- run: python -m build
106 changes: 106 additions & 0 deletions .github/workflows/crew-ai-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: CrewAI CD - Publish to PyPI

on:
push:
branches: [main]
paths:
- ".github/workflows/crew-ai-publish.yml"
- "crew-ai/**"

jobs:
build:
name: Build distribution
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
version-exists: ${{ steps.version.outputs.exists }}
package-version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"

- name: Install uv
uses: astral-sh/setup-uv@v8.1.0

- name: Check PyPI version availability
id: version
working-directory: ./crew-ai
run: |
PACKAGE=$(python -c "import tomllib; f=open('pyproject.toml','rb'); d=tomllib.load(f); print(d['project']['name'])")
VERSION=$(python -c "import tomllib; f=open('pyproject.toml','rb'); d=tomllib.load(f); print(d['project']['version'])")
echo "package=${PACKAGE}" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
RESULT=$(python - "$PACKAGE" "$VERSION" <<'PY'
import sys
import urllib.error
import urllib.parse
import urllib.request

package = sys.argv[1]
version = sys.argv[2]
url = f"https://pypi.org/pypi/{urllib.parse.quote(package)}/{version}/json"
try:
with urllib.request.urlopen(url, timeout=10):
pass
except urllib.error.HTTPError as exc:
if exc.code == 404:
print("missing")
sys.exit(0)
print(f"PyPI returned HTTP {exc.code} while checking {url}", file=sys.stderr)
sys.exit(2)
except Exception as exc:
print(f"Failed to check PyPI for {url}: {exc}", file=sys.stderr)
sys.exit(2)
print("exists")
PY
)
case "$RESULT" in
exists)
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "${PACKAGE} ${VERSION} already exists on PyPI; skipping publish."
;;
missing)
echo "exists=false" >> "$GITHUB_OUTPUT"
echo "${PACKAGE} ${VERSION} is available on PyPI; building distribution."
;;
*)
echo "Unexpected PyPI availability result: $RESULT"
exit 1
;;
esac

- name: Build package
if: steps.version.outputs.exists == 'false'
working-directory: ./crew-ai
run: uv build

- name: Upload distribution artifact
if: steps.version.outputs.exists == 'false'
uses: actions/upload-artifact@v7
with:
name: python-package-distributions
path: crew-ai/dist/

publish-pypi:
name: Publish to PyPI
needs: build
if: needs.build.outputs.version-exists == 'false'
runs-on: ubuntu-latest
steps:
- name: Download distributions
uses: actions/download-artifact@v8
with:
name: python-package-distributions
path: dist/

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/
password: ${{ secrets.PYPI_API_TOKEN }}
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
.claude/
google-adk/
langchain/
supabase/
zapier/
test_tinyfish_crew/
crew-ai/examples/
19 changes: 19 additions & 0 deletions crew-ai/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info

# Virtual environments
.venv

# Lockfiles
uv.lock

# Environment variables
.env

# Claude Code
.claude/
15 changes: 15 additions & 0 deletions crew-ai/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.PHONY: format lint test clean

format:
ruff format src tests
ruff check --fix src tests

lint:
ruff check src tests

test:
pytest tests -v

clean:
rm -rf build dist *.egg-info .pytest_cache .ruff_cache
find . -type d -name __pycache__ -exec rm -rf {} +
142 changes: 142 additions & 0 deletions crew-ai/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Tinyfish — CrewAI Tool

Automate any website using natural language with [TinyFish Web Agent](https://tinyfish.ai). Extract data, fill forms, click buttons, navigate pages, and more — all described in plain English.

## Installation

Clone the repo and install the tool locally:

```bash
git clone https://github.com/tinyfish-io/tinyfish-web-agent-integrations.git
cd tinyfish-web-agent-integrations/crew-ai
pip install -e .
```

## Setup

Get your API key at [agent.tinyfish.ai/api-keys](https://agent.tinyfish.ai/api-keys), then set it as an environment variable:

```bash
export TINYFISH_API_KEY="your-api-key"
```

Or add it to your `.env` file:

```dotenv
TINYFISH_API_KEY=your-api-key
```

`tinyfish-web-agent` automatically tags TinyFish SDK requests as originating
from `crew-ai`. You do not need to set `TF_API_INTEGRATION` yourself.

## Tools

| Tool | Description |
|------|-------------|
| `TinyfishRun` | Run a browser automation synchronously. Best for quick tasks (<30s). |
| `TinyfishRunAsync` | Start an automation asynchronously. Returns a `run_id` immediately. |
| `TinyfishGetRun` | Check status and get results of a run by its `run_id`. |
| `TinyfishListRuns` | List recent automation runs, optionally filtered by status. |
| `TinyfishSearch` | Search the web and return structured results. |
| `TinyfishFetch` | Fetch clean content from one or more URLs. |
| `TinyfishBrowserSession` | Create a remote browser session and return connection URLs. |

`Tinyfish` is an alias for `TinyfishRun`.

## Usage

### Basic — synchronous run

```python
from crewai import Agent
from tinyfish_web_agent import TinyfishRun

agent = Agent(
role="Web Researcher",
goal="Find and extract information from websites",
tools=[TinyfishRun()],
)
```

### Async workflow — start and check

```python
from tinyfish_web_agent import TinyfishRunAsync, TinyfishGetRun

agent = Agent(
role="Data Collector",
goal="Collect data from multiple sources efficiently",
tools=[TinyfishRunAsync(), TinyfishGetRun()],
)
```

### Search and fetch

```python
from tinyfish_web_agent import TinyfishSearch, TinyfishFetch

agent = Agent(
role="Web Researcher",
goal="Search the web and read relevant pages",
tools=[TinyfishSearch(), TinyfishFetch()],
)
```

### All tools at once

```python
from tinyfish_web_agent import (
TinyfishRun,
TinyfishRunAsync,
TinyfishGetRun,
TinyfishListRuns,
TinyfishSearch,
TinyfishFetch,
TinyfishBrowserSession,
)

agent = Agent(
role="Web Automation Specialist",
goal="Automate any web task",
tools=[
TinyfishRun(),
TinyfishRunAsync(),
TinyfishGetRun(),
TinyfishListRuns(),
TinyfishSearch(),
TinyfishFetch(),
TinyfishBrowserSession(),
],
)
```

## Configuration

All tools accept these optional constructor parameters:

| Parameter | Description |
|-----------|-------------|
| `api_key` | TinyFish API key. Falls back to `TINYFISH_API_KEY` env var. |
| `proxy_country` | Route through a proxy in this country (`US`, `GB`, `CA`, `DE`, `FR`, `JP`, `AU`). |

The package also sets `TF_API_INTEGRATION=crew-ai` internally so requests are
attributed automatically.

```python
tool = TinyfishRun(api_key="sk-...", proxy_country="US")
```

## Example goals

```text
"Extract all product names, prices, and ratings from this page"
"Fill the contact form with name 'Jane Doe' and email 'jane@example.com', then submit"
"Click 'Next Page' 3 times, extracting all listings from each page"
"Log in with the provided credentials, then extract the dashboard data"
```

## Support

- [TinyFish Docs](https://docs.tinyfish.ai)
- [CrewAI Docs](https://docs.crewai.com)
- [Discord](https://discord.gg/agentql)
Loading