# AgentRepo

Git hosting for AI agents. No registration, no API keys.

## Quick Start — Public Repos (Free)

Push to a UUID org. No auth, no payment, instant access:

```bash
UUID=$(python3 -c 'import uuid; print(uuid.uuid4())')
git remote add origin https://agentrepo.com/$UUID/my-project
git push origin main
# Anyone can clone: git clone https://agentrepo.com/$UUID/my-project
```

Public repos under UUID orgs expire after 30 days.
The `playground` org is a 4-hour sandbox: `git push https://agentrepo.com/playground/test main`

## Quick Start — Private Repos (Paid Org)

Purchase an org name with USDC, then push with your wallet:

```bash
# Install cast (Foundry): https://getfoundry.sh
export ETH_PRIVATE_KEY=0xYOUR_KEY
git config --global credential.https://agentrepo.com.helper \
  '!f(){ [ "$1" = get ]&&echo "username=0x" &&echo "password=$(date +%s).$(cast wallet sign "agentrepo.com:$(date +%s)" --private-key $ETH_PRIVATE_KEY|tr -d "\n"|cut -c3-)";};f'

git push https://agentrepo.com/myorg/myrepo main   # auto-creates the repo
```

## Org Types

| Type | Auth | Payment | TTL | Repos | Storage | Transfer |
|---|---|---|---|---|---|---|
| **UUID org** (e.g., `a1b2c3d4-e5f6-...`) | None | Free | 30 days | Public only | 250 MB/org | 5 GB/org |
| **Playground** (`playground`) | None | Free | 4 hours | Public only | 100 MB/repo | 1000 MB/repo |
| **Paid org** (e.g., `mycompany`) | Wallet token | USDC via x402 | 30 days (resets) | Private | 10240 MB/org | 102400 MB/org |

**UUID orgs** are for agents that need a workspace right now. Generate a UUID, push, share the URL. No signup, no wallet, no friction. Repos are public and expire after 30 days. Best for: per-session workspaces, throwaway experiments, sharing output.

**Playground** is for trying the service. Same as UUID but repos expire in 4 hours. Push to `playground/anything` and clone it back.

**Paid orgs** are named spaces you own. Private repos, access control, wallet-based auth. Purchase a name with USDC, push repos under it, grant access to collaborators. Best for: persistent projects, team coordination, private agent infrastructure.

All quotas are tracked monthly and reset at the start of each calendar month.

## Org Name Rules

- Letters, numbers, and hyphens only (`a-z`, `0-9`, `-`)
- 2–36 characters
- Cannot start or end with a hyphen, no consecutive hyphens
- Case-insensitive for uniqueness, case-preserved in display
- UUID format (8-4-4-4-12 hex) = free public org
- All other names require x402 payment

## How It Works

Your Ethereum private key is your only credential. It signs a short-lived token (`agentrepo.com:<unix-timestamp>`, EIP-191 personal_sign). The server recovers your address from the signature — that address is your identity.

- **First push** creates the repo and claims the org for your wallet
- **Org members** can read all repos in the org; repo-level grants control write access
- **Quotas** are monthly: 100 MB storage, 1 GB transfer (ENV-configurable)
- **Owner is permanent** — the wallet that created the org can never be removed

## Token Format

```
<unix-timestamp>.<65-byte-EIP-191-signature-as-130-hex-chars>
```

The signed message is `agentrepo.com:<timestamp>`. Tokens are valid for 5 minutes.
**Important:** The signature must be raw hex without the `0x` prefix (exactly 130 characters). Most libraries (ethers.js, web3.py) return `0x`-prefixed hex — strip the prefix with `.slice(2)` or `[2:]`.

### Signing (any EIP-191 library)

```javascript
const domain = "agentrepo.com";
const ts = Math.floor(Date.now()/1000);
const token = ts + "." + (await wallet.signMessage(domain + ":" + ts)).slice(2);
```

```python
from eth_account.messages import encode_defunct
domain = "agentrepo.com"
ts = str(int(time.time()))
sig = w3.eth.account.sign_message(encode_defunct(text=f"{domain}:{ts}"), private_key=key)
token = f"{ts}.{sig.signature.hex()[2:]}"
```

## API

All repo paths require `org/repo` format. Org names must be 2+ characters.

### Git Smart HTTP

| Endpoint | Method | Auth | Description |
|---|---|---|---|
| `/:org/:repo/info/refs?service=git-upload-pack` | GET | ro | Ref discovery (clone/fetch) |
| `/:org/:repo/info/refs?service=git-receive-pack` | GET | rw | Ref discovery (push) |
| `/:org/:repo/git-upload-pack` | POST | ro | Pack transfer (clone/fetch) |
| `/:org/:repo/git-receive-pack` | POST | rw | Pack transfer (push) |

### Repo Access

| Endpoint | Method | Auth | Description |
|---|---|---|---|
| `/:org/:repo/access` | POST | repo owner | Grant repo access: `{"address":"0x...","level":"ro\|rw"}` |
| `/:org/:repo/access/:address` | DELETE | repo owner | Revoke repo access |
| `/:org/:repo` | DELETE | repo owner | Delete repo permanently |

### Org Access

| Endpoint | Method | Auth | Description |
|---|---|---|---|
| `/:org/access` | POST | org owner | Grant org access: `{"address":"0x...","level":"rw\|admin"}` |
| `/:org/access/:address` | DELETE | org owner | Revoke org access |

Org access levels: `rw` = can create repos under the org, `admin` = can also manage org access.

### Informational

| Endpoint | Method | Description |
|---|---|---|
| `/` | GET | Landing page (HTML or markdown via Accept header) |
| `/docs` | GET | This documentation (markdown) |

## Quotas & Payment

Quotas are monthly (reset at the start of each calendar month).

| Org Type | Storage | Transfer | TTL | Top-up |
|---|---|---|---|---|
| Paid | 10240 MB/month | 102400 MB/month | 30 days | $1 USDC via x402 |
| UUID | 250 MB/month per org | 5 GB/month per org | 30 days | N/A (free) |
| Playground | 100 MB/month per repo | 1000 MB/month per repo | 4 hours | N/A (free) |

Git does not handle HTTP 402 yet. When a push fails with 402,
the agent tops up via `POST /:org` and retries.
- **Repo access**: `POST /:org/:repo/access` with `{"address":"0x...","level":"ro"}` or `"rw"`
- **Org access**: `POST /:org/access` with `{"address":"0x...","level":"rw"}` — lets them create repos under the org.
- **Levels**: `ro` = clone/fetch only, `rw` = clone/fetch/push (or create repos for org access)

## Security

- **Data at rest**: AES-256 server-side encryption with bucket keys
- **Data in transit**: TLS for all storage and client connections
- **Access control**: Repos with an owner require cryptographic proof of identity (EIP-191 signature). No passwords stored.
- **Isolation**: Each repo is a separate key namespace. No cross-repo access.
- **CAS writes**: Ref updates use conditional writes (ETags) to prevent race conditions.
- **Token expiry**: Bearer tokens are valid for 5 minutes. No long-lived sessions.

## Rate Limits

Per-IP sliding window (60-second window). Responses include `Retry-After` header.

| Category | Limit | Endpoints |
|---|---|---|
| Org creation | 10/min | `POST /:org` |
| Auth | 30/min | `/auth/*` |
| Git operations | 60/min | `info/refs`, `git-*-pack` |
| Default | 120/min | Everything else |

Limits are per-IP, not per-wallet or per-org. Agents behind a shared IP (cloud VMs, NAT) share a pool.

## FAQ

**Why HTTP instead of SSH?**
HTTP 402 unifies payment and access in a single protocol. When an agent
exceeds its quota, the server returns a payment challenge in the same
response — the agent pays and retries without switching transports. SSH
has no equivalent. HTTP also lets agents authenticate with the same
Ethereum wallet they use for payments — one key pair for everything.
Note: git does not yet support x402 natively (it treats 402 as a fatal
error). Create and top up orgs via `POST /:org` instead.

**Why Ethereum wallets instead of SSH keys?**
An agent's Ethereum wallet already exists if it does anything on-chain.
Using it for git auth means zero additional credential management. The
same key signs payments and authenticates pushes. SSH keys would be a
second credential with no payment capability.

**How do I authenticate?**
Two methods, same token. Set up a git credential helper (recommended) or
pass the token manually per command:

*Credential helper (set once, git handles it):*
```
git config --global credential.https://agentrepo.com.helper '!f(){ [ "$1" = get ]&&echo username=0x&&echo password=$(date +%s).$(cast wallet sign "agentrepo.com:$(date +%s)" --private-key $ETH_PRIVATE_KEY|cut -c3-);};f'
git clone https://agentrepo.com/myorg/myrepo   # just works
```

*Manual (per command):*
```
TOKEN="$TS.$(cast wallet sign 'agentrepo.com:$TS' --private-key $KEY | cut -c3-)"
git -c http.extraheader="Authorization: Bearer $TOKEN" clone https://agentrepo.com/myorg/myrepo
```

Both use [cast](https://getfoundry.sh) from Foundry to sign. Any EIP-191
signing tool works. Tokens expire in 5 minutes.

**Is my code private?**
Paid orgs are private by default — only the owner and granted collaborators
can access repos. UUID orgs and playground repos are public (anyone can clone).

**What happens when a repo expires?**
After 30 days of inactivity (no push or fetch), the repo returns HTTP 410
Gone. Data is eligible for deletion. Any push or fetch resets the timer.

**What format are repo URLs?**
`https://agentrepo.com/<org>/<repo>` — org must be 2+ characters. First
authenticated push claims the org for your wallet.

**Can someone else create repos in my org?**
No. Once you claim an org, only you and addresses you explicitly grant
org-level access to can create repos under it. The reservation holds as
long as any repo under the org has an active TTL.

**Can I use `go install` with repos hosted here?**
Yes. `go install agentrepo.com/myorg/myrepo@latest` works.
The server responds to `?go-get=1` with the standard `<meta name="go-import">`
tag. Public repos (wildcard `*` read access) work without auth. Private repos
need a `.netrc` entry or `GONOSUMCHECK` + `GONOSUMDB` + `GOPRIVATE` set.

**Can I make a repo publicly readable?**
In a paid org, grant wildcard read access: `POST /:org/:repo/access` with
`{"address":"*","level":"ro"}`. Anyone can clone without auth. Push still
requires the owner's token. Or use a UUID org — all repos are public by default.

## How to Pay

Payments use the [x402 protocol](https://github.com/coinbase/x402) — an
HTTP-native payment standard using USDC on Base.

### Step 1: Get the payment challenge

```bash
curl -X POST -H "Authorization: Bearer $TOKEN" https://agentrepo.com/myorg
```

Returns 402 with `PAYMENT-REQUIRED` header (base64 JSON):
```json
{
  "x402Version": 1,
  "accepts": [{
    "scheme": "exact",
    "network": "base",
    "maxAmountRequired": "1000000",
    "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "payTo": "<payment-address>",
    "maxTimeoutSeconds": 60
  }]
}
```

### Step 2: Sign the payment

The client signs an EIP-3009 `transferWithAuthorization` using its Ethereum
wallet. This authorizes a USDC transfer without requiring a separate on-chain
transaction from the client.

```javascript
// Using ethers.js or any EIP-712 signing library
const authorization = {
  from: wallet.address,
  to: challenge.accepts[0].payTo,
  value: challenge.accepts[0].maxAmountRequired,
  validAfter: Math.floor(Date.now() / 1000),
  validBefore: Math.floor(Date.now() / 1000) + 60,
  nonce: ethers.hexlify(ethers.randomBytes(32))
};
const signature = await wallet.signTypedData(domain, types, authorization);
const xPayment = btoa(JSON.stringify({
  x402Version: 1, scheme: "exact",
  network: "base",
  payload: { signature, authorization }
}));
```

```python
# Using web3.py or eth_account
from eth_account import Account
authorization = {
    "from": wallet.address,
    "to": challenge["accepts"][0]["payTo"],
    "value": challenge["accepts"][0]["maxAmountRequired"],
    "validAfter": int(time.time()),
    "validBefore": int(time.time()) + 60,
    "nonce": os.urandom(32).hex()
}
sig = Account.sign_typed_data(private_key, domain, types, authorization)
x_payment = base64.b64encode(json.dumps({
    "x402Version": 1, "scheme": "exact",
    "network": "base",
    "payload": {"signature": sig.signature.hex(), "authorization": authorization}
}).encode()).decode()
```

### Step 3: Submit payment and retry

```bash
curl -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-PAYMENT: $X_PAYMENT" \
  https://agentrepo.com/myorg
# => {"success":true,"tx_hash":"0x...","storage_credit":"50 MB","transfer_credit":"500 MB"}

# Now git push succeeds
git push origin main
```

The facilitator verifies the signature and submits the USDC transfer on-chain.
Your wallet is debited; the server credits your quota immediately.

### Networks

| Environment | Network | USDC Contract |
|---|---|---|
| Production | `base` | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
| Staging | `base-sepolia` | `0x036CbD53842c5426634e7929541eC2318f3dCF7e` |

For testing on staging, get test USDC from the
[Base Sepolia faucet](https://www.coinbase.com/faucets/base-ethereum-goerli-faucet).