commit f515fae4e30941b4dd1dee8f9dea263bc3ee9631 Author: Joachim Hummel Date: Mon Apr 13 09:11:52 2026 +0000 First commit diff --git a/.claude/commands/screens.md b/.claude/commands/screens.md new file mode 100644 index 0000000..aabc934 --- /dev/null +++ b/.claude/commands/screens.md @@ -0,0 +1,29 @@ +# Screens + +Suche nach Screenshots im `screens/`-Verzeichnis des aktuellen Arbeitsverzeichnisses (`process.cwd()/screens`). + +## Vorgehen + +1. Prüfe ob `./screens/` existiert — wenn nicht, darauf hinweisen und ggf. den Server starten +2. Liste alle Dateien in `./screens/` auf (sortiert nach Änderungszeit, neueste zuerst) +3. Zeige Dateinamen, Größe und Datum übersichtlich an +4. Wenn der Nutzer nach einer bestimmten Datei fragt, suche per Glob oder Grep in `./screens/` + +## Befehle + +```bash +# Alle Screenshots auflisten (neueste zuerst) +ls -lt ./screens/ + +# Nur Bilddateien +ls -lt ./screens/*.{png,jpg,jpeg,gif,webp,bmp} 2>/dev/null + +# Nach Name suchen +ls ./screens/** +``` + +## Hinweise + +- Dateien werden durch den Screenshot-Upload-Server in `./screens/` gespeichert +- Server starten: `server.sh start` (aus dem Projektverzeichnis) +- Wenn `./screens/` nicht existiert, wurde noch kein Upload durchgeführt oder der Server läuft nicht diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8565442 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,41 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +A minimal, self-contained image upload server written in vanilla Node.js (ES modules). No external dependencies — only Node.js built-ins (`http`, `fs`, `path`, `url`). + +## Running the Server + +```bash +# Direct +node server.mjs + +# Via daemon manager +./server.sh start +./server.sh stop +./server.sh restart +./server.sh status +``` + +- Listens on `0.0.0.0:8765` (hardcoded in `server.mjs`) +- Logs to `/tmp/server_mjs.log`; PID tracked at `/tmp/server_mjs.pid` + +## Architecture + +The entire application lives in two files: + +**`server.mjs`** — Three logical sections: +1. **Embedded HTML UI** (lines 9–85): Full self-contained frontend (HTML/CSS/JS) as a string constant — no separate asset files. Dark theme, drag-and-drop, German labels. +2. **`parseMultipart()`** (lines 87–110): Custom binary multipart/form-data parser; no external library. +3. **HTTP server** (lines 112–145): Two routes — `GET /` serves the UI, `POST /upload` saves files to the directory where `server.mjs` lives. + +**`server.sh`** — Daemon management (start/stop/restart/status). Note: the path inside the script points to `/home/joachim/git/ai-coding-kit/screens/server.mjs`, not the local copy — update if deploying from this directory. + +## Key Conventions + +- **Save location**: Files are written to the same directory as `server.mjs` via `fileURLToPath(import.meta.url)`. +- **Filename sanitization**: `path.basename()` prevents path traversal; regex `/[^a-zA-Z0-9._\- ()äöüÄÖÜß]/g`replaces disallowed characters with underscores. German characters are intentionally allowed. +- **Synchronous writes**: `fs.writeFileSync()` is used deliberately for simplicity. +- **UI is embedded**: Keep HTML/CSS/JS inside the string constant in `server.mjs` — there are no separate asset files by design. diff --git a/README.md b/README.md new file mode 100644 index 0000000..38307ca --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# Screenshot-Server + +Ein minimaler Upload-Server für Screenshots und Bilder, geschrieben in vanilla Node.js – ohne externe Abhängigkeiten. + +## Funktionsweise + +Der Server stellt eine Web-Oberfläche bereit, über die Bilder per Drag & Drop oder Dateiauswahl hochgeladen werden können. Die Dateien werden direkt in das Verzeichnis gespeichert, in dem `server.mjs` liegt. + +- **Port:** 8765 +- **Erreichbar unter:** `http://:8765` +- **Unterstützte Dateitypen:** Bilder (`image/*`) +- **Dateinamen:** Sonderzeichen werden bereinigt; Umlaute (äöüÄÖÜß) sind erlaubt + +## Voraussetzungen + +- Node.js (ES-Module-Unterstützung, d.h. Node 14+) +- Kein `npm install` notwendig + +## Starten + +### Direkt + +```bash +node server.mjs +``` + +### Als Hintergrunddienst (über `server.sh`) + +```bash +./server.sh start # Starten +./server.sh stop # Stoppen +./server.sh restart # Neustart +./server.sh status # Status anzeigen +``` + +Logs werden nach `/tmp/server_mjs.log` geschrieben. + +> **Hinweis:** In `server.sh` ist der Pfad zur `server.mjs` hardcodiert. Bei abweichendem Speicherort muss `APP_CMD` in Zeile 3 angepasst werden. diff --git a/screens/Screen.png b/screens/Screen.png new file mode 100644 index 0000000..1ff963e Binary files /dev/null and b/screens/Screen.png differ diff --git a/server.mjs b/server.mjs new file mode 100644 index 0000000..b39615b --- /dev/null +++ b/server.mjs @@ -0,0 +1,151 @@ +import http from "http"; +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +const DIR = path.join(process.cwd(), "screens"); +if (!fs.existsSync(DIR)) fs.mkdirSync(DIR, { recursive: true }); +const PORT = 8765; + +const HTML = ` + + + + + 📸 Screens Upload + + + +
+

📸 Screens Upload

+

Screenshots direkt in den screens/ Ordner im aktuellen Verzeichnis hochladen.

+
+ + + Drag & Drop oder klicken zum Auswählen +
+
+ +
+
+ + +`; + +function parseMultipart(body, boundary) { + const files = []; + const sep = Buffer.from("--" + boundary); + const parts = []; + let start = body.indexOf(sep) + sep.length + 2; + while (start < body.length) { + const end = body.indexOf(sep, start); + if (end === -1) break; + parts.push(body.slice(start, end - 2)); + start = end + sep.length + 2; + } + for (const part of parts) { + const headerEnd = part.indexOf("\r\n\r\n"); + if (headerEnd === -1) continue; + const header = part.slice(0, headerEnd).toString(); + const content = part.slice(headerEnd + 4); + const nameMatch = header.match(/name="([^"]+)"/); + const fileMatch = header.match(/filename="([^"]+)"/); + if (nameMatch && fileMatch) { + files.push({ name: fileMatch[1], data: content }); + } + } + return files; +} + +const server = http.createServer((req, res) => { + if (req.method === "GET" && req.url === "/") { + res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); + return res.end(HTML); + } + + if (req.method === "POST" && req.url === "/upload") { + const ct = req.headers["content-type"] || ""; + const match = ct.match(/boundary=(.+)/); + if (!match) { + res.writeHead(400); return res.end("Kein Boundary"); + } + const boundary = match[1]; + const chunks = []; + req.on("data", c => chunks.push(c)); + req.on("end", () => { + const body = Buffer.concat(chunks); + const files = parseMultipart(body, boundary); + if (!files.length) { res.writeHead(400); return res.end("Keine Datei gefunden"); } + const saved = []; + for (const file of files) { + const safeName = path.basename(file.name).replace(/[^a-zA-Z0-9._\- ()äöüÄÖÜß]/g, "_"); + const dest = path.join(DIR, safeName); + fs.writeFileSync(dest, file.data); + saved.push(safeName); + console.log(`✅ Gespeichert: ${safeName}`); + } + res.writeHead(200); res.end(saved.join(", ") + " gespeichert."); + }); + return; + } + + res.writeHead(404); res.end("Not found"); +}); + +server.listen(PORT, "0.0.0.0", () => { + console.log(`📸 Screens Upload Server läuft auf http://0.0.0.0:${PORT}`); + console.log(`📁 Speicherort: ${DIR}`); +}); diff --git a/server.sh b/server.sh new file mode 100755 index 0000000..106d737 --- /dev/null +++ b/server.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +APP_CMD="node $SCRIPT_DIR/server.mjs" +PID_FILE="/tmp/server_mjs.pid" + +start() { + if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if ps -p $PID > /dev/null 2>&1; then + echo "Service läuft bereits (PID: $PID)" + exit 1 + else + echo "Alte PID-Datei gefunden, wird entfernt" + rm -f "$PID_FILE" + fi + fi + + echo "Starte Service..." + nohup $APP_CMD > /tmp/server_mjs.log 2>&1 & + echo $! > "$PID_FILE" + echo "Gestartet mit PID $(cat $PID_FILE)" +} + +stop() { + if [ ! -f "$PID_FILE" ]; then + echo "Keine PID-Datei gefunden – läuft der Service?" + exit 1 + fi + + PID=$(cat "$PID_FILE") + + if ps -p $PID > /dev/null 2>&1; then + echo "Stoppe Service (PID: $PID)..." + kill $PID + rm -f "$PID_FILE" + echo "Gestoppt" + else + echo "Prozess läuft nicht mehr, entferne PID-Datei" + rm -f "$PID_FILE" + fi +} + +status() { + if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if ps -p $PID > /dev/null 2>&1; then + echo "Service läuft (PID: $PID)" + else + echo "PID-Datei vorhanden, aber Prozess läuft nicht" + exit 1 + fi + else + echo "Service gestoppt" + exit 3 + fi +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + stop + sleep 1 + start + ;; + status) + status + ;; + *) + echo "Usage: $0 {start|stop|restart|status}" + exit 1 + ;; +esac