Metadata-Version: 2.4
Name: parlor
Version: 0.5.0
Summary: Parlor - a private parlor for AI conversation
License-Expression: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: fastapi>=0.104.0
Requires-Dist: uvicorn[standard]>=0.24.0
Requires-Dist: sse-starlette>=1.8.0
Requires-Dist: openai>=1.12.0
Requires-Dist: mcp>=1.0.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: filetype>=1.2.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: httpx>=0.25.0; extra == "dev"
Requires-Dist: ruff>=0.4.0; extra == "dev"
Requires-Dist: mypy>=1.8.0; extra == "dev"
Requires-Dist: pip-audit>=2.7.0; extra == "dev"

<p align="center">
  <img src="https://img.shields.io/pypi/v/parlor?style=for-the-badge&color=3b82f6&labelColor=0f1117" alt="PyPI Version">
  <img src="https://img.shields.io/pypi/pyversions/parlor?style=for-the-badge&color=10b981&labelColor=0f1117" alt="Python Versions">
  <img src="https://img.shields.io/github/actions/workflow/status/troylar/parlor/test.yml?style=for-the-badge&label=tests&color=7c3aed&labelColor=0f1117" alt="Tests">
  <img src="https://img.shields.io/github/license/troylar/parlor?style=for-the-badge&color=e8913a&labelColor=0f1117" alt="License">
</p>

<h1 align="center">
  <br>
  Parlor
  <br>
</h1>

<h3 align="center">A private parlor for AI conversation.</h3>

<p align="center">
  Self-hosted ChatGPT-style web UI that connects to any OpenAI-compatible API.<br>
  <strong>Install with pip. Run locally. Own your data.</strong>
</p>

<p align="center">
  <a href="#-quick-start">Quick Start</a> &bull;
  <a href="#-features">Features</a> &bull;
  <a href="#-themes">Themes</a> &bull;
  <a href="#-security">Security</a> &bull;
  <a href="#-api">API</a>
</p>

---

## Why Parlor?

Your company's AI chat UI sucks. You know it. We know it. Parlor replaces it with something you'll actually want to use.

It connects to **any** OpenAI-compatible endpoint --- your company's internal API, OpenAI, Azure, Ollama, LM Studio, or anything else that speaks the OpenAI protocol. Built to [OWASP ASVS L1](SECURITY.md) standards because your conversations deserve real security, not security theater.

> **One command. No cloud. No telemetry. No compromise.**

```bash
pip install parlor
```

---

## Quick Start

**1. Install**

```bash
pip install parlor
```

**2. Configure** --- create `~/.ai-chat/config.yaml`:

```yaml
ai:
  base_url: "https://your-ai-endpoint/v1"
  api_key: "your-api-key"
  model: "gpt-4"
```

**3. Verify** your connection:

```bash
parlor --test
```

**4. Launch:**

```bash
parlor
```

Your browser opens to `http://127.0.0.1:8080`. That's it. You're done.

---

## Features

### Conversations

| | |
|---|---|
| **Create, rename, search, delete** | Full conversation lifecycle |
| **Full-text search** | FTS5-powered search across all messages and titles |
| **Fork at any message** | Explore different directions from any point |
| **Edit & regenerate** | Fix a message and re-run from there |
| **Export to Markdown** | One-click download of any conversation |
| **Auto-titles** | AI generates a title from your first message |
| **Keyboard-first** | `Ctrl+Shift+N` new chat, `Escape` stop generation |

### Projects

Group conversations under projects with **custom system prompts** and **per-project model selection**. Your coding project uses Claude with a developer prompt. Your writing project uses GPT-4 with an editorial voice. Each project is its own world.

### Organization

<table>
<tr>
<td width="50%">

**Folders**
- Nested folder hierarchy in the sidebar
- Collapse/expand to stay organized
- Rename, delete (conversations preserved)

</td>
<td width="50%">

**Tags**
- Color-coded labels on conversations
- Filter the sidebar by tag
- Visual at-a-glance categorization

</td>
</tr>
</table>

### Shared Databases

Connect **multiple SQLite databases** for team or topic-based separation. Each database is fully independent --- its own conversations, attachments, and history.

- **Visual file browser** for selecting database paths
- **Copy conversations** between databases
- **Switch databases** from the sidebar

### Rich Rendering

```
Markdown     ->  Full GFM with tables, lists, blockquotes
Code blocks  ->  Syntax highlighting + one-click copy
LaTeX math   ->  Inline ($x^2$) and display ($$\int$$)
Images       ->  Inline previews for attached images
```

### File Attachments

Drag-and-drop or click to attach. **30+ file types** supported --- code, documents, images, data files. Up to 10 files per message, 10 MB each. Magic-byte verification ensures files are what they claim to be.

### MCP Tool Integration

Connect **stdio** or **SSE-based** MCP servers. Your AI gains access to external tools --- databases, APIs, file systems, anything with an MCP adapter. Tool calls render with expandable input/output so you see exactly what happened.

```yaml
mcp_servers:
  - name: "my-tools"
    transport: "stdio"
    command: "npx"
    args: ["-y", "@my-org/mcp-tools"]
```

### Streaming

Real-time **token-by-token streaming** via Server-Sent Events. Stop generation mid-response with `Escape` or the stop button. A thinking indicator keeps you informed while the AI processes.

### Command Palette

**`Cmd+K`** / **`Ctrl+K`** opens a Raycast-style command palette. Search conversations, switch themes, create projects, jump to settings --- all without touching the mouse. Fuzzy matching makes it fast.

---

## Themes

Four built-in themes, each with a distinct visual identity. Switch instantly via settings or command palette.

| Theme | Vibe | |
|---|---|---|
| **Midnight** | Premium tech dark --- think Linear, Raycast | `Default` |
| **Dawn** | Warm editorial light --- think Notion in sunlight | `Light` |
| **Aurora** | Living gradient dark with animated accents | `Showstopper` |
| **Ember** | Warm luxury dark --- amber by firelight | `Cozy` |

Themes persist across sessions. Glassmorphism, multi-layered shadows, micro-animations on hover/focus, gradient text effects, and smooth 0.5s cross-fade transitions between themes.

---

## Responsive Design

| Breakpoint | Target | Behavior |
|---|---|---|
| **1400px+** | Large desktop | Wider messages, expanded sidebar |
| **769-1399px** | Desktop | Default layout |
| **768-1024px** | Tablet | Compact sidebar, full-width messages |
| **0-767px** | Mobile | Slide-over sidebar with hamburger menu |

---

## Configuration

### Config File

`~/.ai-chat/config.yaml`

```yaml
ai:
  base_url: "https://your-ai-endpoint/v1"
  api_key: "your-api-key"
  model: "gpt-4"
  system_prompt: "You are a helpful assistant."
  verify_ssl: true  # set false for self-signed certs

app:
  host: "127.0.0.1"
  port: 8080
  data_dir: "~/.ai-chat"

# Optional: shared databases
shared_databases:
  - name: "team-shared"
    path: "~/shared/team.db"

# Optional: MCP tool servers
mcp_servers:
  - name: "my-tools"
    transport: "stdio"
    command: "npx"
    args: ["-y", "@my-org/mcp-tools"]

  - name: "remote-tools"
    transport: "sse"
    url: "https://mcp-server.example.com/sse"
```

### Environment Variables

Every config option has an env var override:

| Variable | Default | Description |
|---|---|---|
| `AI_CHAT_BASE_URL` | --- | AI API endpoint **(required)** |
| `AI_CHAT_API_KEY` | --- | API key **(required)** |
| `AI_CHAT_MODEL` | `gpt-4` | Model name |
| `AI_CHAT_SYSTEM_PROMPT` | `You are a helpful assistant.` | System prompt |
| `AI_CHAT_VERIFY_SSL` | `true` | SSL certificate verification |

### Settings UI

Click the gear icon to change model and system prompt at runtime. Available models are fetched live from your API.

---

## CLI

```
parlor              Launch server and open browser
parlor --test       Test connection, list models, send test prompt, exit
parlor --help       Show help
```

<details>
<summary><strong>Example <code>--test</code> output</strong></summary>

```
Config:
  Endpoint: https://your-ai-endpoint/v1
  Model:    gpt-4
  SSL:      enabled

1. Listing models...
   OK - 12 model(s) available
     - gpt-4
     - gpt-4-turbo
     - gpt-3.5-turbo
     ...

2. Sending test prompt to gpt-4...
   OK - Response: Hello! How can I help you today?

All checks passed.
```

</details>

---

## Security

Parlor is hardened for use on corporate networks and shared machines. Not a checkbox exercise --- real, layered defense.

| Layer | What it does |
|---|---|
| **Authentication** | Random session token, HttpOnly cookies, HMAC-SHA256 timing-safe comparison |
| **CSRF** | Per-session tokens validated on all state-changing requests |
| **CSP** | `script-src 'self'`, `frame-ancestors 'none'`, no inline scripts |
| **Security Headers** | X-Frame-Options DENY, X-Content-Type-Options nosniff, strict Referrer-Policy, Permissions-Policy |
| **Database** | Column-allowlisted SQL builder, parameterized queries, `0600` file permissions, path validation |
| **Input Sanitization** | DOMPurify on all rendered HTML, UUID validation on all IDs |
| **Rate Limiting** | 120 req/min per IP with LRU eviction |
| **Body Size** | 15 MB max request |
| **CORS** | Locked to configured origin |
| **File Safety** | MIME allowlist + magic-byte verification, path traversal prevention |
| **MCP Safety** | SSRF protection with DNS resolution, shell metacharacter rejection |
| **SRI** | SHA-384 hashes on all vendor scripts |
| **API Surface** | OpenAPI/Swagger docs disabled |

Full details in [SECURITY.md](SECURITY.md).

---

## Data Storage

Everything stays on your machine. Nothing phones home.

```
~/.ai-chat/
  config.yaml          # Configuration          (permissions: 0600)
  chat.db              # SQLite + WAL journal   (permissions: 0600)
  attachments/         # Files by conversation  (permissions: 0700)
```

The data directory is created with `0700` permissions (owner-only access). Database files are created with `0600` permissions. WAL and SHM sidecar files are locked down too.

### Supported File Types

| Category | Extensions |
|---|---|
| **Code** | `.py` `.js` `.ts` `.java` `.c` `.cpp` `.h` `.hpp` `.rs` `.go` `.rb` `.php` `.sh` `.bat` `.ps1` `.sql` `.css` |
| **Data** | `.json` `.yaml` `.yml` `.csv` `.xml` `.toml` `.ini` `.cfg` `.log` |
| **Documents** | `.txt` `.md` `.pdf` |
| **Images** | `.png` `.jpg` `.jpeg` `.gif` `.webp` |

---

## API

Parlor exposes a full REST API. All endpoints require authentication.

<details>
<summary><strong>Conversations</strong></summary>

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/api/conversations` | List (with `?search=`, `?project_id=`, `?db=`) |
| `POST` | `/api/conversations` | Create |
| `GET` | `/api/conversations/:id` | Get with messages |
| `PATCH` | `/api/conversations/:id` | Rename, move to folder, change model |
| `DELETE` | `/api/conversations/:id` | Delete with attachments |
| `GET` | `/api/conversations/:id/export` | Export as Markdown |
| `POST` | `/api/conversations/:id/chat` | Stream chat (SSE) |
| `POST` | `/api/conversations/:id/stop` | Cancel generation |
| `POST` | `/api/conversations/:id/fork` | Fork at a message |
| `POST` | `/api/conversations/:id/copy` | Copy to another database |

</details>

<details>
<summary><strong>Messages & Attachments</strong></summary>

| Method | Endpoint | Description |
|---|---|---|
| `PUT` | `/api/messages/:id` | Edit a message |
| `DELETE` | `/api/messages/:id` | Delete after position |
| `GET` | `/api/attachments/:id` | Download attachment |

</details>

<details>
<summary><strong>Projects, Folders, Tags</strong></summary>

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/api/projects` | List projects |
| `POST` | `/api/projects` | Create project |
| `PATCH` | `/api/projects/:id` | Update project |
| `DELETE` | `/api/projects/:id` | Delete project |
| `GET` | `/api/folders` | List folders |
| `POST` | `/api/folders` | Create folder |
| `PATCH` | `/api/folders/:id` | Update folder |
| `DELETE` | `/api/folders/:id` | Delete folder |
| `GET` | `/api/tags` | List tags |
| `POST` | `/api/tags` | Create tag |
| `PATCH` | `/api/tags/:id` | Update tag |
| `DELETE` | `/api/tags/:id` | Delete tag |

</details>

<details>
<summary><strong>Config & Infrastructure</strong></summary>

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/api/config` | Get current config |
| `PATCH` | `/api/config` | Update model/system prompt |
| `POST` | `/api/config/validate` | Test connection |
| `GET` | `/api/models` | List available models |
| `GET` | `/api/mcp/tools` | List MCP tools |
| `GET` | `/api/databases` | List shared databases |
| `POST` | `/api/databases` | Add shared database |
| `DELETE` | `/api/databases/:name` | Remove shared database |
| `GET` | `/api/browse` | Browse filesystem |

</details>

---

## Development

```bash
git clone https://github.com/troylar/parlor.git
cd parlor
pip install -e ".[dev]"

pytest tests/ -v          # Run tests
ruff check src/ tests/    # Lint
ruff format src/ tests/   # Format
```

### Tech Stack

| | |
|---|---|
| **Backend** | Python 3.10+, FastAPI, Uvicorn |
| **Frontend** | Vanilla JS, marked.js, highlight.js, KaTeX, DOMPurify |
| **Database** | SQLite with FTS5 full-text search, WAL journaling |
| **AI** | OpenAI Python SDK (async streaming) |
| **MCP** | Model Context Protocol SDK (stdio + SSE) |
| **Streaming** | Server-Sent Events |
| **Typography** | Inter + JetBrains Mono (self-hosted, no external requests) |

---

<p align="center">
  <strong>MIT License</strong><br>
  Built for people who care about their conversations.
</p>
