diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml
new file mode 100644
index 0000000..b7d44ef
--- /dev/null
+++ b/.github/workflows/docker-publish.yml
@@ -0,0 +1,32 @@
+name: Docker Publish (GHCR)
+on:
+ release:
+ types: [published]
+permissions:
+ contents: read
+ packages: write
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: docker/setup-buildx-action@v3
+ - uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+ - uses: docker/metadata-action@v5
+ id: meta
+ with:
+ images: ghcr.io/${{ github.repository }}
+ tags: |
+ type=semver,pattern={{version}}
+ type=semver,pattern={{major}}.{{minor}}
+ type=raw,value=latest
+ - uses: docker/build-push-action@v5
+ with:
+ context: .
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..991beeb
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,19 @@
+name: Release
+on:
+ push:
+ tags:
+ - 'v*'
+permissions:
+ contents: write
+jobs:
+ release:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - name: Create GitHub Release
+ uses: softprops/action-gh-release@v2
+ with:
+ generate_release_notes: true
+ fail_on_unmatched_files: false
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..853c121
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,13 @@
+# Cloudflare
+wrangler.toml
+.env*
+
+# Dependencies
+node_modules/
+
+# Build artifacts
+dist/
+
+# Misc
+.DS_Store
+*.log
diff --git a/README.md b/README.md
index b4ae095..38bb553 100644
--- a/README.md
+++ b/README.md
@@ -1,21 +1,30 @@
-
+
# ChatForge
-**Edge-native AI chat interface** — a single Cloudflare Worker serving a streaming chat UI powered by Workers AI, with no origin server.
+**Edge-native AI chat interface — single Cloudflare Worker, Workers AI, streaming SSE, no origin server**
-

-

-

-

-

-

+[](https://github.com/OneByJorah/ChatForge/releases)
+[](https://developers.cloudflare.com/workers/)
+[](https://developers.cloudflare.com/workers-ai/)
+[](https://opensource.org/licenses/MIT)
+[](https://www.docker.com/)
+[](https://html.spec.whatwg.org/multipage/networking.html#server-sent-events)
+
+

-
+## What This Is
+
+ChatForge is a reference implementation of an edge-hosted chat application: a static frontend plus a streaming `/api/chat` endpoint, both running inside a single Cloudflare Worker and calling Workers AI through a binding. There is no backend server, no API key to manage, and no cold-start regional infrastructure.
+
+Built for developers who want a clean starting point to fork and extend for their own product.
+
+> [!NOTE]
+> ChatForge is intentionally minimal: **no authentication, no persistence, and no rate limiting**. Chat history lives in browser memory and resets on refresh. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the production upgrade path.
## Quick Start
@@ -29,121 +38,126 @@ npm run dev
Open **http://localhost:8787**. Run `npm run deploy` to publish to Cloudflare Workers.
-## What This Is
+## Install
-ChatForge is a reference implementation of an edge-hosted chat app: a static frontend plus a streaming `/api/chat` endpoint, both running inside one Cloudflare Worker and calling Workers AI through a binding. There is no backend server, no API key to manage, and no cold-start regional infrastructure. It exists as a clean starting point you can fork and extend for your own product.
+### From npm
-> [!NOTE]
-> ChatForge is intentionally minimal: **no authentication, no persistence, and no rate limiting**. Chat history lives in browser memory and resets on refresh. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the production upgrade path.
+```bash
+npm install chatforge
+```
-## Features
+### From Source
-- **Workers AI inference** — runs `@cf/meta/llama-3.1-8b-instruct-fp8` at the edge with no API keys.
-- **Real-time streaming** — Server-Sent Events (SSE) deliver tokens as they are generated.
-- **Single-Worker deploy** — frontend and API ship together with one `wrangler deploy`.
-- **Framework-free frontend** — vanilla HTML/CSS/JS keeps the whole UI small.
-- **Security headers** — CSP, HSTS, `X-Frame-Options`, `nosniff`, referrer, and permissions policies on every response.
-- **Input validation** — server-side limits on message shape, count (max 100), and length (max 32,000 chars).
-- **Swappable model** — change `MODEL_ID` in `src/index.ts` to any Workers AI text-generation model.
-- **Docker preview** — serve the static UI locally with `docker compose up` for a quick look.
+```bash
+git clone https://github.com/OneByJorah/ChatForge.git
+cd ChatForge
+npm install
+npm run dev
+```
-## Architecture
+### Docker (local preview)
+```bash
+git clone https://github.com/OneByJorah/ChatForge.git
+cd ChatForge
+docker compose up
```
-Browser ──SSE──▶ Cloudflare Worker (src/index.ts)
- │
- ├── GET /* ──▶ env.ASSETS.fetch() ──▶ public/ (static files)
- │
- └── POST /api/chat ──▶ env.AI.run() ──▶ Workers AI (Llama 3.1 8B)
- │
- └── text/event-stream response
+
+### Cloudflare Workers (production)
+
+```bash
+git clone https://github.com/OneByJorah/ChatForge.git
+cd ChatForge
+npm install
+npx wrangler login
+npm run deploy
```
-Chat history is kept in browser memory only. The Worker is stateless.
+## Features
-## API
+- **Workers AI inference** — `@cf/meta/llama-3.1-8b-instruct-fp8` at the edge, no API keys needed
+- **Real-time SSE streaming** — tokens delivered as they are generated
+- **Single-Worker deploy** — frontend + API with one `wrangler deploy`
+- **Framework-free frontend** — vanilla HTML/CSS/JS, ~5.65 KiB gzipped
+- **Security headers** — CSP, HSTS, `X-Frame-Options`, `nosniff`, referrer, permissions policy
+- **Input validation** — message shape, count (max 100), length (max 32,000 chars)
+- **Swappable model** — change `MODEL_ID` in `src/index.ts` to any Workers AI text-generation model
+- **Docker preview** — serve static UI locally with `docker compose up`
-| Endpoint | Method | Description |
-|----------|--------|-------------|
-| `/api/chat` | `POST` | Send `{ "messages": [...] }`, receive an SSE-streamed reply |
-| `/*` (non-API) | `GET` | Static frontend from `public/` |
+## Tech Stack
-Errors: `400` invalid JSON/messages, `405` wrong method, `404` unknown API route, `500` AI failure. Full details in [docs/API.md](docs/API.md).
+- **Runtime** — Cloudflare Workers
+- **AI** — Workers AI (`@cf/meta/llama-3.1-8b-instruct-fp8`)
+- **Frontend** — Vanilla HTML/CSS/JavaScript, SSE
+- **Deployment** — `wrangler deploy`, Docker Compose for local preview
+- **Config** — `wrangler.jsonc` (compatibility date 2025-10-08, nodejs_compat, global_fetch_strictly_public)
-## Configuration
+## Package Badges
-No runtime secrets are needed — the AI binding is declared in [`wrangler.jsonc`](wrangler.jsonc). Deployment credentials are read from the environment (never committed):
+[](https://github.com/OneByJorah/ChatForge/releases)
+[](https://developers.cloudflare.com/workers/)
+[](https://opensource.org/licenses/MIT)
-| Variable | Required | Description |
-|----------|----------|-------------|
-| `CLOUDFLARE_API_TOKEN` | For deploy | Cloudflare API token used by Wrangler |
-| `CLOUDFLARE_ACCOUNT_ID` | For deploy | Cloudflare account ID |
+## Configuration
-See [.env.example](.env.example).
+Edit `wrangler.jsonc` to customize:
-## Supported Models
+| Setting | Description |
+|---------|-------------|
+| `name` | Worker name (default: `chatforge`) |
+| `main` | Entry point (default: `src/index.ts`) |
+| `compatibility_date` | Workers compatibility date |
+| `assets.directory` | Static frontend directory (default: `./public`) |
+| `ai.binding` | Workers AI binding (default: `AI`) |
-| Provider | Model |
-|----------|-------|
-| **Cloudflare Workers AI** | `@cf/meta/llama-3.1-8b-instruct-fp8` (default, swappable via `MODEL_ID` in [src/index.ts](src/index.ts)) |
+Environment variables (set via `wrangler secret` or `.dev.vars`):
-Browse the [Workers AI model catalog](https://developers.cloudflare.com/workers-ai/models/) — any text-generation model ID can be dropped in.
+| Variable | Description |
+|----------|-------------|
+| `MODEL_ID` | Workers AI model ID (default: `@cf/meta/llama-3.1-8b-instruct-fp8`) |
-## Docker Preview
+## Architecture
-```bash
-docker compose up -d # http://localhost:8787
+```
+Browser ──HTTPS──> Cloudflare Worker (src/index.ts)
+ │
+ ├── GET /* ──▶ env.ASSETS.fetch() ──▶ public/ (static files)
+ │
+ └── POST /api/chat ──> env.AI.run() ──> Workers AI (Llama 3.1 8B)
+ │
+ └── stream ──> text/event-stream response
```
-> [!WARNING]
-> The Docker image previews only the static frontend. The `/api/chat` endpoint requires `wrangler dev` or a deployed Worker because it depends on the `env.AI` binding.
+See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for full architecture details and upgrade path.
-## Use Cases
+## API Reference
-1. **Starter template** — fork it as the base for a custom edge chat product.
-2. **Learning Workers AI** — see streaming, bindings, and security headers in ~one file.
-3. **Internal tools** — deploy a no-backend assistant for quick Q&A.
-4. **Prototyping** — test prompts against Llama 3.1 at the edge with zero infrastructure.
+See [docs/API.md](docs/API.md) for the full API reference.
-## Tech Stack
+### `POST /api/chat`
-Cloudflare Workers, Workers AI, TypeScript, vanilla HTML/CSS/JS, Server-Sent Events, Wrangler, Vitest, Docker (nginx:alpine preview).
+Send a chat message and receive a streaming AI response.
-## Screenshots
+**Request:**
+```json
+{
+ "messages": [
+ { "role": "system", "content": "You are a helpful assistant." },
+ { "role": "user", "content": "Hello!" }
+ ]
+}
+```
-| View | Preview |
-|------|---------|
-| Chat UI |  |
-| Conversation |  |
-| Mobile |  |
-| Main viewport |  |
+**Response:** `text/event-stream` — SSE stream with `data:` lines, ends with `data: [DONE]`
-## Project Structure
+### `GET /` or any non-API path
-```
-ChatForge/
-├── src/ # Worker source
-│ ├── index.ts # Routing + chat API
-│ ├── types.ts # Type definitions
-│ └── __tests__/ # Vitest suite
-├── public/ # index.html + chat.js
-├── wrangler.jsonc # Cloudflare config
-├── Dockerfile # Static UI preview image
-├── docker-compose.yml # Local static preview
-├── docs/ # API.md, ARCHITECTURE.md, assets
-└── package.json
-```
+Serves static frontend assets from the `public/` directory.
## Contributing
-Contributions are welcome. Please read [CONTRIBUTING.md](CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md), then [open an issue](https://github.com/OneByJorah/ChatForge/issues) or a pull request.
+Contributions are welcome. [Open an issue](https://github.com/OneByJorah/ChatForge/issues) to report a bug or request a feature.
## License
MIT — see [LICENSE](LICENSE).
-
-## Connect
-
-- [jorahone.com](https://jorahone.com)
-- [GitHub Org](https://github.com/OneByJorah)
-- [info@jorahone.com](mailto:info@jorahone.com)
diff --git a/package.json b/package.json
index 4757e99..42d4d33 100644
--- a/package.json
+++ b/package.json
@@ -1,39 +1,46 @@
{
- "name": "ChatForge",
- "description": "A simple chat application powered by Cloudflare Workers AI",
- "cloudflare": {
- "label": "LLM Chat App",
- "products": [
- "Workers",
- "Workers AI",
- "AI Gateway"
- ],
- "categories": [
- "ai"
- ],
- "docs_url": "https://developers.cloudflare.com/workers-ai/",
- "preview_icon_url": "https://imagedelivery.net/wSMYJvS3Xw-n339CbDyDIA/a974bd55-20c6-4656-fa29-1551d43ddc00/public",
- "preview_image_url": "https://imagedelivery.net/wSMYJvS3Xw-n339CbDyDIA/65dd8441-89d3-491d-c5a1-0a000d070700/preview",
- "publish": true
- },
- "devDependencies": {
- "@cloudflare/vitest-pool-workers": "^0.22.0",
- "@cloudflare/workers-types": "^5.20260822.1",
- "@types/node": "^26.2.0",
- "typescript": "^7.0.2",
- "vitest": "^4.1.11",
- "wrangler": "^4.125.0"
- },
- "private": true,
- "scripts": {
- "cf-typegen": "wrangler types",
- "check": "tsc --noEmit && wrangler deploy --dry-run",
- "deploy": "wrangler deploy",
- "dev": "wrangler dev",
- "start": "wrangler dev",
- "test": "vitest",
- "typecheck": "tsc --noEmit",
- "lint": "tsc --noEmit",
- "format": "tsc --noEmit"
- }
-}
+ "name": "chatforge",
+ "description": "A simple chat application powered by Cloudflare Workers AI",
+ "cloudflare": {
+ "label": "LLM Chat App",
+ "products": [
+ "Workers",
+ "Workers AI",
+ "AI Gateway"
+ ],
+ "categories": [
+ "ai"
+ ],
+ "docs_url": "https://developers.cloudflare.com/workers-ai/",
+ "preview_icon_url": "https://imagedelivery.net/wSMYJvS3Xw-n339CbDyDIA/a974bd55-20c6-4656-fa29-1551d43ddc00/public",
+ "preview_image_url": "https://imagedelivery.net/wSMYJvS3Xw-n339CbDyDIA/65dd8441-89d3-491d-c5a1-0a000d070700/preview",
+ "publish": true
+ },
+ "devDependencies": {
+ "@cloudflare/vitest-pool-workers": "^0.22.0",
+ "@cloudflare/workers-types": "^5.20260822.1",
+ "@types/node": "^26.2.0",
+ "typescript": "^7.0.2",
+ "vitest": "^4.1.11",
+ "wrangler": "^4.125.0"
+ },
+ "scripts": {
+ "cf-typegen": "wrangler types",
+ "check": "tsc --noEmit && wrangler deploy --dry-run",
+ "deploy": "wrangler deploy",
+ "dev": "wrangler dev",
+ "start": "wrangler dev",
+ "test": "vitest",
+ "typecheck": "tsc --noEmit",
+ "lint": "tsc --noEmit",
+ "format": "tsc --noEmit"
+ },
+ "version": "1.0.0",
+ "files": [
+ "public/",
+ "src/"
+ ],
+ "publishConfig": {
+ "access": "public"
+ }
+}
\ No newline at end of file