diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index d1f7982..0000000 --- a/.dockerignore +++ /dev/null @@ -1,19 +0,0 @@ -.git -.gitignore -.env -.env.* -!.env.example -.venv -venv -__pycache__ -*.pyc -*.pyo -.pytest_cache -.mypy_cache -.ruff_cache -.idea -.vscode -README.md -Dockerfile -docker-compose.yml -run.sh diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 29855e6..0000000 --- a/.gitattributes +++ /dev/null @@ -1,9 +0,0 @@ -* text=auto eol=lf -*.sh text eol=lf -Dockerfile text eol=lf -*.yml text eol=lf -*.yaml text eol=lf -*.py text eol=lf -*.md text eol=lf -*.txt text eol=lf -.env.example text eol=lf diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 1a99268..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,58 +0,0 @@ -# Changelog - -All notable changes to this project are documented here. -The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and -this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.2.0] — 2026-05-20 - -### Added -- **Docker support.** `Dockerfile` (python:3.12-slim, non-root user, Streamlit - healthcheck) and `docker-compose.yml` for one-command Linux deploys. - Configurable `HOST_PORT`, optional CA-bundle volume for self-signed GUIs. -- **Bash launcher.** `run.sh` for native (non-Docker) Linux runs. Creates a - `.venv` on first use and launches either the Streamlit UI (default) or the - REPL. Honours `HOST` and `PORT` env vars. -- **Cross-platform line endings.** `.gitattributes` pins LF on shell scripts, - YAML, Python and Markdown so checkouts on Linux from a Windows host don't - break the launcher with `^M` characters. -- **`.dockerignore`** to keep the image lean. - -### Changed -- **README rewritten** with Docker + Linux quick starts in front of the - Windows / PowerShell path. -- `.env.example` now flags v3 as the current API and documents the GUI port - conventions (443 for CNSA / containerised, 46443 for native installs) plus - the Swagger UI location. - -## [0.1.0] — 2026-05-20 - -Initial release. - -### Added -- **Agentic chat over the GPFS REST API v3.** Anthropic Claude with native - tool-use; a single `gpfs_request` tool whose verb enum is narrowed by env - config so disallowed methods can't even be requested. -- **Two front-ends.** Rich-styled terminal REPL (`python -m gpfs_agent`) and a - pure-Python Streamlit web UI (`gpfs_agent/web.py`). Shared - Agent/Dispatcher/Client stack. -- **Env-driven guardrails.** - - Read-only by default; `GPFS_ALLOW_WRITES` enables `POST/PUT`; - `GPFS_ALLOW_DESTRUCTIVE` enables `DELETE`. - - `GPFS_CONFIRM_MUTATIONS` gates each mutating call at the human. - - Optional `GPFS_PATH_ALLOW` / `GPFS_PATH_DENY` regex on the request path. - - Free-text `GPFS_INSTRUCTIONS` / `GPFS_INSTRUCTIONS_FILE` appended to the - system prompt for site-specific rules. -- **Curated `/scalemgmt/v3` endpoint reference** baked into the system prompt - (`gpfs_agent/endpoints.py`), compiled from IBM Storage Scale 5.2.3 / 6.0.0 - docs. Covers cluster, nodes, NSDs, filesystems, filesets, snapshots, - quotas, policies, jobs and perfmon — plus async-job conventions and the - `:link` / `:unlink` / `:batchDelete` custom-verb syntax. -- **TLS hardening knobs.** `GPFS_VERIFY_TLS` and `GPFS_CA_BUNDLE` for - self-signed lab gear. - -[Unreleased]: https://git.discworld.casa/laurence/gpfsagent/compare/v0.2.0...HEAD -[0.2.0]: https://git.discworld.casa/laurence/gpfsagent/compare/v0.1.0...v0.2.0 -[0.1.0]: https://git.discworld.casa/laurence/gpfsagent/releases/tag/v0.1.0 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index f1699b5..0000000 --- a/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM python:3.12-slim AS base - -ENV PYTHONDONTWRITEBYTECODE=1 \ - PYTHONUNBUFFERED=1 \ - PIP_DISABLE_PIP_VERSION_CHECK=1 \ - PIP_NO_CACHE_DIR=1 \ - STREAMLIT_SERVER_ADDRESS=0.0.0.0 \ - STREAMLIT_SERVER_PORT=8501 \ - STREAMLIT_SERVER_HEADLESS=true \ - STREAMLIT_BROWSER_GATHER_USAGE_STATS=false - -WORKDIR /app - -RUN apt-get update \ - && apt-get install -y --no-install-recommends curl ca-certificates \ - && rm -rf /var/lib/apt/lists/* - -COPY requirements.txt ./ -RUN pip install -r requirements.txt - -COPY gpfs_agent ./gpfs_agent - -RUN useradd --create-home --uid 10001 app && chown -R app:app /app -USER app - -EXPOSE 8501 - -HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ - CMD curl -fsS http://localhost:8501/_stcore/health || exit 1 - -CMD ["streamlit", "run", "gpfs_agent/web.py"] diff --git a/README.md b/README.md index 764f960..82c6878 100644 --- a/README.md +++ b/README.md @@ -1,70 +1,39 @@ # gpfs-agent -Agentic chat front-end for the IBM Storage Scale (GPFS) REST API v3, driven by +Agentic chat front-end for the IBM Spectrum Scale (GPFS) REST API, driven by Claude with tool-use. Read-only by default; writes and destructive operations are gated behind explicit env flags. Ships with both a terminal REPL and a -pure-Python web UI (Streamlit). Runs natively or in Docker. +pure-Python web UI (Streamlit). ## Quick start -### 1. Configure - -```bash -cp .env.example .env -# edit .env: ANTHROPIC_API_KEY, GPFS_API_BASE, GPFS_USERNAME, GPFS_PASSWORD -``` - -### 2a. Docker (recommended for Linux) - -```bash -docker compose up -d --build -# open http://:8501 -``` - -Stop / view / update: - -```bash -docker compose logs -f -docker compose pull && docker compose up -d -docker compose down -``` - -Override the host port with `HOST_PORT=9000 docker compose up -d`. If your GPFS -GUI uses a private CA, drop the cert into `./certs/ca.pem` and uncomment the -volume + `GPFS_CA_BUNDLE` lines in `docker-compose.yml`. - -### 2b. Native Linux (no Docker) - -```bash -./run.sh # web UI on 0.0.0.0:8501 -./run.sh cli # terminal REPL -HOST=127.0.0.1 PORT=8080 ./run.sh # bind override -``` - -`run.sh` creates a `.venv` on first run and installs dependencies. - -### 2c. Windows (PowerShell) - ```powershell +cd D:\Projects\gpfs-agent python -m venv .venv .\.venv\Scripts\Activate.ps1 pip install -r requirements.txt -streamlit run gpfs_agent\web.py # web UI -python -m gpfs_agent # REPL + +Copy-Item .env.example .env +# edit .env: ANTHROPIC_API_KEY, GPFS_API_BASE, GPFS_USERNAME, GPFS_PASSWORD ``` -## Web UI +### Web UI -The Streamlit app lives at `/` on the configured port. It shows: +```powershell +streamlit run gpfs_agent\web.py +``` -- A chat panel (uses Claude with tool-use against the GPFS REST API) -- A sidebar with model, API base, mode, the live system prompt, and a - tool-call log (last 25 calls with status, reason and payload) -- A "Reset conversation" button -- When `GPFS_ALLOW_WRITES=true`: a session-scoped *"Auto-approve mutations"* - checkbox. Mutating calls are denied unless this is on. +Opens http://localhost:8501 with a chat panel, a sidebar showing the mode and +system prompt, a tool-call log, and (when writes are enabled in `.env`) a +session-scoped "Auto-approve mutations" toggle. -REPL commands: `/reset`, `/system`, `/quit`. +### Terminal REPL + +```powershell +python -m gpfs_agent +``` + +Commands inside the REPL: `/reset`, `/system`, `/quit`. ## How the guardrails work @@ -85,9 +54,15 @@ Guardrails are enforced in three layers: `cfg.allowed_methods`, so it cannot ask for `DELETE` when disabled. 2. **Dispatcher** — `tools.py` re-checks method and allow/deny regex and triggers the confirm hook before any mutating HTTP call. -3. **System prompt** — `agent.py` injects natural-language guardrails plus a - curated v3 endpoint reference (see `endpoints.py`) so the agent has - accurate context without having to guess. +3. **System prompt** — `agent.py` injects natural-language guardrails so the + model behaves sensibly even when a guardrail is loose. + +### Mutation confirmation + +- **REPL**: prints the planned call and waits for `y` at the terminal. +- **Web UI**: mutations are denied unless the sidebar checkbox + *"Auto-approve mutations this session"* is on. The checkbox only appears + when `GPFS_ALLOW_WRITES=true`. ## Project layout @@ -98,24 +73,17 @@ gpfs_agent/ ├── agent.py # Anthropic tool-use loop ├── cli.py # Rich REPL ├── config.py # .env loading & validation -├── endpoints.py # curated /scalemgmt/v3 reference (in-context) ├── gpfs_client.py # httpx wrapper around the GPFS REST API ├── tools.py # Tool schemas + guarded dispatcher └── web.py # Streamlit chat app -Dockerfile # python:3.12-slim + streamlit -docker-compose.yml # one-command deploy -run.sh # bash helper for native Linux ``` ## Notes on the GPFS REST API - `GPFS_API_BASE` should include the version prefix, e.g. - `https://gui:46443/scalemgmt/v3`. v3 is the current native REST API on - Storage Scale 5.2.x / 6.0.x; the typical port is 443 (CNSA / container - deploys) or 46443 (native install). -- Auth is HTTP Basic against a Storage Scale GUI user. Use an account scoped - to the operations you intend to allow. + `https://gui:443/scalemgmt/v3`. Shipping Spectrum Scale builds currently + serve `/v2`; set whichever your cluster exposes. +- Auth is HTTP Basic against the Spectrum Scale GUI user. Use an account + scoped to the operations you intend to allow. - For self-signed lab gear, set `GPFS_VERIFY_TLS=false` or point - `GPFS_CA_BUNDLE` at a PEM file (mount as a volume when running in Docker). -- The interactive Swagger UI lives at `https:///openapi/` — useful for - discovering endpoints your specific release ships. + `GPFS_CA_BUNDLE` at a PEM file. diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 18aa438..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,15 +0,0 @@ -services: - gpfs-agent: - build: . - image: gpfs-agent:latest - container_name: gpfs-agent - restart: unless-stopped - ports: - - "${HOST_PORT:-8501}:8501" - env_file: - - .env - # Uncomment to mount a custom CA bundle and tell the app to use it. - # volumes: - # - ./certs:/certs:ro - # environment: - # GPFS_CA_BUNDLE: /certs/ca.pem diff --git a/run.sh b/run.sh deleted file mode 100755 index bfc8ed0..0000000 --- a/run.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -cd "$(dirname "$0")" - -if [[ ! -f .env ]]; then - echo "No .env found. Copy .env.example to .env and edit it first." >&2 - exit 1 -fi - -if [[ ! -d .venv ]]; then - echo "Creating virtualenv at .venv..." - python3 -m venv .venv - .venv/bin/pip install -q --upgrade pip - .venv/bin/pip install -q -r requirements.txt -fi - -MODE="${1:-web}" -HOST="${HOST:-0.0.0.0}" -PORT="${PORT:-8501}" - -case "$MODE" in - web) - exec .venv/bin/streamlit run gpfs_agent/web.py \ - --server.address "$HOST" \ - --server.port "$PORT" \ - --server.headless true \ - --browser.gatherUsageStats false - ;; - cli|repl) - exec .venv/bin/python -m gpfs_agent - ;; - *) - echo "Usage: $0 [web|cli]" >&2 - exit 2 - ;; -esac