diff --git a/README.md b/README.md index 8985e83..cfe70ae 100644 --- a/README.md +++ b/README.md @@ -4,34 +4,46 @@ Javis ist ein langfristiges, lokal-first ausgerichtetes Projekt für einen pers ## Aktueller Stand -Der erste lokale Textchat funktioniert auf Windows mit Ollama und `qwen3:8b`. -Er bietet echte lokale Modellantworten ohne Cloud-Fallback, neue und fortsetzbare -Sitzungen sowie SQLite-Persistenz außerhalb des Repositories. +Der lokale Textchat funktioniert auf Windows mit Ollama und `qwen3:8b`. +Ein optionaler Gemini-Free-Provider ist auf einem Feature-Branch vorbereitet; +Datenschutzrouter, Nullkostenmodus und lokales Fallback sind automatisiert +getestet, aber noch nicht von Pascal mit einem echten Key abgenommen. Noch nicht implementiert sind Obsidian-Zugriff, Serverzugriff, Sprache, Mobile Client und Living Mind. -## Lokaler Start +## Einfacher Start -Ollama läuft in einem eigenen Terminal ausschließlich auf localhost: +Aus dem Repository: ```powershell -$env:OLLAMA_MODELS = 'D:\Javis-Data\ollama-models' -$env:OLLAMA_HOST = '127.0.0.1:11434' -& 'D:\Javis-Tools\ollama\ollama.exe' serve +powershell -ExecutionPolicy Bypass -File .\scripts\start-javis.ps1 ``` -Der Chat startet aus dem Repository in einem zweiten Terminal: +Das Skript findet die portablen Werkzeuge standardmäßig relativ zum Laufwerk des +Repositories, setzt Variablen nur für seinen Prozess, prüft Ollama ausschließlich +auf `127.0.0.1:11434`, startet es bei Bedarf versteckt und öffnet den Chat. Es +ändert weder PATH, Registry, Autostart, Windows-Dienste noch die systemweite +PowerShell-Ausführungsrichtlinie. -```powershell -$env:UV_CACHE_DIR = 'D:\Javis-Tools\uv-cache' -$env:JAVIS_DATA_DIR = 'D:\Javis-Data\runtime' -& 'D:\Javis-Tools\uv\uv.exe' run javis chat -``` +Abweichende Pfade sind über die Skriptparameter `-UvPath`, `-OllamaPath`, +`-OllamaModels`, `-DataDir`, `-UvCacheDir` und `-PythonInstallDir` oder die +entsprechenden `JAVIS_*`-Umgebungsvariablen möglich. Im Chat zeigt `/help` alle Befehle. Ohne `JAVIS_DATA_DIR` nutzt Javis einen sicheren benutzerspezifischen Plattformstandard. +Gemini bleibt standardmäßig aus. Die spätere lokale Einrichtung erfolgt ohne +Key in der Konfigurationsdatei: + +```powershell +& 'D:\Javis-Tools\uv\uv.exe' run javis configure gemini +& 'D:\Javis-Tools\uv\uv.exe' run javis secrets set gemini +``` + +Der Key wird bei der zweiten Zeile verdeckt eingegeben und im +Betriebssystem-Keyring gespeichert, niemals in Git oder TOML. + ## Grundregeln - Plattformunabhängiger modularer Monolith, voraussichtlich in Python. diff --git a/docs/CHATGPT_HANDOFF.md b/docs/CHATGPT_HANDOFF.md index 1b7b43a..10eb120 100644 --- a/docs/CHATGPT_HANDOFF.md +++ b/docs/CHATGPT_HANDOFF.md @@ -95,6 +95,9 @@ Rootserver: - Der echte CLI-Chat verwendet nun `hybrid/auto`; der Key wird erst vor einem zulässigen Cloudaufruf gelesen. `/provider`, `/privacy` und `/status` zeigen ausschließlich bereinigte Modi, Entscheidungen und Zähler. +- `scripts/start-javis.ps1` verifiziert das Repository, setzt nur + Prozessvariablen, prüft Ollama auf Loopback, startet es bei Bedarf versteckt + und wartet begrenzt. Pfade sind per Parameter oder `JAVIS_*` überschreibbar. ## Aktuelle Architektur @@ -151,6 +154,8 @@ Rootserver: - Hybridprovider in die CLI verdrahtet, sicheren Aktivierungsdialog und bereinigte Provider-/Datenschutz-/Statusbefehle ergänzt. - Lesender Ollama-Status prüft nur Loopback-Erreichbarkeit und Modellliste. +- Sicheres PowerShell-Startskript und kompakte Startdokumentation ergänzt; keine + PATH-, Registry-, Autostart-, Dienst- oder Richtlinienänderung. ## Aktuelle Tests @@ -165,8 +170,8 @@ Letzter bestätigter Projektstand: - uv-Lock und `uv sync --dev`: bestanden - Python in `.venv`: 3.12.13 - Ruff in `.venv`: 0.16.0 -- Unit-Tests: 59 bestanden; CLI-Befehle, doppelte Bestätigung, verzögertes - Key-Laden und Ollama-Status sind abgedeckt +- Unit-Tests: 60 bestanden; Startskript-Sicherheitsregeln zusätzlich abgedeckt +- PowerShell-Syntax des Startskripts: erfolgreich geparst - Ruff Lint: bestanden - Ruff Formatprüfung: bestanden @@ -252,9 +257,10 @@ Abnahmestatus: Auf dem bestehenden Feature-Branch als nächstes einzelnes Arbeitspaket: -1. sicheres `scripts/start-javis.ps1` ohne Systemänderungen ergänzen -2. Start- und Einrichtungsablauf kompakt dokumentieren und automatisiert prüfen -3. noch keine echte Cloudanfrage ausführen und noch nicht nach `main` mergen +1. geforderte Resttests ergänzen, insbesondere Timeout und bereinigte Fehler +2. vollständige Qualitäts-, Gitignore-, Secret- und Außenbereichsprüfung ausführen +3. Abschlussdokumentation aktualisieren, Feature-Branch pushen und vor Pascals + manuellem Live-Test stoppen; nicht nach `main` mergen Keinen API-Key in Chat, Git, Obsidian, Logs oder Dokumentation übernehmen. diff --git a/docs/DEVELOPMENT_SETUP.md b/docs/DEVELOPMENT_SETUP.md index 9406c26..5f2cd95 100644 --- a/docs/DEVELOPMENT_SETUP.md +++ b/docs/DEVELOPMENT_SETUP.md @@ -35,19 +35,27 @@ $env:UV_CACHE_DIR = 'D:\Javis-Tools\uv-cache' & 'D:\Javis-Tools\uv\uv.exe' run ruff format --check . ``` -Vor dem Chat wird Ollama in einem eigenen Terminal gestartet: +## Komfortabler Start + +Der normale Start aus dem Repository benötigt nur: ```powershell -$env:OLLAMA_MODELS = 'D:\Javis-Data\ollama-models' -$env:OLLAMA_HOST = '127.0.0.1:11434' -& 'D:\Javis-Tools\ollama\ollama.exe' serve +powershell -ExecutionPolicy Bypass -File .\scripts\start-javis.ps1 ``` -Danach: +`-ExecutionPolicy Bypass` gilt nur für diesen Prozess; das Skript ändert keine +systemweite Richtlinie. Es prüft Ollama auf Loopback, startet bei Bedarf die +portable Laufzeit versteckt und wartet höchstens 15 Sekunden. Werkzeug- und +Datenpfade können mit Parametern oder folgenden Umgebungsvariablen überschrieben +werden: -```powershell -$env:JAVIS_DATA_DIR = 'D:\Javis-Data\runtime' -& 'D:\Javis-Tools\uv\uv.exe' run javis chat -``` +- `JAVIS_UV_PATH` +- `JAVIS_OLLAMA_PATH` +- `JAVIS_OLLAMA_MODELS` +- `JAVIS_DATA_DIR` +- `JAVIS_UV_CACHE_DIR` +- `JAVIS_PYTHON_INSTALL_DIR` -Die absoluten Pfade sind ausschließlich lokale Entwicklungsdokumentation. Der Anwendungscode darf keine festen Windows-Pfade enthalten. +Die Standardpfade werden vom Laufwerk des Repositorys abgeleitet; persönliche +Windows-Benutzerpfade sind nicht eingebrannt. PATH, Registry, Autostart und +Windows-Dienste bleiben unverändert. diff --git a/docs/PROJECT_STATUS.md b/docs/PROJECT_STATUS.md index 3d9a8d7..4600f87 100644 --- a/docs/PROJECT_STATUS.md +++ b/docs/PROJECT_STATUS.md @@ -58,7 +58,9 @@ Stand: lokaler Textchat auf `main`, 30.07.2026. - `javis configure gemini` mit doppelter ausdrücklicher Bestätigung - echter Chat auf Hybridprovider verdrahtet; Key wird verzögert geladen - `/provider`, `/privacy` und `/status` geben keine Inhalte oder Keywerte aus -- 59 Unit-Tests aktuell erfolgreich; Ruff-Format und Lint sauber +- sicheres `scripts/start-javis.ps1` mit relativer Repositoryprüfung, + Loopback-Ollama und überschreibbaren externen Pfaden +- 60 Unit-Tests aktuell erfolgreich; PowerShell-Syntax, Ruff-Format und Lint sauber ## Nicht implementiert @@ -83,6 +85,6 @@ Living-Mind-Frontend und automatische Ollama-Prozessverwaltung. Ollama wird bewusst manuell in einem eigenen Terminal gestartet. Antworten werden noch nicht gestreamt; Sitzungen haben noch keine Titel, Suche oder Löschfunktion. `qwen3:8b` ist nicht automatisch für die GTX 1050 des späteren Zielhosts geeignet. -Das sichere Startskript und die abschließende Bedienungsdokumentation fehlen noch. +Abschließende Resttests, Qualitätsprüfung und Feature-Branch-Push fehlen noch. Es wurde kein echter API-Key angefordert, angezeigt oder konfiguriert und keine Cloudanfrage ausgeführt. diff --git a/scripts/start-javis.ps1 b/scripts/start-javis.ps1 new file mode 100644 index 0000000..b80374d --- /dev/null +++ b/scripts/start-javis.ps1 @@ -0,0 +1,121 @@ +[CmdletBinding()] +param( + [string]$UvPath, + [string]$OllamaPath, + [string]$OllamaModels, + [string]$DataDir, + [string]$UvCacheDir, + [string]$PythonInstallDir +) + +$ErrorActionPreference = 'Stop' + +function Get-ConfiguredPath { + param( + [string]$ParameterValue, + [string]$EnvironmentName, + [string]$DefaultValue + ) + + if (-not [string]::IsNullOrWhiteSpace($ParameterValue)) { + return [System.IO.Path]::GetFullPath($ParameterValue) + } + $environmentValue = [System.Environment]::GetEnvironmentVariable($EnvironmentName) + if (-not [string]::IsNullOrWhiteSpace($environmentValue)) { + return [System.IO.Path]::GetFullPath($environmentValue) + } + return [System.IO.Path]::GetFullPath($DefaultValue) +} + +function Test-OllamaReady { + try { + $null = Invoke-RestMethod ` + -Uri 'http://127.0.0.1:11434/api/version' ` + -Method Get ` + -TimeoutSec 2 + return $true + } + catch { + return $false + } +} + +$repositoryRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..')) +if (-not (Test-Path -LiteralPath (Join-Path $repositoryRoot 'pyproject.toml') -PathType Leaf)) { + throw "Das Repository konnte relativ zum Startskript nicht verifiziert werden." +} + +$driveRoot = [System.IO.Path]::GetPathRoot($repositoryRoot) +$resolvedUv = Get-ConfiguredPath ` + -ParameterValue $UvPath ` + -EnvironmentName 'JAVIS_UV_PATH' ` + -DefaultValue (Join-Path $driveRoot 'Javis-Tools\uv\uv.exe') +$resolvedOllama = Get-ConfiguredPath ` + -ParameterValue $OllamaPath ` + -EnvironmentName 'JAVIS_OLLAMA_PATH' ` + -DefaultValue (Join-Path $driveRoot 'Javis-Tools\ollama\ollama.exe') +$resolvedModels = Get-ConfiguredPath ` + -ParameterValue $OllamaModels ` + -EnvironmentName 'JAVIS_OLLAMA_MODELS' ` + -DefaultValue (Join-Path $driveRoot 'Javis-Data\ollama-models') +$resolvedData = Get-ConfiguredPath ` + -ParameterValue $DataDir ` + -EnvironmentName 'JAVIS_DATA_DIR' ` + -DefaultValue (Join-Path $driveRoot 'Javis-Data\runtime') +$resolvedCache = Get-ConfiguredPath ` + -ParameterValue $UvCacheDir ` + -EnvironmentName 'JAVIS_UV_CACHE_DIR' ` + -DefaultValue (Join-Path $driveRoot 'Javis-Tools\uv-cache') +$resolvedPython = Get-ConfiguredPath ` + -ParameterValue $PythonInstallDir ` + -EnvironmentName 'JAVIS_PYTHON_INSTALL_DIR' ` + -DefaultValue (Join-Path $driveRoot 'Javis-Tools\python') + +if (-not (Test-Path -LiteralPath $resolvedUv -PathType Leaf)) { + throw "uv.exe wurde nicht gefunden: $resolvedUv" +} +if (-not (Test-Path -LiteralPath $resolvedModels -PathType Container)) { + throw "Der externe Ollama-Modellpfad wurde nicht gefunden: $resolvedModels" +} + +$env:JAVIS_DATA_DIR = $resolvedData +$env:OLLAMA_MODELS = $resolvedModels +$env:OLLAMA_HOST = '127.0.0.1:11434' +$env:UV_CACHE_DIR = $resolvedCache +$env:UV_PYTHON_INSTALL_DIR = $resolvedPython + +if (-not (Test-OllamaReady)) { + if (-not (Test-Path -LiteralPath $resolvedOllama -PathType Leaf)) { + throw "Ollama ist nicht erreichbar und ollama.exe wurde nicht gefunden: $resolvedOllama" + } + Write-Host "Ollama wird lokal auf 127.0.0.1:11434 gestartet ..." + $ollamaProcess = Start-Process ` + -FilePath $resolvedOllama ` + -ArgumentList 'serve' ` + -WindowStyle Hidden ` + -PassThru + + $ready = $false + for ($attempt = 1; $attempt -le 30; $attempt++) { + if ($ollamaProcess.HasExited) { + throw "Ollama wurde beendet, bevor die lokale API bereit war." + } + Start-Sleep -Milliseconds 500 + if (Test-OllamaReady) { + $ready = $true + break + } + } + if (-not $ready) { + throw "Ollama war nach 15 Sekunden nicht unter 127.0.0.1:11434 erreichbar." + } +} + +Push-Location $repositoryRoot +try { + & $resolvedUv run javis chat + exit $LASTEXITCODE +} +finally { + Pop-Location +} diff --git a/tests/unit/test_start_script.py b/tests/unit/test_start_script.py new file mode 100644 index 0000000..996a3be --- /dev/null +++ b/tests/unit/test_start_script.py @@ -0,0 +1,30 @@ +import unittest +from pathlib import Path + + +class StartScriptTests(unittest.TestCase): + def test_script_is_local_overridable_and_avoids_system_changes(self) -> None: + repository_root = Path(__file__).resolve().parents[2] + script = (repository_root / "scripts" / "start-javis.ps1").read_text(encoding="utf-8") + normalized = script.lower() + + self.assertIn("$psscriptroot", normalized) + self.assertIn("http://127.0.0.1:11434/api/version", normalized) + self.assertIn("-windowstyle hidden", normalized) + self.assertIn("javis_uv_path", normalized) + self.assertIn("javis_ollama_path", normalized) + self.assertIn("javis_data_dir", normalized) + self.assertIn("run javis chat", normalized) + self.assertNotIn("c:\\users\\", normalized) + for forbidden in ( + "set-executionpolicy", + "setx ", + "new-service", + "schtasks", + "hkey_", + ): + self.assertNotIn(forbidden, normalized) + + +if __name__ == "__main__": + unittest.main()