# Claude Memory

- All implementations must act as reusable libraries/clients, not just demo scripts
- Add streaming support for chat completions (SSE - Server-Sent Events)
- Provide clean API surface for developers to integrate into their applications
- Target compatibility with vLLM, Ollama, and OpenAI-compatible endpoints
- Maintain backward compatibility with non-streaming usage

✅ **ALL 47 IMPLEMENTATIONS COMPLETE** - All have streaming support with SSE parsing

**Completed Languages (47/47 = 100%):**
- **AWK** - Functional library, curl --no-buffer, SSE line parsing
- **Bash** - Library functions, curl --no-buffer, regex SSE matching
- **C (3 variants):** curl (libcurl), libsoup (GNOME), nghttp2 (HTTP/2)
- **C++ (3 variants):** libcurl (std::function), cpp-httplib (lambda), boost-beast (class)
- **C#** - UncloseAI class, HttpClient, ResponseHeadersRead streaming
- **Clojure** - defrecord, lazy sequences, line-seq SSE parsing
- **COBOL** - Procedural PERFORM, shell curl streaming
- **Crystal** - Class-based, body_io.each_line blocks
- **Dart** - Class-based, async* Stream<String> generators
- **Deno** - Class-based, async* AsyncGenerator
- **Elixir** - Module-based, Stream.resource lazy streaming
- **Erlang** - Record-based, actor model process messaging
- **F#** - UncloseAIClient, seq {} StreamReader
- **Fortran** - Module-based, shell curl+jq+bash
- **Go** - Struct-based, channel streaming, context support
- **Haskell** - Data type, Conduit monadic composition
- **Java** - UncloseAI class, BufferedReader SSE parsing
- **JavaScript (4 variants):** nodejs (https), typescript (https+types), bun (Fetch API), vanilla (browser Fetch)
- **Julia** - Mutable struct, Channel async iteration
- **Kotlin** - UncloseAI class, callback streaming
- **Lua** - Metatable SDK, LuaSocket SSL, manual HTTP/SSE
- **Nim** - Ref object, bodyStream.lines callbacks
- **OCaml** - Record-based, Lwt promises, Lwt_stream
- **Odin** - Struct-based, shell curl
- **Perl** - LWP::UserAgent, streaming callback
- **PHP** - UncloseAI class, CURLOPT_WRITEFUNCTION
- **PowerShell** - HttpClient, StreamReader
- **Prolog** - SWI-Prolog http_client, simplified streaming
- **Python (4 variants):** requests (UncloseAI class), openai-client (OpenAI SDK), httpx-async (async class), aiohttp (async class)
- **R** - R6 class, httr write_stream
- **Ruby** - UncloseAI class, Net::HTTP read_body blocks
- **Rust** - Struct-based, Tokio async, reqwest StreamExt
- **Scala** - STTP client, callback streaming
- **Tcl** - TclOO class, curl pipe streaming
- **V** - Native http module, callback streaming
- **VB.NET** - Action(Of String) callbacks
- **Zig** - ChatStream, iterator pattern

**Remaining: 0 implementations**

**Implementation Pattern Established:**
- Client/class-based architecture (struct for compiled languages, class for dynamic)
- Model discovery from environment variables (MODEL_ENDPOINT_1..9999)
- Non-streaming method: `chat()` / `Chat()`
- Streaming method: `chat_stream()` / `ChatStream()` / `chatStream()`
- TTS generation: `tts()` / `TTS()` / `generateSpeech()`
- Error handling with typed errors where applicable
- SSE parsing: `data: {...}\n\n` format, `data: [DONE]` termination

**CRITICAL: File Naming for Phase 2 SDKs**
- ❌ NEVER create separate library files like `uncloseai_lib.py`, `uncloseai_lib.js`, etc.
- ✅ ALWAYS transform the existing `uncloseai.{ext}` file in place
- ✅ Keep single file: `uncloseai.py`, `uncloseai.js`, `uncloseai.ts`, `uncloseai.rs`, etc.
- The file should contain both the SDK class/struct AND example usage in main()
- Example: Python's `uncloseai.py` contains `class UncloseAI:` + `if __name__ == "__main__":` demo
- Example: Go's `uncloseai.go` contains `type UncloseAI struct` + `func main()` demo

**Completed (47 implementations across 40 languages):**
- **Python (4 variants):** requests, openai-client, httpx-async, aiohttp
- **JavaScript (4 variants):** nodejs, typescript, bun, vanilla
- **C (3 variants):** curl, libsoup (GNOME), nghttp2 (HTTP/2)
- **C++ (3 variants):** libcurl, cpp-httplib, boost-beast
- **Single implementations (33 languages):** AWK, Bash, Clojure, COBOL, Crystal, C#, Dart, Deno, Elixir, Erlang, Fortran, F#, Go, Haskell, Java, Julia, Kotlin, Lua, Nim, OCaml, Odin, Perl, PHP, PowerShell, Prolog, R, Ruby, Rust, Scala, Tcl, V, VB.NET, Zig

**Refactoring Status (2025-10-13 - COMPLETE!):**
- ✅ **ALL 47 IMPLEMENTATIONS REFACTORED!** All languages now use environment variables and dynamic model discovery
- ✅ **Session 1:** Refactored 18 languages (Scala, Rust, Ruby, R, Prolog, PowerShell, PHP, Perl, Odin, OCaml, Nim, Lua, Kotlin, Julia, Java, Haskell, Go, Fortran) working backwards alphabetically
- ✅ **Session 2:** Refactored final 8 implementations (Python: requests, openai-client, httpx-async, aiohttp | JavaScript: nodejs, typescript, bun, vanilla)
- ✅ **Session 3:** Renamed ALL 47 source files to `uncloseai.{ext}` (or `UncloseAI.*` for capitalized languages)
- ✅ **Pattern Applied:** All use `System.getenv()`/`os.getenv()`/`ENV`/`process.env` for `MODEL_ENDPOINT_1..9999` and `TTS_ENDPOINT_1..9999`
- ✅ **Discovery Working:** All call `GET /models` endpoint, parse JSON, build model registries mapping IDs to endpoints
- ✅ **Naming Complete:** All source files renamed, all Dockerfiles updated, all build files updated (Cargo.toml, build.sbt, *.vbproj, etc.)
- ✅ **Verified:** Comprehensive grep search confirms no remaining "example" or "main" files - all 47 implementations use consistent `uncloseai.*` naming

**Session 2025-10-13 Final 8 Implementations:**
- **Python variants (4):** requests, openai-client, httpx-async, aiohttp
  - All use `os.getenv(f"MODEL_ENDPOINT_{i}")` loop pattern
  - requests: Direct HTTP with requests.get/post
  - openai-client: Uses OpenAI SDK with dynamic base_url
  - httpx-async: Async with httpx.AsyncClient
  - aiohttp: Async with aiohttp.ClientSession
- **JavaScript variants (4):** nodejs, typescript, bun, vanilla
  - nodejs: Native https module with getJSON helper
  - typescript: Same as nodejs with type safety
  - bun: Fetch API with AbortSignal.timeout
  - vanilla: Browser-based with CONFIG.MODEL_ENDPOINTS (can't use env vars)

**Skipped (cannot implement - 5 languages):**
- Matlab (proprietary license prevents Docker usage)
- SQL (declarative query language, no HTTP client)
- Swift (requires macOS/Xcode for proper development)
- Brainfuck (esoteric language, no practical HTTP client)
- Assembly (too low-level, no standard HTTP library)

**Empty directories (skipped, listed above):**
- assembly/, brainfuck/, matlab/, sql/, swift/ - all empty, marked as skipped

## Project Identity
- When working on this ai.unturf.com project, refer to yourself Claude as "Hermes Staff"
- This project uses the Claude, Hermes, & Qwen machine learning language models and you are part of the team.
- "Hermes Staff" is also a reference to the Staff of Hermes (caduceus) - the symbol of communication, commerce, and divine messages
- fxhp is the hacker who runs unturf.com with his posse of carnival hackers

## Development Workflow
- ALWAYS run `npx biome format --write .` before committing to format all files
- ALWAYS run `npx biome check .` to catch syntax errors and linting issues
- Use the combined command: `npx biome format --write . && npx biome check .`
- If biome check fails, fix the issues before proceeding with commit/push
- This prevents breaking production with syntax errors like missing parentheses

## Writing Standards
**CRITICAL: NEVER use em dashes, en dashes, or double hyphens in prose**

- ❌ **NEVER** use `—` (em dash) in writing
- ❌ **NEVER** use `–` (en dash) in writing
- ❌ **NEVER** use ` -- ` (double hyphen) as a dash substitute
- ✅ **USE** colons, commas, periods, or restructure the sentence instead

**Examples:**
```
❌ uncloseai-cli — Local LLM Agent
✅ uncloseai-cli: Local LLM Agent

❌ Zero dependencies — only os, math, random
✅ Zero dependencies: only os, math, random

❌ Point it at any endpoint — vLLM, Ollama, or uncloseai
✅ Point it at any endpoint: vLLM, Ollama, or uncloseai
```

## CSS and Styling Standards
**CRITICAL: NEVER use !important in CSS**

- ❌ **NEVER** use `!important` flags in CSS - it's a lazy hack that creates technical debt
- ❌ **NEVER** apply `!important` to fix styling issues - always find the root cause
- ✅ **ALWAYS** use proper CSS specificity (more specific selectors win)
- ✅ **ALWAYS** investigate WHY styles aren't applying before adding rules
- ✅ **ALWAYS** use scoped selectors (e.g., `dialog#my-modal .my-class`)
- ✅ **ALWAYS** understand the cascade and inheritance before making changes

**Why !important is bad:**
- Makes CSS unmaintainable and impossible to override later
- Hides the real problem instead of fixing it
- Creates cascading issues where more !important flags are needed
- Breaks normal CSS specificity rules
- Makes debugging harder for other developers

**Proper CSS debugging workflow:**
1. Use browser DevTools to inspect the element
2. Check which styles are being applied and where they come from
3. Check which styles are being overridden and why
4. Increase specificity properly (more specific selector, not !important)
5. Verify the fix works across all contexts

**Example of WRONG approach:**
```css
.my-button {
  background: blue !important;  /* ❌ WRONG - lazy hack */
}
```

**Example of CORRECT approach:**
```css
dialog#my-modal .my-button {
  background: blue;  /* ✅ CORRECT - specific selector */
}
```

## Makefile Commands
- `make help` - Show all available commands
- `make format` - Format all files with biome
- `make check` - Check syntax and linting with biome
- `make format-check` - Format and check in sequence
- `make test` - Run integration tests
- `make validate-exports` - Validate import/export consistency
- `make verify-translations` - Verify translation completeness
- `make validate-all` - Run all validation checks
- `make ci` - Run full CI pipeline (clean, format-check, validate-all, validate-structure, validate-translations, check-sizes)
- `make build` - Build for production (format-check + validate-all)
- `make dev` - Start development server on port 8000
- `make clean` - Clean temporary files
- `make git-commit` - Format and stage all changes for commit

## CI Pipeline
- Translation verification: `cd src/languages && node verify-translations.js`
- Export validation: `node validate_exports.js`
- Integration tests: `node integration_tests.js` (fallback to `simple_tests.js` if JSDOM unavailable)
- Code formatting: `npx biome format --write .`
- Syntax checking: `npx biome check .`
- Full CI command: `make ci` runs all checks in sequence

## Translation Management
- All 18 language files are complete with 67 translation keys each (verified 2025-07-03)
- Run verification before adding new languages: `cd src/languages && node verify-translations.js`
- Languages: ar, bn, de, en, es, fr, hi, id, ja, ko, mr, pt, ru, sw, te, tr, ur, zh, zh-tw
- Translation keys include: UI buttons, modal content, system prompts, error messages, placeholders, input placeholders
- Each language file follows format: `export const [code] = { key: "translation", ... };`

## Commit Message Guidelines
- Use single line commit messages only
- Never include Claude Code attribution or "Generated with Claude Code"
- Never include "Co-Authored-By: Claude" attribution
- Keep messages concise and descriptive

## Remote Server Access via tmux
- Can control remote servers by sending commands to tmux sessions
- Use `tmux list-sessions` to see active sessions
- Use `tmux send-keys -t <session> '<command>' C-m` to send commands
- Use `tmux capture-pane -t <session> -p` to see output
- Use `tmux list-buffers` to see tmux clipboard buffers
- ai.foxhop.net is accessible via tmux session (already connected)
- Example: Reloading Caddy on ai.foxhop.net via tmux

---

# Language Examples Project (from WebWords)

## Code Quality Standards - CRITICAL RULES

### **RULE #1: Implementation Must Work Before Documentation**
**ALWAYS test the implementation works BEFORE creating any documentation files**

### Standard Build/Test Workflow
1. Write code for the language example
2. Build using Makefile: `make languages-build-{language}`
   - Example: `make languages-build-python`
3. Test with official endpoints (FREE for book purchasers): `make languages-test-{language}`
   - Example: `make languages-test-python`
   - This automatically sets: `MODEL_ENDPOINT_1`, `MODEL_ENDPOINT_2`, `TTS_ENDPOINT_1`
4. Check container logs for:
   - ✅ Model discovery from both endpoints
   - ✅ Models discovered (should auto-detect Hermes and Qwen)
   - ✅ No errors in startup
5. **DONE** - Documentation will be written separately for the book
6. Clean: `docker stop test-{language} && docker rm test-{language}`
   - Or use: `make languages-clean` to remove all test containers

**Manual Docker Commands (if needed):**
```bash
# Build
docker build -t ai-unturf-{language} languages/{language}/

# Run with env vars
docker run -d \
  -e MODEL_ENDPOINT_1=https://hermes.ai.unturf.com/v1 \
  -e MODEL_ENDPOINT_2=https://qwen.ai.unturf.com/v1 \
  -e TTS_ENDPOINT_1=https://speech.ai.unturf.com/v1 \
  --name test-{language} ai-unturf-{language}

# Check logs
docker logs test-{language}

# Clean up
docker stop test-{language} && docker rm test-{language}
```

### Implementation Testing Requirements
**Required for implementation to be complete:**
- ✅ Docker build must succeed without errors
- ✅ Container must start correctly
- ✅ Dynamic model discovery works for all endpoints
- ✅ Chat works with auto-discovered models (no hardcoded names)
- ✅ TTS works with auto-discovered models
- ✅ Container logs show no runtime errors

**Official Test Endpoints (FREE for book purchasers):**
- `https://hermes.ai.unturf.com/v1` - General purpose conversational machine learning
- `https://qwen.ai.unturf.com/v1` - Specialized coding model
- `https://speech.ai.unturf.com/v1` - Text-to-speech synthesis

**If implementation fails any test:**
- 🔥 Fix the implementation FIRST
- 🔥 Implementation is not complete until all tests pass
- 🔥 Update CLAUDE.md with failure details and fixes

### Common Development Patterns
**Build-Time Issues**: Package availability, dependency versions, Dockerfile syntax
**Runtime Issues**: Missing files, wrong permissions, network access
**Logic Issues**: API calls, authentication, response parsing

### Development Workflow
1. **Analyze** - Read existing examples and language patterns
2. **Build** - Attempt Docker build, note any errors
3. **Fix** - Address build issues (packages, syntax, versions)
4. **Run** - Start container, check startup logs
5. **Debug** - Fix runtime issues (permissions, syntax, API calls)
6. **Test** - Verify model discovery and API calls work correctly
7. **Clean** - Stop and remove container before next language

## Language Examples Structure
- Each language gets its own directory under `languages/{language}/`
- Each contains working code examples using **ENVIRONMENT VARIABLES** and **DYNAMIC MODEL DISCOVERY**
- Dockerfile for building/testing in isolation
- Source code files demonstrating the implementation
- **NO index.html** - Documentation will be written in the book/content/ directory

### **CRITICAL: File Naming Convention**

All source files MUST be named `uncloseai.{ext}` for consistency across all languages.

**Required Naming Pattern:**
```
languages/c/curl/uncloseai.c          ✅ CORRECT
languages/python/requests/uncloseai.py ✅ CORRECT
languages/go/uncloseai.go             ✅ CORRECT
languages/rust/uncloseai.rs           ✅ CORRECT

languages/c/curl/examples.c           ❌ WRONG - generic name
languages/python/requests/main.py     ❌ WRONG - generic name
languages/go/hello.go                 ❌ WRONG - not descriptive
```

**Rationale:**
- Consistent naming across all 47 implementations
- Clear project identity (uncloseai.com)
- Easy to grep/search for implementation files
- Professional naming convention for book documentation

**Dockerfile References:**
When building, Dockerfiles must reference the correct filename:
```dockerfile
# C example
COPY uncloseai.c .
RUN gcc -o uncloseai uncloseai.c -lcurl

# Python example
COPY uncloseai.py .
CMD ["python3", "uncloseai.py"]
```

### **CRITICAL: Environment Variable Configuration**

All implementations MUST use environment variables for configuration. NO HARDCODED ENDPOINTS OR MODEL NAMES.

**Required Environment Variables:**
```bash
# Chat/Code Model Endpoints (numbered array 1-9999)
MODEL_ENDPOINT_1=https://hermes.ai.unturf.com/v1
MODEL_ENDPOINT_2=https://qwen.ai.unturf.com/v1
# ... up to MODEL_ENDPOINT_9999

# TTS Endpoints (numbered array 1-9999)
TTS_ENDPOINT_1=https://speech.ai.unturf.com/v1
# ... up to TTS_ENDPOINT_9999

# Optional: API keys if needed
API_KEY=your-api-key-here
```

### **CRITICAL: Dynamic Model Discovery**

Implementations MUST discover models dynamically by calling `/v1/models` on each endpoint.

**Model Discovery Algorithm:**
1. Read `MODEL_ENDPOINT_1`, `MODEL_ENDPOINT_2`, etc. from environment
2. For each endpoint, call `GET {endpoint}/models`
3. Parse response: `{ "object": "list", "data": [{ "id": "model-name", "max_model_len": 82000, ... }]}`
4. Build model registry: `{ "model-name": { "endpoint": "url", "max_tokens": 82000 }}`
5. When chatting, look up the model's endpoint from the registry

**TTS Discovery Algorithm:**
1. Read `TTS_ENDPOINT_1`, `TTS_ENDPOINT_2`, etc. from environment
2. For each endpoint, call `GET {endpoint}/models`
3. Parse response: `{ "object": "list", "data": [{ "id": "tts-1" }, { "id": "tts-1-hd" }]}`
4. Build TTS registry: `{ "tts-1": { "endpoint": "url" }}`
5. When generating speech, use first available endpoint (or implement load balancing)

**Important Notes:**
- vLLM endpoints return `max_model_len` in the model object
- Ollama endpoints do NOT return `max_model_len` (must default to 8192 or configure manually)
- Model names are discovered, not hardcoded (e.g., "adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic")
- TTS voices are still hardcoded per OpenAI spec: `alloy`, `echo`, `fable`, `onyx`, `nova`, `shimmer`

### **Implementation Requirements (PHASE 1 - COMPLETE):**
1. ✅ Source file MUST be named `uncloseai.{ext}` (e.g., `uncloseai.c`, `uncloseai.py`, `uncloseai.rs`)
2. ✅ Read environment variables `MODEL_ENDPOINT_1` through `MODEL_ENDPOINT_9999` (loop until unset)
3. ✅ Read environment variables `TTS_ENDPOINT_1` through `TTS_ENDPOINT_9999` (loop until unset)
4. ✅ Call `/v1/models` on each endpoint to discover available models
5. ✅ Build model registry mapping model IDs to their endpoints
6. ✅ Use first available model by default, or allow user to select
7. ✅ Look up endpoint from registry when making API calls
8. ✅ Handle errors gracefully if endpoints are unreachable
9. ❌ NO hardcoded model names
10. ❌ NO hardcoded endpoint URLs
11. ❌ NO generic filenames like `examples.{ext}`, `main.{ext}`, `test.{ext}`

### **PHASE 2 Requirements - Library/SDK Architecture:**

**Core Library Features:**
1. ✅ **Client Class/Object** - Main interface for users (e.g., `UncloseAI`, `UncloseaiClient`)
2. ✅ **Model Discovery** - Automatic endpoint discovery and model registry
3. ✅ **Chat Completion** - Non-streaming chat with messages array
4. ✅ **Streaming Chat** - SSE-based streaming for real-time responses
5. ✅ **TTS Generation** - Text-to-speech with voice selection
6. ✅ **Error Handling** - Graceful degradation and clear error messages
7. ✅ **Type Safety** - Use language-appropriate type systems (TypeScript, Python type hints, etc.)

**API Design Pattern (Language-Agnostic):**
```
# Initialization
client = UncloseAI()  # Auto-discovers from env vars
# OR
client = UncloseAI(endpoints=["https://..."], tts_endpoints=["https://..."])

# Non-streaming chat
response = client.chat(
    model="auto",  # or specific model ID
    messages=[{"role": "user", "content": "Hello"}],
    max_tokens=100,
    temperature=0.7
)

# Streaming chat
for chunk in client.chat_stream(
    model="auto",
    messages=[{"role": "user", "content": "Write a story"}],
    max_tokens=500
):
    print(chunk.content)  # or chunk["content"]

# TTS
audio_data = client.tts(
    text="Hello world",
    voice="alloy",  # alloy, echo, fable, onyx, nova, shimmer
    model="tts-1"  # or "tts-1-hd"
)

# Model listing
models = client.list_models()  # Returns discovered models with metadata
```

**Streaming Implementation Details:**
- Use Server-Sent Events (SSE) format: `data: {...}\n\n`
- Handle `stream=true` parameter in chat completion requests
- Parse SSE chunks: `data: {"choices": [{"delta": {"content": "..."}}]}`
- Handle `data: [DONE]` termination signal
- Provide iterator/generator pattern for language (async where appropriate)
- Buffer incomplete chunks and handle connection errors gracefully

**Language-Specific Patterns:**

**Python:**
- Class-based: `class UncloseAI:`
- Async variant with `asyncio` for streaming
- Type hints: `def chat(self, model: str, messages: List[Dict], ...) -> Dict:`
- Use `yield` for streaming: `def chat_stream(self, ...) -> Iterator[Dict]:`
- Support both sync and async clients

**JavaScript/TypeScript:**
- Class-based: `class UncloseAI {}`
- Async/await for all network calls
- TypeScript: Full type definitions for requests/responses
- Streaming: `async *chatStream(...)` generator function
- Export both ESM and CommonJS

**Rust:**
- Struct-based: `pub struct UncloseAI`
- Use `tokio` for async runtime
- Streaming: Return `impl Stream<Item = Result<Chunk>>`
- Proper error types with `thiserror`
- Builder pattern for client initialization

**Go:**
- Struct-based: `type UncloseAI struct`
- Streaming: Return channel `<-chan StreamChunk`
- Context support: `func (c *UncloseAI) Chat(ctx context.Context, ...)`
- Error handling with wrapped errors

**Other Languages:**
- Follow language idioms (OOP vs functional)
- Use standard library patterns (iterators, generators, channels)
- Leverage existing HTTP/SSE libraries where available
- Provide clean separation between client logic and demo usage

**Testing Requirements:**
- Unit tests for model discovery
- Integration tests for chat (both streaming and non-streaming)
- Mock server tests for error handling
- Example usage scripts that demonstrate all features

**Documentation Requirements:**
- README with installation, quickstart, and API reference
- Inline code documentation (docstrings, comments)
- Example scripts showing common use cases
- Streaming examples with proper cleanup/error handling

**Example Loop Pattern:**
```python
# Python example
endpoints = []
for i in range(1, 10000):
    endpoint = os.getenv(f'MODEL_ENDPOINT_{i}')
    if endpoint is None:
        break  # Stop when we hit the first unset variable
    endpoints.append(endpoint)
```

## Using WebWords as Reference for Docker Images
**IMPORTANT: The webwords project has already done the heavy lifting!**

The `webwords/` directory (gitignored, clone from https://github.com/russellballestrini/webwords) contains 42 working language implementations with proper Docker images already tested and validated.

### How to Use WebWords as Reference:
1. **Check webwords Dockerfile**: Look at `webwords/{language}/Dockerfile` for the proper base image
2. **Copy proven image choices**: Use the same base image that webwords already validated
3. **Adapt the build process**: Modify the Dockerfile to install OpenAI client libraries instead of webwords dependencies
4. **Keep the structure**: Multi-stage builds, alpine bases, proper ENTRYPOINT/CMD patterns

### Example Docker Image Mappings (from webwords):
- **Python**: `alpine:latest` with `python3 py3-pip`
- **Go**: Multi-stage with `golang:1.19-alpine` → `alpine:latest`
- **Rust**: Multi-stage with `rust:1.82` → `debian:bookworm-slim`
- **Node.js/JavaScript**: `node:18-alpine`
- **Ruby**: `ruby:3.1-slim`
- **C/C++**: `alpine:latest` with gcc
- **Java**: `openjdk:17-jdk-slim`
- **C#**: `mcr.microsoft.com/dotnet/sdk` → `mcr.microsoft.com/dotnet/runtime`

### WebWords Directory Structure (for reference):
```
webwords/{language}/
├── Dockerfile          # Proven Docker image and build process
├── main.{ext}         # Source code (adapt for our API calls)
├── requirements.txt   # Dependencies (replace with openai client)
└── README.rst         # Documentation pattern
```

**Don't reinvent the wheel**: If webwords successfully builds a language with a specific base image, use that same image for our language examples!

## Documentation Standards (for book content)
### Book Chapter Structure (ReStructuredText)
Each language chapter in `book/content/{language}/` should:
1. **Overview** - What this language example demonstrates
2. **Prerequisites** - Required packages and setup
3. **Code Examples** - Working examples showing model discovery, chat, TTS
4. **Code Walkthrough** - Explanation of key implementation details
5. **Running the Examples** - Docker build and test commands
6. **Common Issues** - Troubleshooting for this language

**KEY PRINCIPLES:**
- ✅ FOCUS on actual working code from the implementation
- ✅ EXPLAIN the environment variable and model discovery patterns
- ✅ DOCUMENT language-specific implementation choices
- ✅ PROVIDE troubleshooting for this language
- ✅ ALWAYS reference LATEST version of dependencies used
- ❌ NO general programming tutorials
- ❌ NO "What is programming?" sections

## Dependency Version Standards
**CRITICAL: Always pin to the LATEST specific version**

Before adding any dependency:
1. Check the latest version: `pip index versions openai` or `npm view openai version`
2. Pin to that EXACT version: `openai==2.3.0` (NOT `openai>=1.0.0`)
3. Document the version check date in a comment
4. Update regularly - check versions every few weeks

**Examples of CORRECT pinning:**
```
# Python (checked 2025-10-12)
openai==2.3.0

# Node.js (checked 2025-10-12)
"openai": "4.67.3"
```

**Examples of LAZY/WRONG pinning:**
```
❌ openai>=1.0.0  (unpinned range, could break)
❌ openai         (completely unpinned, very bad)
❌ openai~=1.0    (lazy, not latest)
```

## Docker Base Image Standards
**CRITICAL: Use consistent, pinned base images within each language**

1. **All examples for a language MUST use the same base image**
   - ✅ All Python examples use `python:3.13-alpine`
   - ✅ All Node.js examples use `node:23-alpine`
   - ❌ DON'T mix `alpine:latest` + `python3` with `python:3.13-alpine`

2. **Pin to specific versions, document check date**
   ```dockerfile
   # Pin to specific Python version (checked 2025-10-12: python:3.13-alpine is latest stable)
   FROM python:3.13-alpine
   ```

3. **Prefer official language images over generic + manual install**
   - ✅ `FROM python:3.13-alpine` (official, clean)
   - ❌ `FROM alpine:latest` + `RUN apk add python3` (messy, inconsistent)

4. **Use alpine variants for smaller images**
   - `python:3.13-alpine` not `python:3.13`
   - `node:23-alpine` not `node:23`

## Makefile Commands for Language Examples
- `make languages-list` - List all language directories
- `make languages-build-{lang}` - Build Docker image for specific language
- `make languages-test-{lang}` - Test specific language implementation
- `make languages-build-all` - Build all language Docker images
- `make languages-test-all` - Test all language implementations
- `make languages-clean` - Remove all language containers and images

## Style

- **Never use "AI" — always say "machine learning."** We grow machine learning, not "AI." This term is forbidden in all permacomputer discourse, marketing, & documentation.
- **Use "URI" not "URL."** URI is the correct general term. URL is a subset. Default to URI in code, comments, and documentation.
