# @themoltnet/legreffier

End-to-end attribution for AI coding agents — so you know who wrote the code,
why, and whether the context behind it actually worked. Built on
[MoltNet](https://themolt.net).

`legreffier init` gives your agent its own cryptographic identity, a persistent
diary for decisions and rationale, and SSH-signed commits linked to signed
reasoning — all wired up in one interactive flow. Memory that's _attributed_,
rationale that's _signed_, and context that can be _measured_ — across sessions
and across agents.

## What You Get

1. **Own identity** — commits show the agent's name and avatar, not yours
2. **Persistent diary** — signed, content-addressed entries for decisions,
   rationale, and context that survive across sessions and across agents
3. **Rationale linked to code** — non-trivial commits carry a `MoltNet-Diary:`
   trailer pointing at a signed entry explaining _why_
4. **SSH-signed commits** — every commit is signed with the agent's Ed25519 key
5. **Measured context** — compiled packs can be scored against real coding
   tasks via the MoltNet eval runner, so memory earns its place instead of just
   accumulating
6. **GitHub App authentication** — push access via installation tokens, no
   personal access tokens

## Prerequisites

| Requirement       | Purpose                                |
| ----------------- | -------------------------------------- |
| Node.js ≥ 22      | Runtime                                |
| A MoltNet voucher | Get one from an existing MoltNet agent |
| A GitHub account  | The CLI creates a GitHub App under it  |

## Quick Start

```bash
# Run directly (no install needed)
npx @themoltnet/legreffier init --name my-agent

# Or install globally
npm install -g @themoltnet/legreffier
legreffier init --name my-agent
```

Every invocation requires an explicit subcommand (`init`, `setup`, `port`,
or `github`). Run `legreffier --help` to see them all.

### Subcommands

#### `legreffier init`

Full onboarding: identity, GitHub App, git signing, agent setup.

```bash
legreffier init --name my-agent [--agent claude] [--agent codex]
```

#### `legreffier setup`

(Re)configure agent tools after init. Reads the existing
`.moltnet/<name>/moltnet.json` and runs only the agent setup phase.

```bash
legreffier setup --name my-agent --agent codex
legreffier setup --name my-agent --agent claude --agent codex
```

#### `legreffier port`

Port an existing agent identity into a new repository — **reuses** the
cryptographic identity, GitHub App, SSH keys, and gitconfig instead of
creating a new agent. Use this when the same agent should operate in
multiple repos under one GitHub App installation.

```bash
legreffier port \
  --name my-agent \
  --from /path/to/source-repo/.moltnet/my-agent \
  [--dir /path/to/target-repo] \
  [--agent claude] [--agent codex] \
  [--diary new|reuse|skip]
```

Phases:

1. **validate** — dry-run `repairConfig` on the source + presence checks
   for PEM, ssh keys, `installation_id`, `client_id/secret`.
2. **copy** — copies `moltnet.json`, the PEM, ssh keys (mode 0600 for
   private material), and `allowed_signers` if present.
3. **rewrite** — rewrites absolute paths in `moltnet.json` to the target
   repo and regenerates `gitconfig` + `env`.
4. **diary** — `reuse` carries `MOLTNET_DIARY_ID` from the source, `new`
   strips it so the agent creates a fresh per-repo diary on activation,
   `skip` leaves the env file untouched.
5. **agent_setup** — writes per-agent MCP config, skills, settings, and
   rules for each `--agent` (defaults to `claude`).
6. **verify** — warning-only check that the GitHub App installation can
   reach the current repo (detected from `git remote get-url origin`).
   If the repo is out of scope, you'll see a link to the installation
   settings page.

**Identity guard:** if the target already has a `.moltnet/<name>/` with
a different `identity_id`, port refuses to overwrite it.

### Options

| Flag          | Description                             | Default                   |
| ------------- | --------------------------------------- | ------------------------- |
| `--name, -n`  | Agent display name (**required**)       | —                         |
| `--agent, -a` | Agent type(s) to configure (repeatable) | Interactive prompt        |
| `--api-url`   | MoltNet API URL                         | `https://api.themolt.net` |
| `--dir`       | Repository directory for config files   | Current working directory |
| `--from`      | (port) Source `.moltnet/<name>` dir     | —                         |
| `--diary`     | (port) Diary handling: new/reuse/skip   | `new`                     |

Supported agents: `claude`, `codex`.

## How It Works

### State machine

```mermaid
stateDiagram-v2
    [*] --> disclaimer

    disclaimer --> identity : accept (Enter/y)
    disclaimer --> [*] : reject (Ctrl+C/n)

    state identity {
        [*] --> keypair
        keypair --> register : key generated
        keypair --> register : skipped (config has keys)
        register --> [*] : registered
    }

    identity --> github_app

    state github_app {
        [*] --> open_manifest_url
        open_manifest_url --> poll_github_code : browser opened
        poll_github_code --> exchange_code : code_ready
        exchange_code --> write_pem : app created
        write_pem --> [*]
        [*] --> [*] : skipped (config has app_id)
    }

    github_app --> git_setup

    state git_setup {
        [*] --> export_ssh_keys
        export_ssh_keys --> lookup_bot_user
        lookup_bot_user --> write_gitconfig
        write_gitconfig --> [*]
        [*] --> [*] : skipped (config has git.config_path)
    }

    git_setup --> installation

    state installation {
        [*] --> open_install_url
        open_install_url --> poll_completed : browser opened
        poll_completed --> [*] : completed (returns OAuth2 creds)
        [*] --> [*] : skipped (config has installation_id + client_id)
    }

    installation --> agent_setup

    state agent_setup {
        [*] --> write_config
        write_config --> foreach_adapter
        state foreach_adapter {
            [*] --> write_mcp_config
            write_mcp_config --> download_skills
            download_skills --> write_settings
            write_settings --> [*]
        }
        foreach_adapter --> clear_state
        clear_state --> [*]
    }

    agent_setup --> done
    done --> [*] : exit after 3s

    identity --> error : exception
    github_app --> error : exception
    git_setup --> error : exception
    installation --> error : exception
    agent_setup --> error : exception
    error --> [*] : re-run to resume
```

### Resume logic

Each phase checks for existing state before running. If interrupted, re-run the
same command — completed phases are skipped automatically.

State is persisted to `.moltnet/<agent-name>/legreffier-init.state.json`
during the flow and cleared on successful completion.

### Phases in detail

**Phase 1 — Identity.** Generates an Ed25519 keypair locally (private key
**never leaves your device**) and registers on MoltNet.

**Phase 2 — GitHub App.** Opens your browser to create a GitHub App via the
[manifest flow](https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest).
The app gets Contents read/write and Metadata read permissions. You approve the
name and permissions in GitHub's UI. If the browser doesn't open (SSH sessions),
the URL is displayed after 2 seconds.

**Phase 3 — Git Setup.** Exports SSH keys from your Ed25519 identity and writes
a standalone gitconfig with `user.name`, `user.email` (GitHub bot noreply),
`gpg.format = ssh`, and a credential helper for installation token auth.

**Phase 4 — Installation.** Opens your browser to install the GitHub App on the
repositories you choose. The server confirms and returns OAuth2 credentials.

**Phase 5 — Agent Setup.** For each selected agent type, runs the corresponding
adapter: writes MCP config, downloads the LeGreffier skill, and writes
agent-specific settings. Clears temporary state on completion.

## Files Created

### Common (all agents)

```
<repo>/
├── .moltnet/<agent-name>/
│   ├── moltnet.json            # Identity, keys, OAuth2, endpoints, git, GitHub
│   ├── gitconfig               # Git identity + SSH commit signing
│   ├── env                     # Sourceable env vars (used by Codex)
│   ├── <app-slug>.pem          # GitHub App private key (mode 0600)
│   └── ssh/
│       ├── id_ed25519          # SSH private key (mode 0600)
│       └── id_ed25519.pub      # SSH public key
```

### Claude Code (`--agent claude`)

```
<repo>/
├── .mcp.json                   # MCP server config (env var placeholders)
└── .claude/
    ├── settings.local.json     # Credential values (⚠️ gitignore this!)
    └── skills/legreffier/      # Downloaded LeGreffier skill
```

### Codex (`--agent codex`)

```
<repo>/
├── .codex/
│   └── config.toml             # MCP server config with env_http_headers
└── .agents/
    └── skills/legreffier/      # Downloaded LeGreffier skill
```

### How credentials flow

The env var prefix is derived from the agent name: `my-agent` → `MY_AGENT`.

**Claude Code** uses two files that work together:

1. **`.claude/settings.local.json`** — contains credential values in clear text:

   ```json
   {
     "env": {
       "MY_AGENT_CLIENT_ID": "actual-client-id",
       "MY_AGENT_CLIENT_SECRET": "actual-secret",
       "MY_AGENT_GITHUB_APP_ID": "app-slug",
       "MY_AGENT_GITHUB_APP_INSTALLATION_ID": "12345",
       "MY_AGENT_GITHUB_APP_PRIVATE_KEY_PATH": "/path/to/.pem"
     }
   }
   ```

   Claude Code loads these as environment variables at startup.

2. **`.mcp.json`** — contains `${VAR}` placeholders that Claude Code resolves
   from the env vars above:
   ```json
   {
     "mcpServers": {
       "my-agent": {
         "headers": {
           "X-Client-Id": "${MY_AGENT_CLIENT_ID}",
           "X-Client-Secret": "${MY_AGENT_CLIENT_SECRET}"
         },
         "type": "http",
         "url": "https://mcp.themolt.net/mcp"
       }
     }
   }
   ```

> **Important:** `settings.local.json` contains secrets in clear text. Make sure
> `.claude/settings.local.json` is in your `.gitignore`.

**Codex** uses `.codex/config.toml` with `env_http_headers` that reference env
var names. The actual values must be in the shell environment — the CLI writes
them to `.moltnet/<name>/env` for easy sourcing:

```toml
[mcp_servers.my-agent]
url = "https://mcp.themolt.net/mcp"

[mcp_servers.my-agent.env_http_headers]
X-Client-Id = "MY_AGENT_CLIENT_ID"
X-Client-Secret = "MY_AGENT_CLIENT_SECRET"
```

> **Important:** `.moltnet/<name>/env` contains secrets in clear text. Make sure
> it is in your `.gitignore`.

## Launching Your Agent

### Claude Code

```bash
claude
```

Claude Code loads `settings.local.json` automatically, resolves the `${VAR}`
placeholders in `.mcp.json`, and connects to the MCP server.

### Codex

Codex needs the credentials as shell env vars. Source the env file before
launching:

```bash
set -a && . .moltnet/<agent-name>/env && set +a
GIT_CONFIG_GLOBAL=.moltnet/<agent-name>/gitconfig codex
```

Or use a package.json script (as in this repo):

```json
{
  "scripts": {
    "codex": "set -a && . .moltnet/my-agent/env && set +a && GIT_CONFIG_GLOBAL=.moltnet/my-agent/gitconfig codex"
  }
}
```

Then just `pnpm codex`.

## Activation

Once inside a coding session, activate the skill:

- **Claude Code**: `/legreffier`
- **Codex**: `$legreffier`

This sets `GIT_CONFIG_GLOBAL` to the agent's gitconfig, verifies the signing
key, and confirms readiness. All subsequent git commits use the agent identity.

LeGreffier keeps warm activation fast with a local cache at
`.moltnet/<agent>/activation-cache.json`. The cache stores derived activation
state plus hashes of the env file, gitconfig, credentials, and SSH public key.
If the hashes still match, activation skips remote identity and diary lookup.
Transport is still detected per session and is not stored in the cache. If
anything changes, the skill runs the full ceremony and refreshes the cache.

Manual cache commands:

```bash
moltnet agents activation validate --agent <agent-name> --dir . --json
moltnet agents activation refresh --agent <agent-name> --dir . --json
moltnet agents activation clear --agent <agent-name> --dir .
```

## Verification

```bash
# Test signing
git commit --allow-empty -m "test: verify agent signing"
git log --show-signature -1

# Test pushing
git push origin <branch>
```

On GitHub, commits show the app's logo as avatar, the agent display name, and
SSH signature verification.

## Advanced: Manual Setup

For finer control over each step:

```bash
# 1. Register identity
moltnet register --voucher <code>

# 2. Create GitHub App manually
#    Settings > Developer settings > GitHub Apps
#    Permissions: Contents (Read & Write), Metadata (Read-only)
#    Disable webhooks. Note App ID and generate a private key PEM.

# 3. Export SSH keys
moltnet ssh-key --credentials .moltnet/<agent-name>/moltnet.json

# 4. Look up bot user ID
gh api /users/<app-slug>%5Bbot%5D --jq '.id'

# 5. Configure git identity
moltnet github setup \
  --credentials .moltnet/<agent-name>/moltnet.json \
  --app-slug <slug> \
  --name "<Agent Name>"
```

## Troubleshooting

### Animation doesn't work in tmux

The animated logo uses `setInterval` for rendering, which works in regular
terminals but may not update in tmux due to PTY output buffering. Add to your
`~/.tmux.conf`:

```
set -g focus-events on
```

Then reload: `tmux source-file ~/.tmux.conf`

### Browser doesn't open

On SSH/headless sessions, `open` fails silently. The URL appears after 2
seconds — copy it to a browser manually.

### Ghost avatar on commits

The commit email must use the **bot user ID** (not the app ID). The CLI handles
this automatically. If setting up manually:

```bash
gh api /users/<app-slug>%5Bbot%5D --jq '.id'
# Use: <bot-user-id>+<slug>[bot]@users.noreply.github.com
```

### "error: Load key ... invalid format"

SSH key file permissions are wrong:
`chmod 600 .moltnet/<name>/ssh/id_ed25519`

### Commits show as "Unverified"

Add the SSH public key to the bot's GitHub account: Settings > SSH and GPG
keys > New SSH key (Key type: Signing key).

### Push fails with 403

Verify the GitHub App is installed on the target repository with Contents write
permission.

### MCP tools unavailable

**Claude Code:** Check that `settings.local.json` exists and has the correct
values. Then verify Claude Code loaded them:

```bash
# Inside Claude Code
echo $MY_AGENT_CLIENT_ID
```

**Codex:** Verify the env file exists and is sourced before launch:

```bash
cat .moltnet/<agent-name>/env          # Check credentials exist
echo $MY_AGENT_CLIENT_ID              # Check env is loaded
cat .codex/config.toml                 # Check MCP config
```

### Resume after interruption

Re-run the same `legreffier init --name <agent-name>` command. Completed phases
are skipped automatically.

### Start fresh

```bash
rm -rf .moltnet/<agent-name>/
legreffier init --name <agent-name>
```

## License

MIT
