Metadata-Version: 2.4
Name: foxygpu
Version: 0.2.0
Summary: Run local code on Google Colab's free GPU
Author-email: Shamshad Choudhary <chaudhary.s.shamshad07@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/Shamshadz/FoxyGPU
Project-URL: Repository, https://github.com/Shamshadz/FoxyGPU
Project-URL: Issues, https://github.com/Shamshadz/FoxyGPU/issues
Keywords: colab,gpu,ollama,deployment,cli,fastapi
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: typer>=0.9
Requires-Dist: requests>=2.31
Requires-Dist: websockets>=12.0
Requires-Dist: rich>=13.0
Requires-Dist: pyyaml>=6.0
Provides-Extra: dev
Requires-Dist: pytest>=7.4; extra == "dev"
Requires-Dist: pytest-timeout>=2.2; extra == "dev"
Requires-Dist: fastapi>=0.100; extra == "dev"
Requires-Dist: uvicorn>=0.23; extra == "dev"
Requires-Dist: python-multipart>=0.0.6; extra == "dev"
Dynamic: license-file

# FoxyGPU

[![Tests](https://github.com/Shamshadz/FoxyGPU/actions/workflows/tests.yml/badge.svg)](https://github.com/Shamshadz/FoxyGPU/actions/workflows/tests.yml)

Run local code — FastAPI backends, frontend dev servers, or anything else — on
Google Colab's free-tier GPU, driven entirely from your own machine.

## How it works

`foxygpu launch` opens FoxyGPU's own runner notebook directly in Colab — no
manual notebook upload, and **no GitHub account or token needed**. The notebook
is identical for every user (nothing personalized is baked in), so it's just
committed straight into this repo and Colab loads it from there; Colab can open
any public GitHub file with zero authentication. That notebook starts a
control-plane agent, reached from your machine over a [Cloudflare
Tunnel](https://github.com/cloudflare/cloudflared) quick tunnel (no account needed).
The local `foxygpu` CLI talks to that agent to upload your project, start it with a
shell command, stream its logs, and expose whatever port it's listening on with its
own public URL.

```
┌───────────────────┐
│    foxygpu CLI    │
│  (local machine)  │
└───────────────────┘
          │
          │  HTTPS/WSS via a Cloudflare Tunnel
          │  (no account needed)
          ▼
┌──────────────────────────────┐
│         foxygpu_agent        │
│    (Colab VM, GPU runtime)   │
│                              │
│      spawns your process     │
│  (uvicorn / npm / anything)  │
└──────────────────────────────┘
```

Every agent endpoint requires a bearer token generated at startup — the tunnel URL
alone isn't enough to run anything on your VM.

## Install

Everything — the CLI and the Colab agent it deploys — ships as one Python package:

```bash
pip install -e .
```

## Setup

### 1. Launch the Colab runtime

```bash
foxygpu launch
```

This just opens Colab straight to FoxyGPU's own committed notebook — nothing to
sign in to, no token, no account needed.

In the browser: select a GPU runtime (`Runtime > Change runtime type > GPU`),
run all cells. The last cell prints a `foxygpu connect ...` command — copy it.

Prefer not to open a link we host at all? `foxygpu notebook ./FoxyGPU_Runner.ipynb`
writes the same notebook to a local file so you can read it yourself and upload
it to Colab manually (`File > Upload notebook`) — zero network calls to anything
but Colab itself.

If you've modified `foxygpu/agent_source.py` locally and want the one-click
experience for your own version without forking/hosting a repo, `foxygpu launch
--gist` publishes your copy to a GitHub Gist instead — that path does need a
**classic** GitHub token with the `gist` scope (fine-grained tokens don't support
the Gists API and fail with a 404); create one at https://github.com/settings/tokens
-> "Generate new token (classic)".

### 2. Connect

Paste the command Colab printed, e.g.:

```bash
foxygpu connect https://xxxx.trycloudflare.com --token <token>
```

## Usage

Run a project (any language/framework — it's just a shell command). The agent
picks a free port for you and injects it as `$PORT` — reference that instead of
a literal number so you never have to think about which ports are free or
reserved:

```bash
foxygpu run ./my-fastapi-app --cmd 'pip install -r requirements.txt && uvicorn main:app --host 0.0.0.0 --port $PORT' --expose
```

> **Shell note**: use **single quotes** around the `--cmd` value, exactly as
> above, in PowerShell, bash, or zsh — all three treat single quotes as a
> literal string, so `$PORT` and `&&` reach the remote command unchanged. This
> does **not** work in `cmd.exe` (no concept of single-quoted literal strings,
> and it interprets `&&` itself) — use PowerShell or a bash-like shell instead.

### One-command deploy (`foxygpu.yaml`)

Once you know your `--cmd`, save it to a `foxygpu.yaml` in your project so you
don't have to retype it:

```yaml
runtime: colab   # only "colab" works today — see the multi-runtime issue
gpu: true
command: pip install -r requirements.txt && uvicorn main:app --host 0.0.0.0 --port $PORT
```

Then just:

```bash
foxygpu deploy
```

`deploy` behaves like `redeploy` (stops the previous deployment of the same
project first) and exposes automatically by default. If there's no
`foxygpu.yaml` yet, it tries to auto-detect one for you — a FastAPI/Flask app
next to `requirements.txt`, or a Vite/Next.js/generic npm project next to
`package.json` — writes it, and deploys. If nothing's recognized, it tells you
so rather than guessing; write the file yourself or use `run`/`redeploy` with
an explicit `--cmd` instead.

Logs stream live, and the CLI prints which port got assigned. `--expose`
immediately opens a public tunnel once the process starts and prints the URL.
If you skip it, expose later — with no argument it defaults to the most
recently started process's port:

```bash
foxygpu expose
```

### Databases, external APIs, and secrets

An **externally-hosted** database or API (a hosted Postgres, a third-party
API, an OAuth provider) just works — the Colab VM has normal outbound
internet access, no different from any other server. A **local-only**
database your app points at via `localhost` won't be reachable from the VM;
either run it on the VM too as part of your command (state is wiped when the
session ends) or point at an externally-hosted instance instead.

For secrets (API keys, DB passwords) — **don't** embed them with
`export SECRET=x && ...` in `--cmd`: the full command is stored and shown
verbatim in `foxygpu status` and echoed as the first line of streamed logs.
Instead, use `--env`/`--env-file`, which inject them directly as process
environment variables without ever appearing in `--cmd`, `status`, or the logs
(only the variable *names* are ever shown, never the values):

```bash
foxygpu run ./my-app --cmd 'uvicorn main:app --host 0.0.0.0 --port $PORT' --env DATABASE_URL=postgres://... --env-file .env
```

`--env` (repeatable) and `--env-file` (a `.env`-style `KEY=VALUE` file) both
work on `run`, `redeploy`, and `deploy`; `--env` wins on a conflicting key.
In `foxygpu.yaml`, use `env_file: .env` for the same thing — **avoid** putting
real secret values directly under an inline `env:` mapping in a file you
commit to git; `env_file` should point at a local, gitignored file instead.

One more gotcha specific to this tool: if your app does OAuth login, the
callback URL is normally registered as a fixed value with the provider.
FoxyGPU's exposed URL is a fresh random `*.trycloudflare.com` address on every
`expose`/redeploy, which breaks flows expecting a stable callback URL — not
something to work around today, just worth knowing going in.

Edited your code and want to update what's running? `foxygpu run` always
starts a fresh, separate deployment — it won't stop whatever's already running
first. Use `redeploy` instead, which stops the previous deployment of the same
project (matched by directory name, or `--name` if you gave one) before
starting the new one:

```bash
foxygpu redeploy ./my-fastapi-app --cmd 'pip install -r requirements.txt && uvicorn main:app --host 0.0.0.0 --port $PORT' --expose
```

If the new run lands back on the same port — likely, since stopping the old
one just freed it — an existing exposed URL for that port keeps working
automatically, no need to `expose` again.

Check GPU status and running processes (including their assigned ports):

```bash
foxygpu status
```

Stream logs for a process, reconnect after detaching, or stop it (add `--all`
to stop everything):

```bash
foxygpu logs <process-id>
foxygpu stop <process-id>
foxygpu stop --all
```

Pressing Ctrl+C while logs are streaming only detaches your terminal — the
remote process keeps running on Colab. The CLI reminds you of the `logs`/`stop`
commands above when you do this.

### More examples

Node.js app (read `process.env.PORT` in your server code):
```bash
foxygpu run ./my-node-app --cmd 'npm install && node server.js' --expose
```

Frontend dev server (Vite/React/etc.):
```bash
foxygpu run ./my-frontend --cmd 'npm install && npm run dev -- --host 0.0.0.0 --port $PORT' --expose
```

One-off script or training job (no server, so skip `--expose`):
```bash
foxygpu run ./train-job --cmd 'pip install -r requirements.txt && python train.py'
```

See `foxygpu run --help` for this same set of examples from the CLI.

### Full working example

[examples/ollama-chat](examples/ollama-chat/) is a complete ChatGPT-style app
(FastAPI backend + a small frontend) that runs a real GPU-backed Ollama model
on Colab — a good first thing to deploy to confirm your setup end-to-end.

## Excluding files from upload

By default `.git`, `node_modules`, `__pycache__`, `venv`/`.venv`, and a few build
directories are excluded when zipping your project. Add more patterns by copying
[.foxygpuignore.default](.foxygpuignore.default) to `.foxygpuignore` in your project
root.

## Development

The test suite runs a real instance of the agent locally (no Colab needed) and
drives it over HTTP/WebSocket, plus in-process CLI tests via Typer's test
runner. It never touches your real `~/.foxygpu/config.json` — every test gets
an isolated one automatically.

```bash
pip install -e ".[dev]"
pytest
```

## Known limitations

- `foxygpu deploy`'s auto-detection is deliberately limited (FastAPI/Flask +
  requirements.txt, or Vite/Next.js/generic npm + package.json) — a
  confidently wrong guess is worse than admitting it doesn't recognize your
  project. When it doesn't, write `foxygpu.yaml` by hand.
- `runtime:` in `foxygpu.yaml` only supports `colab` today. Kaggle/RunPod/
  Lambda/local-GPU support is tracked as a future multi-runtime effort.
- FastAPI is only used to build the agent itself (the control-plane server running
  inside Colab) — it is not a requirement for what you deploy. `foxygpu run` just
  executes whatever shell command you give it via `--cmd`, so any language or
  framework the Colab VM can run works (Node, Go, Rust, Flask, Streamlit, a plain
  training script, anything), not just Python or FastAPI.
- Colab free-tier sessions are ephemeral (idle timeout, ~12h cap). If the session
  restarts, run the notebook again (re-run `foxygpu launch` if you closed the tab)
  and `foxygpu connect` again with the new URL/token.
- The control URL and token grant code execution on the VM — don't share them.
- `foxygpu launch --gist` (the opt-in path) publishes to a **public** Gist (Gist
  API has no private-but-linkable option) — it contains no secrets (the agent's
  token is generated fresh at runtime in Colab, not baked into the notebook), but
  anyone who finds the Gist URL can see and re-run it against their own Colab.
- The agent source lives at `foxygpu/agent_source.py`. The committed
  `notebook/FoxyGPU_Runner.ipynb` embeds a copy of it — regenerate that file with
  `foxygpu notebook notebook/FoxyGPU_Runner.ipynb` and commit it after changing
  the agent, since (unlike `--gist`, which always embeds the current source) the
  default `launch` opens the version already committed to this repo.
- **Port 8765 is reserved** — the agent itself listens there inside Colab. You
  shouldn't need to think about this: reference `$PORT` in your `--cmd` (see
  Usage) and the agent hands you a free port automatically, preferring `9876`
  and falling back to another free one if that's taken (e.g. a second
  concurrent project).
- **A command with an animated progress bar can hang your whole `--cmd` chain
  forever.** Some CLI tools (Ollama's `pull` is one — see
  [examples/ollama-chat](examples/ollama-chat/README.md)) never exit their
  progress renderer when run through a non-interactive pipe like the one the
  agent uses to capture output, even though the real work finishes. Since
  `foxygpu run` chains commands with `&&`, a hung one blocks everything after
  it. If a step seems stuck, check whether it actually finished (e.g. via a
  second `foxygpu run` with a quick status-checking command) before assuming
  it's slow — the fix is usually prefixing that one command with `TERM=dumb`.
