Metadata-Version: 2.4
Name: stitchflow-local-agent
Version: 0.3.18
Summary: Local Stitchflow daemon for browser automation tasks and data sync
Requires-Python: <3.14,>=3.11
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.27.0
Requires-Dist: keyring>=25.0.0
Requires-Dist: undetected-chromedriver>=3.5.5
Requires-Dist: setuptools>=68
Requires-Dist: pydantic>=2.9.0
Requires-Dist: rumps>=0.4.0; sys_platform == "darwin"
Requires-Dist: pyobjc-framework-Cocoa>=10.0; sys_platform == "darwin"
Requires-Dist: questionary>=2.1.1
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: ruff>=0.4.0; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"

# stitchflow-local-agent

Local daemons that automate SaaS admin tasks for a Stitchflow workspace:

- **Action agent** — executes user removals and role changes in ChatGPT and Calendly.
- **Data sync** — exports user CSVs from ChatGPT and Calendly, then uploads them to Stitchflow.

Both run headlessly via `agent-browser` (Playwright) using a shared persistent browser profile for session management.

## Install

```bash
pip install "git+https://github.com/Stitchflow-website-webapps/stitchflow-local-agent.git"
```

Two CLI commands become available:

| CLI | Purpose |
|---|---|
| `stitchflow-agent` | Action execution — user removal, role changes |
| `stitchflow-sync` | Daily data sync — app CSV export and Stitchflow upload |

## Quick start

```bash
# 1. First-time setup — installs agent-browser, opens browsers for login
stitchflow-agent setup
stitchflow-sync setup

# 2. Single run
stitchflow-agent run
stitchflow-sync run

# 3. Foreground daemon commands (for local testing)
stitchflow-agent daemon
stitchflow-sync daemon

# 4. Check health
stitchflow-agent status
stitchflow-sync status
```

## Package structure

```
stitchflow_local_agent/
  common/                  shared config, browser helpers, daemon, logging, installer
  actions/
    api_client.py          Stitchflow task API client
    dispatcher.py          task routing by (integration, action)
    chatgpt/               ChatGPT: remove_user
    calendly/              Calendly: remove_user, change_role
  sync/
    stitchflow_upload.py   generic CSV → Stitchflow UI upload
    calendly/              Calendly user export (UI-based)
    chatgpt/               ChatGPT user export (API via browser session)
  agent_cli.py             stitchflow-agent entrypoint
  sync_cli.py              stitchflow-sync entrypoint
```

---

## Action agent (`stitchflow-agent`)

Polls the Stitchflow API for pending tasks, claims them, and executes each one through browser automation.

```bash
stitchflow-agent setup    # auto-install agent-browser + Chromium, configure env, open login
stitchflow-agent login    # re-authenticate (auto-installs if missing)
stitchflow-agent run      # process all pending tasks once
stitchflow-agent daemon   # poll continuously (foreground; launchd/task manages background)
stitchflow-agent status   # show config, daemon PID, last task results
```

### Supported actions

| Integration | Action | Description |
|---|---|---|
| ChatGPT | `remove_user` | Remove a team member by email from the ChatGPT admin panel |
| Calendly | `remove_user` | Remove a user from the Calendly organization |
| Calendly | `change_role` | Change a user's role (Admin ↔ User) |

### Extending

Register a new handler in `actions/dispatcher.py`:

```python
HANDLERS[("app_name", "action_name")] = handler_function
```

---

## Data sync (`stitchflow-sync`)

Exports user CSVs from connected apps and uploads each to the matching Stitchflow connection. All configured apps are synced in a single cycle.

```bash
stitchflow-sync setup     # open Calendly, ChatGPT, and Stitchflow for login
stitchflow-sync run       # run one full sync cycle
stitchflow-sync daemon    # local foreground loop (use launchd/task scheduler for production background runs)
stitchflow-sync status    # show config, logs, daemon status, recent CSVs
```

### Supported sync sources

| App | Export method | Notes |
|---|---|---|
| Calendly | UI automation — click Export → Active → download ZIP | CSV extracted from ZIP automatically |
| ChatGPT | API via browser session — JS fetches `/api/auth/session`, paginates the users API | Account ID auto-discovered from session |

### Sync cycle

Each cycle runs sequentially: Calendly export → upload → ChatGPT export → upload → close browser.

**Calendly**

1. Opens Calendly admin → clicks Export → Active → downloads ZIP → extracts CSV.
2. Opens Stitchflow → Connections → searches "Calendly" → uploads CSV → polls until "Connected".

**ChatGPT**

1. Opens ChatGPT → executes JS that fetches the auth token, auto-discovers the workspace account ID from the session, and paginates the users API.
2. Converts the returned JSON to CSV in Python, saves to `~/.stitchflow/data-sync/chatgpt/`.
3. Opens Stitchflow → Connections → searches "ChatGPT" → uploads CSV → polls until "Connected".

### Audit logs

JSONL logs under `~/.stitchflow/logs/`:

| File | Records |
|---|---|
| `calendly-export.jsonl` | Each Calendly export — timestamp, CSV path, file size (success) or error (failure) |
| `chatgpt-export.jsonl` | Each ChatGPT export — timestamp, CSV path, file size, user count (success) or error (failure) |
| `data-sync-audit.jsonl` | Each Stitchflow upload per app — timestamp, CSV path (success) or error (failure) |
| `data-sync.log` | Full sync cycle run log |

### Extending

Add a new sync source in three steps:

1. Create `sync/<app>/export.py` with an `async def export_<app>_users() -> str | None` function.
2. The upload side (`sync/stitchflow_upload.py`) is already generic — pass any `app_name`.
3. Wire the export into `sync_cli.py`'s `run_sync()` via `_sync_app()`.

---

## Environment variables

| Variable | Required | Default | Description |
|---|---|---|---|
| `STITCHFLOW_API_URL` | Yes | `http://localhost:3030` | Stitchflow API base URL |
| `STITCHFLOW_WORKSPACE_ID` | Yes | — | Stitchflow workspace ID |
| `STITCHFLOW_API_KEY` | No | — | API key (prefer `keyring` storage) |
| `AGENT_BROWSER_PROFILE` | No | `~/.agent-browser-profile` | Persistent Chromium profile path |
| `CHATGPT_ACCOUNT_ID` | No | auto-discovered | Override for ChatGPT workspace ID (normally detected from session) |
| `STITCHFLOW_AGENT_POLL_INTERVAL_SECONDS` | No | `60` | Action daemon poll interval |
| `STITCHFLOW_AGENT_POLL_MAX_IDLE_SECONDS` | No | `900` | Action daemon max idle backoff |
| `STITCHFLOW_AGENT_POLL_MAX_ERROR_SECONDS` | No | `300` | Action daemon max error backoff |
| `STITCHFLOW_SYNC_INTERVAL_SECONDS` | No | `86400` | Sync daemon interval (24 h) |

All config is persisted to `~/.stitchflow/agent.env` during `setup`.

## Daemon scheduling (recommended for production)

See [`DAEMON_SETUP.md`](DAEMON_SETUP.md) for macOS `launchd` and Windows Task Scheduler installation guides covering both daemons.

## Security

- **API keys** — stored in the OS credential manager (macOS Keychain / Windows DPAPI) via `keyring`. Falls back to `~/.stitchflow/agent.env` if `keyring` is unavailable.
- **Browser sessions** — maintained in a persistent profile at `~/.agent-browser-profile`. No credentials are stored in code or config files.
- **Action proof artifacts** — `before.png`, `after.png`, and `audit.json` per task under `~/.stitchflow/artifacts/`.
- **Audit trail** — rolling log at `~/.stitchflow/logs/audit.log`.

## Dependencies

| Package | Purpose |
|---|---|
| Python 3.11 - 3.13 | Runtime |
| `httpx` | Async HTTP client for the Stitchflow API |
| `keyring` | OS credential manager integration |
| `agent-browser` | Playwright-based browser automation CLI (installed via npm) |

`stitchflow-agent setup` / `stitchflow-sync setup` auto-installs `agent-browser` via `npm install -g agent-browser` (or `brew install agent-browser` on macOS), then runs `agent-browser install` to download Chromium.

## Building and releasing

All Python source files are compiled to native extensions via Cython before distribution. Published wheels contain only compiled binaries — no `.py` or `.c` source files.

### Release workflow

1. Bump the version in `pyproject.toml`.
2. Commit the version bump, then create the matching tag from that same commit: `git commit -am "Release v<version>" && git tag v<version> && git push && git push --tags`.
3. GitHub Actions builds compiled wheels for macOS (ARM + Intel), Linux, and Windows across Python 3.11–3.13.
4. A verification step checks that no source files leaked into any wheel.
5. The workflow validates that `GITHUB_REF` matches `[project].version` before publishing.
6. Wheels are published to PyPI automatically.

### Local testing

```bash
# Install build deps
pip install "cython>=3.0" setuptools wheel

# Compile extensions in-place (for development testing)
python setup.py build_ext --inplace

# Build a wheel for your current platform
pip wheel . --no-deps -w dist/

# Verify no source files in the wheel
unzip -l dist/*.whl | grep -E '\.(py|c)$'
# (should return no results)
```

### CI configuration

The build pipeline is defined in `.github/workflows/build-wheels.yml`. It requires a `PYPI_API_TOKEN` secret configured in the GitHub repository settings under an environment named `pypi`.

## Further reading

- [`ARCHITECTURE.md`](ARCHITECTURE.md) — internals: dispatcher, browser layer, sync pipeline, error handling, file layout.
- [`DAEMON_SETUP.md`](DAEMON_SETUP.md) — launchd / Task Scheduler install guides.
