.
This commit is contained in:
44
AGENTS.md
Normal file
44
AGENTS.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Essential Commands
|
||||||
|
|
||||||
|
Build, test, and develop:
|
||||||
|
```shell
|
||||||
|
make # Full workflow: format, lint, test, clean, build
|
||||||
|
make test # Run tests only
|
||||||
|
make build # Build binary to ./dist/gmi2html
|
||||||
|
make fmt # Format code with gofumpt and gci
|
||||||
|
make lint # Run linter after formatting
|
||||||
|
make lintfix # Run linter with auto-fix
|
||||||
|
```
|
||||||
|
|
||||||
|
Running the tool:
|
||||||
|
```shell
|
||||||
|
./dist/gmi2html <input.gmi >output.html
|
||||||
|
./dist/gmi2html --no-container <input.gmi >content.html
|
||||||
|
./dist/gmi2html --replace-gmi-ext <input.gmi >output.html
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
This is a Go library and CLI tool that converts Gemini text format to HTML.
|
||||||
|
|
||||||
|
**Core Components:**
|
||||||
|
- `gmi2html.go`: Main conversion logic with `Gmi2html()` function and `convertGeminiContent()` for parsing
|
||||||
|
- `templates.go`: HTML templates for each Gemini element type (headings, links, lists, etc.)
|
||||||
|
- `cmd/gmi2html/main.go`: CLI entry point that reads from stdin and writes to stdout
|
||||||
|
- `assets/main.html`: Embedded HTML template with CSS for the full page container
|
||||||
|
|
||||||
|
**Key Architecture Patterns:**
|
||||||
|
- Uses Go's `html/template` package for safe HTML generation with automatic escaping
|
||||||
|
- Embeds the main HTML template using `//go:embed` directive
|
||||||
|
- Line-by-line parser that switches between normal and preformatted modes
|
||||||
|
- Two output modes: full HTML document or content-only for embedding
|
||||||
|
- Optional `.gmi` to `.html` link conversion for static site generation
|
||||||
|
|
||||||
|
**Gemini Format Support:**
|
||||||
|
- Headings (#, ##, ###), links (=>), lists (*), quotes (>), preformatted blocks (```)
|
||||||
|
- Proper handling of preformatted content with mode switching
|
||||||
|
- URL parsing and validation for links
|
||||||
98
CLAUDE.md
Normal file
98
CLAUDE.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to AI Agents such as Claude Code or ChatGPT Codex when working with code in this repository.
|
||||||
|
|
||||||
|
## General guidelines
|
||||||
|
|
||||||
|
Use idiomatic Go as possible. Prefer simple code than complex.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Gemserve is a simple Gemini protocol server written in Go that serves static files over TLS-encrypted connections. The Gemini protocol is a lightweight, privacy-focused alternative to HTTP designed for serving text-based content.
|
||||||
|
|
||||||
|
### Development Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build, test, and format everything
|
||||||
|
make
|
||||||
|
|
||||||
|
# Run tests only
|
||||||
|
make test
|
||||||
|
|
||||||
|
# Build binaries to ./dist/ (gemserve, gemget, gembench)
|
||||||
|
make build
|
||||||
|
|
||||||
|
# Format code with gofumpt and gci
|
||||||
|
make fmt
|
||||||
|
|
||||||
|
# Run golangci-lint
|
||||||
|
make lint
|
||||||
|
|
||||||
|
# Run linter with auto-fix
|
||||||
|
make lintfix
|
||||||
|
|
||||||
|
# Clean build artifacts
|
||||||
|
make clean
|
||||||
|
|
||||||
|
# Run the server (after building)
|
||||||
|
./dist/gemserve
|
||||||
|
|
||||||
|
# Generate TLS certificates for development
|
||||||
|
certs/generate.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
|
||||||
|
Core Components
|
||||||
|
|
||||||
|
- **cmd/gemserve/gemserve.go**: Entry point with TLS server setup, signal handling, and graceful shutdown
|
||||||
|
- **cmd/gemget/**: Gemini protocol client for fetching content
|
||||||
|
- **cmd/gembench/**: Benchmarking tool for Gemini servers
|
||||||
|
- **server/**: Request processing, file serving, and Gemini protocol response handling
|
||||||
|
- **gemini/**: Gemini protocol implementation (URL parsing, status codes, path normalization)
|
||||||
|
- **config/**: CLI-based configuration system
|
||||||
|
- **lib/logging/**: Structured logging package with context-aware loggers
|
||||||
|
- **lib/apperrors/**: Application error handling (fatal vs non-fatal errors)
|
||||||
|
- **uid/**: Connection ID generation for logging (uses external vendor package)
|
||||||
|
|
||||||
|
Key Patterns
|
||||||
|
|
||||||
|
- **Security First**: All file operations use `filepath.IsLocal()` and path cleaning to prevent directory traversal
|
||||||
|
- **Error Handling**: Uses structured errors via `lib/apperrors` package distinguishing fatal from non-fatal errors
|
||||||
|
- **Logging**: Structured logging with configurable levels via internal logging package
|
||||||
|
- **Testing**: Table-driven tests with parallel execution, heavy focus on security edge cases
|
||||||
|
|
||||||
|
Request Flow
|
||||||
|
|
||||||
|
1. TLS connection established on port 1965
|
||||||
|
2. Read up to 1KB request (Gemini spec limit)
|
||||||
|
3. Parse and normalize Gemini URL
|
||||||
|
4. Validate path security (prevent traversal)
|
||||||
|
5. Serve file or directory index with appropriate MIME type
|
||||||
|
6. Send response with proper Gemini status codes
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
|
||||||
|
Server configured via CLI flags:
|
||||||
|
- `--listen`: Server address (default: localhost:1965)
|
||||||
|
- `--root-path`: Directory to serve files from
|
||||||
|
- `--dir-indexing`: Enable directory browsing (default: false)
|
||||||
|
- `--log-level`: Logging verbosity (debug, info, warn, error; default: info)
|
||||||
|
- `--response-timeout`: Response timeout in seconds (default: 30)
|
||||||
|
- `--tls-cert`: TLS certificate file path (default: certs/server.crt)
|
||||||
|
- `--tls-key`: TLS key file path (default: certs/server.key)
|
||||||
|
- `--max-response-size`: Maximum response size in bytes (default: 5242880)
|
||||||
|
|
||||||
|
Testing Strategy
|
||||||
|
|
||||||
|
- **server/server_test.go**: Path security and file serving tests
|
||||||
|
- **gemini/url_test.go**: URL parsing and normalization tests
|
||||||
|
- Focus on security edge cases (Unicode, traversal attempts, malformed URLs)
|
||||||
|
- Use parallel test execution for performance
|
||||||
|
|
||||||
|
Security Considerations
|
||||||
|
|
||||||
|
- All connections require TLS certificates (stored in certs/)
|
||||||
|
- Path traversal protection is critical - test thoroughly when modifying file serving logic
|
||||||
|
- Request size limited to 1KB per Gemini specification
|
||||||
|
- Input validation on all URLs and paths
|
||||||
2
Makefile
2
Makefile
@@ -8,7 +8,6 @@ debug:
|
|||||||
@echo "GOPATH: $(shell go env GOPATH)"
|
@echo "GOPATH: $(shell go env GOPATH)"
|
||||||
@which go
|
@which go
|
||||||
@which gofumpt
|
@which gofumpt
|
||||||
@which gci
|
|
||||||
@which golangci-lint
|
@which golangci-lint
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@@ -24,7 +23,6 @@ tidy:
|
|||||||
# Format code
|
# Format code
|
||||||
fmt:
|
fmt:
|
||||||
gofumpt -l -w .
|
gofumpt -l -w .
|
||||||
gci write .
|
|
||||||
|
|
||||||
# Run linter
|
# Run linter
|
||||||
lint: fmt
|
lint: fmt
|
||||||
|
|||||||
@@ -9,18 +9,31 @@
|
|||||||
<link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&family=Source+Serif+4:ital,opsz,wght@0,8..60,200..900;1,8..60,200..900&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&family=Source+Serif+4:ital,opsz,wght@0,8..60,200..900;1,8..60,200..900&display=swap" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--text-color: #eee;
|
--text-color: #333;
|
||||||
--bg-color: #292929;
|
--bg-color: #ffffff;
|
||||||
--link-color: #4a9eff;
|
--link-color: #0066cc;
|
||||||
--link-hover: #77b6ff;
|
--link-hover: #0052a3;
|
||||||
--quote-bg: #333;
|
--quote-bg: #f5f5f5;
|
||||||
|
--quote-border: #ddd;
|
||||||
|
--pre-bg: #f8f8f8;
|
||||||
|
--pre-border: #e1e1e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--text-color: #e0e0e0;
|
||||||
|
--bg-color: #1a1a1a;
|
||||||
|
--link-color: #66a3ff;
|
||||||
|
--link-hover: #87b5ff;
|
||||||
|
--quote-bg: #2a2a2a;
|
||||||
--quote-border: #444;
|
--quote-border: #444;
|
||||||
--pre-bg: #2a2a2a;
|
--pre-bg: #252525;
|
||||||
--pre-border: #3a3a3a;
|
--pre-border: #3a3a3a;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: "Source Serif Pro", serif;
|
font-family: serif;
|
||||||
/* font-weight: 300; */
|
/* font-weight: 300; */
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
@@ -97,7 +110,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.gemini-preformatted {
|
.gemini-preformatted {
|
||||||
font-family: "Source Code Pro", monospace !important;
|
|
||||||
background-color: var(--pre-bg);
|
background-color: var(--pre-bg);
|
||||||
border: 1px solid var(--pre-border);
|
border: 1px solid var(--pre-border);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
noContainer := flag.Bool("no-container", false, "Don't output container HTML")
|
noContainer := flag.Bool("no-container", false, "Don't output container HTML")
|
||||||
replaceGmiExt := flag.Bool("replace-gmi-ext", false, "Replace .gmi extension with .html in links")
|
replaceGmiExt := flag.Bool("replace-gmi-ext", false, "In links, replace original .gmi extension with .html")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
err := runApp(*noContainer, *replaceGmiExt)
|
err := runApp(*noContainer, *replaceGmiExt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user