Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Adds HTTP request-body inspection to Keep policies. File- and pack-based `networ

### Added

- **GitHub Copilot CLI agent** — run GitHub Copilot CLI with `moat copilot`. Copilot uses the existing `github` grant: Moat injects that GitHub token for GitHub/Copilot API hosts plus HTTPS git, while the container receives only placeholders. `moat copilot` installs `@github/copilot`, stages Copilot config/context, passes `--allow-all` by default, and supports `copilot.model`, `copilot.experimental`, and `copilot.autopilot` in `moat.yaml`. See [Running GitHub Copilot CLI](https://majorcontext.com/moat/guides/copilot). ([#436](https://github.com/majorcontext/moat/pull/436))
- **Pi packages & safe defaults** — declare Pi extensions/skills/themes in `pi.packages` (remote `npm:`/`git:`/`https:`/`ssh:` sources) and Moat installs them into the image at build time via `pi install`, baked into a reproducible cached layer. Every `moat pi` image also bakes a safe `~/.pi/agent/settings.json` — `defaultProjectTrust: never` (a checked-out repo's own `.pi/` extensions, which are arbitrary code, do not auto-load), telemetry off, quiet startup — that a workspace cannot override. Because Pi config can redirect model traffic to any host, `moat pi` now warns under a permissive network policy (only `network.policy: strict` truly constrains egress). See [Running Pi](https://majorcontext.com/moat/guides/pi). ([#434](https://github.com/majorcontext/moat/pull/434))
- **Pi coding agent** — run the [Pi coding agent](https://github.com/earendil-works/pi) with `moat pi`. Pi has no credential of its own; it runs against your existing `anthropic` or `openai` grant. When exactly one is configured it is used automatically; when both are, choose one with `--provider` or `pi.provider` in `moat.yaml`. Only the `anthropic` and `openai` backends are supported today — any other backend, or a missing/ambiguous grant, fails before a container is created. Configure with the `pi:` block (`provider`, `model`). See [Running Pi](https://majorcontext.com/moat/guides/pi) and `examples/agent-pi`. ([#433](https://github.com/majorcontext/moat/pull/433))
- **`opentofu` and `terragrunt` dependencies** — two new managed cloud tools. `opentofu` installs the OpenTofu CLI as the `tofu` command; `terragrunt` installs the Terragrunt orchestration wrapper. Both install as prebuilt release binaries with no image rebuild cost beyond their own layer. Terragrunt delegates to a Terraform or OpenTofu binary on `PATH`, so pair it with an engine — `dependencies: [terraform, terragrunt]`, or `dependencies: [opentofu, terragrunt]` with `env.TERRAGRUNT_TFPATH: tofu`. See [Dependencies](https://majorcontext.com/moat/reference/dependencies). ([#430](https://github.com/majorcontext/moat/pull/430))
Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,15 @@ moat codex # Interactive mode
moat codex -p "explain this codebase" # Non-interactive
```

Both agents run in isolated containers with credentials injected at the network layer. See the [Running Claude Code](docs/content/guides/01-claude-code.md) and [Running Codex](docs/content/guides/02-codex.md) guides for details.
### GitHub Copilot CLI

```bash
moat grant github # One-time: imports GitHub credentials with Copilot access
moat copilot # Interactive mode
moat copilot -p "fix the failing tests" # Non-interactive
```

Agents run in isolated containers with credentials injected at the network layer. See the [Running Claude Code](docs/content/guides/01-claude-code.md), [Running Codex](docs/content/guides/02-codex.md), and [Running GitHub Copilot CLI](docs/content/guides/17-copilot.md) guides for details.

## Configuration

Expand Down Expand Up @@ -136,6 +144,7 @@ See the [moat.yaml reference](docs/content/reference/02-moat-yaml.md) for all op
| Command | Description |
|---------|-------------|
| `moat claude [workspace]` | Run Claude Code |
| `moat copilot [workspace]` | Run GitHub Copilot CLI |
| `moat codex [workspace]` | Run Codex |
| `moat run [path] [-- cmd]` | Run an agent |
| `moat join <run> <agent>` | Run another agent in a running container |
Expand Down
2 changes: 2 additions & 0 deletions cmd/moat/cli/grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ func runGrant(cmd *cobra.Command, args []string) error {
// "google" is an alias for "gemini"
// "anthropic" and "claude" are separate registered providers; no remapping needed
switch providerName {
case "copilot":
return fmt.Errorf("GitHub Copilot CLI uses GitHub credentials.\n\nRun: moat grant github")
case "openai":
providerName = "codex"
case "google":
Expand Down
3 changes: 3 additions & 0 deletions cmd/moat/cli/grant_providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ func runGrantProviders(cmd *cobra.Command, args []string) error {
// Skip agent-only providers (claude, codex, gemini) from grant listing
// if they have a CLI name alias — we show the alias instead
name := p.Name()
if name == "copilot" {
continue
}
if cliName, ok := goProviderCLINames[name]; ok {
name = cliName
}
Expand Down
11 changes: 11 additions & 0 deletions cmd/moat/cli/grant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"os"
"strings"
"testing"

"github.com/majorcontext/moat/internal/credential"
Expand Down Expand Up @@ -60,3 +61,13 @@ func TestGrantMCP(t *testing.T) {
t.Errorf("expected token 'test-api-key-123', got %q", cred.Token)
}
}

func TestGrantCopilotUsesGitHub(t *testing.T) {
err := runGrant(grantCmd, []string{"copilot"})
if err == nil {
t.Fatal("runGrant(copilot) = nil, want error")
}
if !strings.Contains(err.Error(), "moat grant github") {
t.Fatalf("runGrant(copilot) error = %v, want moat grant github guidance", err)
}
}
80 changes: 80 additions & 0 deletions docs/content/guides/17-copilot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
title: "Running GitHub Copilot CLI"
navTitle: "Copilot CLI"
description: "Run GitHub Copilot CLI in an isolated container with credential injection."
keywords: ["moat", "copilot", "github copilot", "ai agent", "coding assistant"]
---

# Running GitHub Copilot CLI

This guide covers running GitHub Copilot CLI in a Moat container.

## Prerequisites

- Moat installed
- An active GitHub Copilot subscription
- A Copilot-capable GitHub token, either from `gh auth login` or a fine-grained PAT with the **Copilot Requests** permission

## Granting GitHub credentials

Run `moat grant github` to configure authentication:

```bash
moat grant github
```

Moat checks `GITHUB_TOKEN` and `GH_TOKEN` first. If none are set, it can use `gh auth token` from GitHub CLI, or prompt for a PAT.

The token must be able to use Copilot. GitHub CLI OAuth tokens from `gh auth login` work when the account has an active Copilot subscription. Fine-grained PATs must be created for your personal account with the **Copilot Requests** account permission. Classic PATs are not supported by GitHub Copilot CLI.

Before starting a Copilot container, Moat checks the stored GitHub credential against `api.github.com/copilot_internal/user`. If the token cannot use Copilot, the run fails before building or starting the container.

## How credentials are injected

The raw GitHub credential is stored encrypted on the host. Inside the container, Moat sets format-valid placeholders in `COPILOT_GITHUB_TOKEN` and `GH_TOKEN`. The proxy intercepts GitHub and Copilot HTTPS requests and injects the real GitHub token for `api.github.com`, Copilot API hosts, and HTTPS git operations against `github.com`.

## Running Copilot

Interactive mode:

```bash
moat copilot
moat copilot ./my-project
```

Interactive mode with an initial prompt:

```bash
moat copilot -- "explain this codebase"
```

Non-interactive mode:

```bash
moat copilot -p "fix the failing tests"
```

Moat passes `--allow-all` to Copilot CLI by default so the agent can complete tasks without approval prompts. The container, workspace mount, and network policy still constrain the run.

## Configuration

```yaml
agent: copilot
grants:
- github

copilot:
model: gpt-5.4
experimental: false
autopilot: false
```
`moat copilot` adds the `github` grant automatically when a GitHub credential is configured, so you do not need to list it unless you use `moat run` directly.

## Network policy

`moat copilot` adds the GitHub and Copilot hosts it needs to the run's network rules. Under `network.policy: strict`, add any extra hosts your task needs with `--allow-host` or `network.rules`.

```bash
moat copilot --allow-host registry.npmjs.org -p "update dependencies"
```
63 changes: 60 additions & 3 deletions docs/content/reference/01-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ If a name matches multiple runs, batch commands (`stop`, `destroy`) prompt for c

## Common agent flags

The agent commands (`moat claude`, `moat codex`, `moat gemini`, `moat pi`) share the following flags. These flags work identically across `moat claude`, `moat codex`, `moat gemini`, and `moat pi`.
The agent commands (`moat claude`, `moat copilot`, `moat codex`, `moat gemini`, `moat pi`) share the following flags. These flags work identically across `moat claude`, `moat copilot`, `moat codex`, `moat gemini`, and `moat pi`.

| Flag | Description |
|------|-------------|
Expand All @@ -62,6 +62,7 @@ All agent commands support passing an initial prompt after `--`. Unlike `-p`, wh

```bash
moat claude -- "is this thing on?"
moat copilot -- "explain this codebase"
moat codex -- "explain this codebase"
```

Expand Down Expand Up @@ -281,6 +282,62 @@ moat claude --noyolo

---

## moat copilot

Run GitHub Copilot CLI in a container.

```
moat copilot [workspace] [flags] [-- initial-prompt]
```

In addition to the command-specific flags below, `moat copilot` accepts all [common agent flags](#common-agent-flags).

`moat copilot` uses the `github` grant. Before creating the run, Moat checks that the stored GitHub credential can use Copilot. The container receives only Copilot/GitHub token placeholders; Moat's proxy injects the real GitHub token for GitHub API, Copilot API, and HTTPS git requests.

### Arguments

| Argument | Description |
|----------|-------------|
| `workspace` | Workspace directory (default: current directory) |
| `initial-prompt` | Text after `--` is passed to Copilot as an initial prompt (interactive mode) |

### Command-specific flags

| Flag | Description |
|------|-------------|
| `-p`, `--prompt TEXT` | Run non-interactive with prompt |
| `--allow-all` | Allow all Copilot tools, paths, and URLs without prompting. Default: `true`. |
| `--model MODEL` | Select the model to use. Overrides `copilot.model`. |
| `--experimental` | Enable Copilot CLI experimental features. |
| `--autopilot` | Start Copilot CLI in autopilot mode. |

### Examples

```bash
# One-time credential setup
moat grant github

# Interactive Copilot CLI
moat copilot

# In specific directory
moat copilot ./my-project

# Interactive with initial prompt
moat copilot -- "explain this codebase"

# Non-interactive with prompt
moat copilot -p "fix the failing tests"

# Select a model
moat copilot --model gpt-5.4

# Run in a git worktree
moat copilot --worktree=dark-mode --prompt "implement dark mode"
```

---

## moat codex

Run OpenAI Codex CLI in a container.
Expand Down Expand Up @@ -568,8 +625,8 @@ moat grant <provider>[:<scopes>]

GitHub credentials are obtained from multiple sources, in order of preference:

1. **gh CLI** -- Uses token from `gh auth token` if available
2. **Environment variable** -- Falls back to `GITHUB_TOKEN` or `GH_TOKEN`
1. **Environment variable** -- Uses `GITHUB_TOKEN` or `GH_TOKEN` if set
2. **gh CLI** -- Uses token from `gh auth token` if available
3. **Personal Access Token** -- Interactive prompt for manual entry

```bash
Expand Down
51 changes: 51 additions & 0 deletions docs/content/reference/02-moat-yaml.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ codex:
grant: openai
cwd: /workspace

# GitHub Copilot CLI
copilot:
model: gpt-5.4
experimental: false
autopilot: false

# Gemini CLI
gemini:
sync_logs: true
Expand Down Expand Up @@ -1574,6 +1580,51 @@ When `grant` is specified, the corresponding environment variable is set automat

---

## GitHub Copilot CLI

### copilot.model

Model to pass to `copilot --model`.

```yaml
copilot:
model: gpt-5.4
```

- Type: `string`
- Default: Copilot CLI default
- CLI override: `moat copilot --model`

### copilot.experimental

Start Copilot CLI with experimental features enabled.

```yaml
copilot:
experimental: true
```

- Type: `boolean`
- Default: `false`
- CLI override: `moat copilot --experimental`

### copilot.autopilot

Start Copilot CLI in autopilot mode.

```yaml
copilot:
autopilot: true
```

- Type: `boolean`
- Default: `false`
- CLI override: `moat copilot --autopilot`

`moat copilot` uses the `github` grant. Run `moat grant github` before starting a session. See [Running GitHub Copilot CLI](../guides/17-copilot.md).

---

## Gemini

### gemini.sync_logs
Expand Down
38 changes: 35 additions & 3 deletions docs/content/reference/04-grants.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ No flags. The command automatically detects your credential source.

### Credential sources (in order of preference)

1. **gh CLI** -- Uses the token from `gh auth token` if the GitHub CLI is installed and authenticated
2. **Environment variable** -- Falls back to `GITHUB_TOKEN` or `GH_TOKEN` if set
1. **Environment variable** -- Uses `GITHUB_TOKEN` or `GH_TOKEN` if set
2. **gh CLI** -- Uses the token from `gh auth token` if the GitHub CLI is installed and authenticated
3. **Personal Access Token** -- Interactive prompt for manual PAT entry

### What it injects
Expand All @@ -63,9 +63,13 @@ The proxy injects an `Authorization` header, using the scheme each host expects:

The container receives `GH_TOKEN` set to a format-valid placeholder so the gh CLI works without prompting. Git is configured with `http.proxyAuthMethod=basic` so it authenticates to the proxy and can establish the HTTPS tunnel.

For `moat copilot` runs, the same GitHub grant is also injected for Copilot API hosts, and the container receives a format-valid `COPILOT_GITHUB_TOKEN` placeholder. The GitHub token must be Copilot-capable: use a GitHub CLI OAuth token from an account with Copilot access, or a fine-grained PAT from your personal account with the **Copilot Requests** account permission.

Moat validates Copilot access before starting a Copilot run by calling `api.github.com/copilot_internal/user` with the stored GitHub credential.

### Refresh behavior

Tokens sourced from `gh auth token` or environment variables are refreshed every 30 minutes. PATs entered manually are static.
Tokens sourced from `gh auth token` are refreshed every 30 minutes. Environment variables and PATs entered manually are static.

### moat.yaml

Expand All @@ -88,6 +92,34 @@ GitHub credential saved
$ moat run --grant github ./my-project
```

## GitHub Copilot

GitHub Copilot CLI uses the `github` grant. There is no separate Copilot credential to configure.

```bash
moat grant github
```

Classic PATs are not supported by GitHub Copilot CLI. Fine-grained PATs must be created for your personal account, not an organization, and need the **Copilot Requests** account permission.

### What it injects

For Copilot runs, the proxy injects the GitHub token for:

- `api.github.com` and Copilot API hosts -- Copilot CLI authentication, model traffic, and GitHub API calls
- `github.com` -- HTTPS git operations

The container receives `COPILOT_GITHUB_TOKEN` and `GH_TOKEN` placeholders. Copilot CLI and gh CLI authenticate normally, but the raw token stays on the host.

### moat.yaml

```yaml
grants:
- github
```

Use this grant with `moat copilot`; the command adds it automatically.

## Anthropic / Claude

Anthropic credentials are split into two separate grant types:
Expand Down
13 changes: 13 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type Config struct {
Network NetworkConfig `yaml:"network,omitempty"`
Command []string `yaml:"command,omitempty"`
Claude ClaudeConfig `yaml:"claude,omitempty"`
Copilot CopilotConfig `yaml:"copilot,omitempty"`
Codex CodexConfig `yaml:"codex,omitempty"`
Gemini GeminiConfig `yaml:"gemini,omitempty"`
Pi PiConfig `yaml:"pi,omitempty"`
Expand Down Expand Up @@ -333,6 +334,18 @@ type CodexConfig struct {
MCP map[string]MCPServerSpec `yaml:"mcp,omitempty"`
}

// CopilotConfig configures GitHub Copilot CLI integration options.
type CopilotConfig struct {
// Model optionally selects the model passed to `copilot --model`.
Model string `yaml:"model,omitempty"`

// Experimental starts Copilot CLI with --experimental.
Experimental bool `yaml:"experimental,omitempty"`

// Autopilot starts Copilot CLI in autopilot mode.
Autopilot bool `yaml:"autopilot,omitempty"`
}

// GeminiConfig configures Google Gemini CLI integration options.
type GeminiConfig struct {
// SyncLogs enables mounting Gemini's session logs directory so logs from
Expand Down
Loading
Loading