# gravitational_lens

Portable operator tools for any repository: tickets, code navigation, read-only Git review, and an MCP endpoint.

`gravitational_lens` is meant to run as a small sidecar beside the repo you are working on. It does not require a database. It reads the target repository through `GRAVITATIONAL_LENS_REPO_DIR` and stores its own operator state under `GRAVITATIONAL_LENS_DATA_ROOT`.

## What is included

- Ticket UI: `/tickets`, `/tickets?format=json`, `/tickets/<uuid>?format=json`, and `/tickets/watch`.
- MCP endpoint: `/operator-mcp` with OAuth support.
- Code tools: browse, outline, file list, definition/reference search, file read, text search.
- Git tools: log, diff, show commit, and file history.
- Optional deploy artifact route: `/repo.zip`.

## Recommended shape

```text
any-repo/
  src/...
  .gravitational_lens/              # durable local state: tickets, tokens, session secret, optional repo.zip

gravitational_lens-package/
  Cargo.toml
  src/...
```

Run the package against any target repo:

```bash
cd /path/to/gravitational_lens
GRAVITATIONAL_LENS_REPO_DIR=/path/to/any-repo \
GRAVITATIONAL_LENS_DATA_ROOT=/path/to/any-repo/.gravitational_lens \
GRAVITATIONAL_LENS_PUBLIC_URL=http://localhost:8787 \
GRAVITATIONAL_LENS_BIND=127.0.0.1:8787 \
GRAVITATIONAL_LENS_INSECURE_DISABLE_AUTH=1 \
cargo run --bin gravitational_lens
```

Then open:

```text
http://localhost:8787/tickets
http://localhost:8787/tooling/manifest
http://localhost:8787/dashboard
```

The local `GRAVITATIONAL_LENS_INSECURE_DISABLE_AUTH=1` setting is only for development. Remove it for deployed use and configure OAuth credentials.

## Drop-in launcher for a repo

From this package directory:

```bash
scripts/drop-in-gravitational-lens.sh /path/to/any-repo
```

That creates a small launcher directory inside the target repo:

```text
/path/to/any-repo/.gravitational_lens/run-local.sh
/path/to/any-repo/.gravitational_lens/package-repo-zip.sh
/path/to/any-repo/.gravitational_lens/gravitational_lens.env.example
```

Then from the target repo:

```bash
.gravitational_lens/run-local.sh
```

## Container sidecar

```bash
docker build -f Containerfile -t gravitational_lens .

cd /path/to/any-repo
mkdir -p .gravitational_lens
docker run --rm -p 8787:8787 \
  -v "$PWD:/repo:ro" \
  -v "$PWD/.gravitational_lens:/data" \
  -e GRAVITATIONAL_LENS_REPO_DIR=/repo \
  -e GRAVITATIONAL_LENS_DATA_ROOT=/data \
  -e GRAVITATIONAL_LENS_BIND=0.0.0.0:8787 \
  -e GRAVITATIONAL_LENS_PUBLIC_URL=http://localhost:8787 \
  -e GRAVITATIONAL_LENS_INSECURE_DISABLE_AUTH=1 \
  gravitational_lens
```

## MCP smoke test

With local auth disabled:

```bash
curl -s http://localhost:8787/operator-mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' \
  | python3 -m json.tool
```

Create a ticket through MCP:

```bash
curl -s http://localhost:8787/operator-mcp \
  -H 'content-type: application/json' \
  -d '{
    "jsonrpc":"2.0",
    "id":2,
    "method":"tools/call",
    "params":{
      "name":"create_ticket",
      "arguments":{
        "subject":"Hello from gravitational_lens",
        "body":"This ticket was filed against the mounted repository.",
        "labels":["hello_world"],
        "priority":"P3",
        "ticket_type":"chore"
      }
    }
  }'
```

## Repo zip artifact

The code tools read the live mounted repository. `/repo.zip` is a separate optional route for a deploy-produced source snapshot.

```bash
cd /path/to/any-repo
/path/to/gravitational_lens/scripts/package-repo-zip.sh "$PWD" "$PWD/.gravitational_lens/repo.zip"
GRAVITATIONAL_LENS_REPO_ZIP="$PWD/.gravitational_lens/repo.zip" gravitational_lens
```

The packaging script skips common non-source directories such as `.git`, `target`, `node_modules`, `.gravitational_lens`, virtualenvs, caches, and build output.

## Web UI write access

The ticket pages are public read-only by default. To let a human create tickets, comment, confirm resolutions, cancel, or file follow-ups from the browser, configure a login:

```bash
cargo run --bin gravitational_lens_hash_password -- 'correct horse battery staple'
```

Then run with:

```bash
GRAVITATIONAL_LENS_HUMAN_USERNAME=johnny \
GRAVITATIONAL_LENS_HUMAN_PASSWORD_HASH='$argon2id$...' \
gravitational_lens
```

`GRAVITATIONAL_LENS_HUMAN_PASSWORD` exists as a local-development fallback and is hashed at startup. Prefer `GRAVITATIONAL_LENS_HUMAN_PASSWORD_HASH` for deployed environments.

## OAuth for MCP clients

When MCP auth is enabled, the binary auto-generates OAuth client credentials on first boot and persists them to:

```text
{GRAVITATIONAL_LENS_DATA_ROOT}/client.json
```

You can pin credentials instead:

```bash
GRAVITATIONAL_LENS_CLIENT_ID=my-client \
GRAVITATIONAL_LENS_CLIENT_SECRET='long-random-secret' \
GRAVITATIONAL_LENS_CLIENT_REDIRECT_URIS='https://ops.example.com/oauth/callback,http://localhost:6274/oauth/callback' \
gravitational_lens
```

OAuth metadata:

```text
/.well-known/oauth-authorization-server
/.well-known/oauth-protected-resource
```

Protected MCP endpoint:

```text
/operator-mcp
```

## Tool inventory

Code navigation:

```text
browse_codebase
outline
list_code_files
find_definition
find_references
read_code
search_code
```

Git review:

```text
git_log
git_diff
git_show_commit
git_file_history
```

Tickets:

```text
create_ticket
get_ticket
list_tickets
add_ticket_comment
file_followup
handler_respond_ticket
user_confirm_resolution
user_cancel_ticket
user_change_ticket_status
archive_closed_tickets
```

## Environment variables

| Variable | Default | Purpose |
|---|---:|---|
| `GRAVITATIONAL_LENS_DATA_ROOT` | `./.gravitational_lens` | Durable local state: tickets, OAuth tokens, session secret, optional repo zip. |
| `GRAVITATIONAL_LENS_BIND` | `127.0.0.1:8787` | Socket address to bind. |
| `GRAVITATIONAL_LENS_PUBLIC_URL` | `http://localhost:8787` | Public URL used in OAuth metadata. |
| `GRAVITATIONAL_LENS_REPO_DIR` | current directory | Target repository for code navigation and Git tools. |
| `GRAVITATIONAL_LENS_REPO_ZIP` | unset | Optional source zip served at `/repo.zip`. |
| `GRAVITATIONAL_LENS_INSECURE_DISABLE_AUTH` | false | Local-dev only: disables Bearer auth on MCP endpoints. |
| `GRAVITATIONAL_LENS_CLIENT_ID` | generated | MCP OAuth client id. |
| `GRAVITATIONAL_LENS_CLIENT_SECRET` | generated | MCP OAuth client secret. |
| `GRAVITATIONAL_LENS_CLIENT_REDIRECT_URIS` | local callback allow-list | Comma-separated redirect allow-list. |
| `GRAVITATIONAL_LENS_HUMAN_USERNAME` | unset | Enables human web UI login when paired with a password hash. |
| `GRAVITATIONAL_LENS_HUMAN_PASSWORD_HASH` | unset | Argon2id PHC password hash. |
| `GRAVITATIONAL_LENS_SESSION_SECRET` | persisted/generated | HMAC secret for human session cookies. |

## Implementation boundary

`ticket_ops` is the single source of truth for ticket lifecycle transitions. Both MCP tools and HTML POST forms call into it.

The code-review tools are read-only and anchored by `RepoRoot`. Absolute paths and path traversal are rejected before a file read occurs.
