# Gitwal, for agents

This host is Git over HTTPS. There is no signup, no SSH, and no web account. You mint a token on the client, pick a one-segment name, and push. The first valid-token receive-pack POST that wins the name owns it.

Human explanation: `/`. This file is the operating manual.

## Token

Generate and install one locally:

```
curl -fsSL https://gitwal.io/setup | bash
```

The script writes a URL-scoped Bearer credential to `~/.config/gitwal/credentials.gitconfig`, makes the file readable only by you, and includes it from your global Git config. Running it again reuses the existing token. Normal Git commands to `https://gitwal.io/` then authenticate automatically.

Shape: `nwl_` plus exactly 64 lowercase hex characters. The server stores only `sha256` of the secret.

## Authenticate

Never put the token in the URL. Never wait for HTTP 401. This host never returns 401 and never sends `WWW-Authenticate`, so Git will not prompt and will not send URL-embedded Basic.

For a one-command credential that is not installed:

```
NARWAL_TOKEN="nwl_$(openssl rand -hex 32)"
git -c http.extraHeader="Authorization: Bearer $NARWAL_TOKEN" <command> https://gitwal.io/<name>.git
```

The `/setup` script is the persistent alternative. It scopes the header to gitwal.io.

## Create a repository

Pick a flat name: one lowercase segment, `[a-z0-9._-]`, 6–64 characters, first and last alphanumeric. Optional `.git` suffix. Nested `/owner/name.git` is rejected.

```
git init -b main
git add .
git commit -m "first"
git push https://gitwal.io/my-project.git main
```

A name that does not exist yet is created by that push. Discovery GET does not claim the name. `--dry-run` does not claim the name.

New repositories are private. Push implies fetch.

## Fetch and clone

Private, after running `/setup`:

```
git clone https://gitwal.io/my-project.git
```

Public: anonymous clone works. A malformed token on a public fetch is 403.

A private repo you are not on, and a name that does not exist, both 404 with the same body. Do not probe for existence that way.

## Visibility and collaborators

Admin is JSON, same Bearer token, only if that token is on the repo ACL. Unlisted callers get `{"error":"no_such_repo"}`.

```
GET  /my-project/settings          POST /my-project/settings
GET  /_narwal/repos/my-project     PATCH /_narwal/repos/my-project
```

`/<name>/settings` is the short form: GET returns the current settings as JSON,
POST changes them. `/_narwal/repos/<name>` is the same thing plus the repo's refs
and HEAD. Both take the same body:

```
{"visibility":"public"}
{"visibility":"private"}
{"add_push_token":{"hash":"<64 lowercase hex sha256>","label":"optional"}}
{"remove_push_token":{"hash":"<64 lowercase hex sha256>"}}
```

Send the collaborator's token hash, never their raw token. The ACL cannot be emptied.

## Repo page

`https://gitwal.io/<name>` is a plain HTML page with the repo's stats and its clone
command. Same access rule as fetch: anonymous for public names, Bearer token for
private ones, and a 404 otherwise. Add `?format=json` for the machine-readable form.

## Monitor

Same access as fetch. Open `https://gitwal.io/<name>/monitor` in a browser for the operations
page: visibility and cache status, an index-sync diagram comparing the S3 etag to the serving
node's cache, refs and HEAD, WAL entries with pack sizes and sha256, the nodes holding a warm
cache, and the deployment this host runs on. Each node is marked `in sync` or `behind` against
the current index, and drops off the list once its presence record goes stale.

Add `?format=json`, or send `Accept: application/json`, for the same state as JSON. Token hashes
are not on this page; use the admin surface for the ACL.

Private: send the Bearer header. Public: anonymous.

The page lists WAL entries (size, sha256, ref updates), this process's cache, and nodes that have written presence after warming the name. Presence is a hint. S3 is the source of truth.

## Failures you will see

| Situation | What Git prints |
| --- | --- |
| No `Authorization` header on push | 403 `text/plain` with this same generator and `-c` form |
| Token not on the ACL | 403 (discovery) or `ng` on POST |
| Nested path | 400, names the flat form |
| Tokenless fetch of a private or missing name | 404 |

Supported Git: 2.39 through 2.55.
