feat(cli): add GitHub Copilot CLI as fourth supported agent - #94
feat(cli): add GitHub Copilot CLI as fourth supported agent#94shejnowicz wants to merge 15 commits into
Conversation
Design: add `copilot` alongside claude/cursor/codex. Auth via COPILOT_GITHUB_TOKEN env with placeholder substitution (identical to Codex Bearer-token mechanism). Install as npm package under NodeSource Node.js 22 in the Dockerfile. Config directory ~/.copilot/ with optional bind-mount (mcp-config.json + session-state/). VS Code extensions GitHub.copilot + GitHub.copilot-chat. Network allowlist covers *.githubcopilot.com + api.github.com. Plan: 7 tasks fork the Codex integration pattern (PR#87) with per-agent dispatcher hooks in agents.bash, an add_copilot_config_volumes in composefile.bash, new settings template + mitmproxy addon files, and a hands-on end-to-end verification that exercises real auth and network traffic. Streaming flags kept empty by default (Copilot's SSE looks Codex-shaped, not Cursor-shaped) with a Task 6 fallback if verification finds otherwise. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds copilot case branches to four dispatcher functions: - sct_agent_vscode_extension: emits GitHub.copilot - sct_agent_devcontainer_settings_block: no forced settings (empty) - sct_agent_compose_environment_entries: no env vars (empty) - sct_agent_mitm_streaming_flags: buffered (empty, verified in Task 6) Also adds four test cases that verify each dispatcher returns expected values for copilot agent. Step 1 verification confirmed sct_agent_vscode_extension callers treat output as single token; chat extension omitted, user adds manually if desired. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add sct_agent_docker_install_block, sct_agent_docker_home_prep_block, and sct_agent_user_init_block cases for copilot agent. Installs Node.js 22 via NodeSource, @github/copilot npm package, pre-creates ~/.copilot, and runs copilot --version health check on startup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds add_copilot_config_volumes to composefile.bash — mounts ~/.copilot/mcp-config.json and ~/.copilot/session-state into the agent container — and wires it into customize_compose_file's copilot) branch via SANDCAT_MOUNT_COPILOT_CONFIG (default true). Header comment updated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds settings-user-copilot.json with COPILOT_GITHUB_TOKEN secret and GitHub network rules; adds mitmproxy_addon_copilot.py (thin SandcatAddon wrapper, no streaming flags); wires copilot) case into the mitm_addon_file switch in devcontainer.bash; adds bats tests for template resolution and addon rendering; fixes pre-existing interactive stub failures caused by copilot being added to sct_available_agents without updating the stubs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two runtime issues surfaced by the hands-on integration test that the task-1..5 plan didn't anticipate: 1. Copilot CLI validates the token format client-side — anything not prefixed with `gho_` or `github_pat_` triggers "No authentication information found" and the request never reaches mitmproxy. The default sandcat placeholder `SANDCAT_PLACEHOLDER_COPILOT_GITHUB_TOKEN` is rejected on that check. Fix: allow a per-secret `placeholder` override in settings.json (mitmproxy_addon_common falls back to the default when not set), and set the copilot template's placeholder to `gho_SANDCAT_PLACEHOLDER_COPILOT_GITHUB_TOKEN`. Mitmproxy's `.replace(placeholder, value)` still swaps the ENTIRE string with the real token, so the on-the-wire request is well-formed. Backward- compatible: existing secrets without a `placeholder` field keep the old default. 2. `~/.copilot/session-state` was mounted read-only. Copilot appends to `events.jsonl` under a session UUID directory on every prompt and fails with EROFS otherwise. Change the bind-mount to rw — same trust posture as other agent config mounts. Verified end-to-end: `copilot -p "reply PONG" --allow-all-tools` from inside the sandbox agent returned `PONG` and 2.07 AI credits were billed against the real Copilot subscription — proof the substituted token reached the real API. `curl https://example.com/` still returns 403 (non-allowlisted host rejected as expected). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add GitHub Copilot CLI to the supported agents in README.md and cli/README.md. Includes setup instructions, authentication options (fine-grained PAT and gh OAuth), notes on image size delta from Node.js 22, and VS Code extension integration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove claim that GitHub.copilot-chat is shipped (only GitHub.copilot is set) - Add gho_ prefix to placeholder (gho_SANDCAT_PLACEHOLDER_COPILOT_GITHUB_TOKEN) - Remove copilot-yolo alias claim (not actually implemented in agents.bash) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add two unit tests to TestOpSecretResolution: - test_secret_uses_default_placeholder_when_not_specified: verifies that an entry without a `placeholder` field resolves to SANDCAT_PLACEHOLDER_<NAME> - test_secret_honors_custom_placeholder_field: verifies that a custom `placeholder` value (e.g. gho_custom_placeholder) is stored verbatim, covering the Copilot client-side token-format check regression. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove docs/superpowers/specs/2026-08-11-copilot-agent-design.md and docs/superpowers/plans/2026-08-11-copilot-agent.md — SDD artifacts are not part of the shipped deliverable and are excluded from PRs per standing project convention. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot CLI natively accepts --yolo as a shortcut for --allow-all-tools --allow-all-paths --allow-all-urls. Add a shell alias in .bashrc so `copilot-yolo` works out of the box, mirroring the existing `claude-yolo` and `codex-yolo` aliases. The sandcat network isolation is the security boundary, so bypassing in-container permission prompts is the intended workflow — same rationale as the sibling aliases. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds copilot as a fourth first-class sandcat agent alongside claude, cursor, and codex, including templates, devcontainer wiring, mitmproxy integration, and test coverage to support initializing and running a Copilot CLI-based sandbox.
Changes:
- Introduces a Copilot agent path across the CLI dispatchers (agent listing/validation, devcontainer template selection, compose mounts, Docker install/home prep, and user-init health check).
- Adds a Copilot-specific settings template and mitmproxy addon, plus extends secret loading to support per-secret placeholder overrides.
- Expands bats + Python tests to cover the new agent and the placeholder override behavior.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Documents Copilot as a supported agent and adds setup/auth/integration guidance. |
| cli/README.md | Updates CLI docs to list copilot as a valid --agent option. |
| cli/lib/agents.bash | Adds copilot to agent dispatchers (mount env var, host paths, help text, VS Code extension, install/home prep, user init). |
| cli/lib/devcontainer.bash | Wires Copilot’s mitmproxy addon selection for template customization. |
| cli/lib/composefile.bash | Adds Copilot config volume mounts and hooks them into compose customization. |
| cli/templates/settings-user-copilot.json | New user settings template defining token secret, placeholder, and default network allowlist. |
| cli/templates/devcontainer/sandcat/scripts/mitmproxy_addon_copilot.py | New Copilot-focused mitmproxy addon wrapper using the shared common addon. |
| cli/templates/devcontainer/sandcat/scripts/mitmproxy_addon_common.py | Extends secret loading to honor an optional per-secret placeholder field. |
| cli/test/agents/agents.bats | Adds/updates tests covering Copilot agent dispatcher behavior and Docker/init blocks. |
| cli/test/composefile/composefile.bats | Adds tests for Copilot config mounts and compose wiring. |
| cli/test/init/init.bats | Adds tests ensuring init accepts copilot and creates COPILOT_GITHUB_TOKEN defaults. |
| cli/test/init/extensions.bats | Adds tests ensuring Copilot mitmproxy defaults are applied and Cursor-only streaming flags are not. |
| cli/test/mitmproxy/test_mitmproxy_addon.py | Adds unit tests for default vs custom placeholder behavior in secret loading. |
Suppressed comments (1)
README.md:579
- This claims the VS Code Copilot extension is authenticated via
COPILOT_GITHUB_TOKENand needs no separate sign-in step, but the devcontainer wiring here only installs the extension. The VS Code extension generally relies on VS Code/GitHub authentication, not an env var, so this statement is likely misleading.
**VS Code integration:** When the IDE is `vscode`, the bundled `devcontainer.json`
includes the `GitHub.copilot` extension. It uses the same GitHub account as the
Copilot CLI (authenticated via the `COPILOT_GITHUB_TOKEN` environment variable) —
no separate sign-in step is needed.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| 2. **GitHub CLI OAuth token (quick setup):** If you already have `gh` CLI logged in: | ||
| ```bash | ||
| # Edit ~/.config/sandcat/settings.json and set: | ||
| COPILOT_GITHUB_TOKEN=$(gh auth token) | ||
| ``` |
| # Per-secret placeholder override (rare). Needed when the consumer | ||
| # tool validates token format client-side and would reject the | ||
| # default `SANDCAT_PLACEHOLDER_<NAME>` shape before ever hitting | ||
| # mitmproxy — GitHub Copilot CLI checks the `gho_`/`github_pat_` | ||
| # prefix on `COPILOT_GITHUB_TOKEN` and errors out with "no auth | ||
| # information found" for anything else. A custom placeholder in | ||
| # settings.json (e.g. `gho_SANDCAT_PLACEHOLDER_COPILOT_GITHUB_TOKEN`) | ||
| # keeps the CLI happy on the env-var side; mitmproxy's on-the-wire | ||
| # `.replace(placeholder, value)` still swaps the ENTIRE string with | ||
| # the real token, so what actually leaves the sandbox is well-formed. | ||
| placeholder = entry.get("placeholder") or f"SANDCAT_PLACEHOLDER_{name}" |
Guard against non-string and marker-free placeholder values that could cause type errors or false-positive substitutions in request traffic. Warn via ctx.log.warn and fall back to the default when invalid. Update existing placeholder test to use a marker-containing value; add three new unit tests covering the rejection paths and the valid prefixed case. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Option 2 auth setup now shows a yq command that mutates settings.json rather than a bare shell assignment floating in a JSON comment context. VS Code extension auth note corrected: the extension uses VS Code's own GitHub sign-in, not the COPILOT_GITHUB_TOKEN env var. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot users need a separate GITHUB_TOKEN for regular git/gh operations alongside COPILOT_GITHUB_TOKEN (which is scoped to the Copilot API). Matching the codex template — two secrets, one per concern, least-privilege by default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Copilot review comments addressed in the three follow-up commits pushed to the branch:
Bonus commit |
Fixes #62.
Summary
Adds `copilot` alongside `claude`, `cursor`, and `codex` as a first-class sandcat agent. `sandcat init --agent copilot ...` produces a container running the `@github/copilot` CLI with the same network isolation, secret substitution, mitmproxy interception, no-new-privileges hardening, and IDE integration as the existing agents.
Follows the Codex integration (PR #87) pattern — every per-agent case statement in `cli/lib/agents.bash` gains a `copilot` branch; new user-settings template + mitmproxy addon file; VS Code extension `GitHub.copilot` added to devcontainer.json when `ide=vscode`; `copilot-yolo` shell alias mirroring `claude-yolo`/`codex-yolo`.
Auth model
Copilot CLI accepts these env vars in precedence order: `COPILOT_GITHUB_TOKEN` → `GH_TOKEN` → `GITHUB_TOKEN`. Sandcat's template defines `COPILOT_GITHUB_TOKEN` as a dedicated secret (least privilege — Copilot API scope only) and also keeps a separate `GITHUB_TOKEN` for regular git/gh operations. Users supply a fine-grained PAT with the "Copilot Requests" permission, or reuse `gh auth token` for a quick start.
Custom placeholder for Copilot's client-side format check: Copilot CLI validates that the token starts with `gho_` or `github_pat_` and rejects the request locally otherwise — sandcat's default `SANDCAT_PLACEHOLDER_` shape fails this check. Extended `mitmproxy_addon_common.py` `load_secrets` to honor a per-secret `placeholder` field in settings.json (fallback to the default when absent). The copilot template sets `"placeholder": "gho_SANDCAT_PLACEHOLDER_COPILOT_GITHUB_TOKEN"` — the CLI accepts the `gho` prefix, and mitmproxy's on-the-wire `.replace(placeholder, value)` still swaps the entire string for a well-formed real token. Backward-compatible: existing agents don't set the field and keep the default.
Install
Node.js 22 (via NodeSource setup script) + `npm install -g @github/copilot` under `USER root`, restored to `USER vscode` at end of block. Adds ~120MB to image (base image doesn't ship Node.js).
Config layout
Network allowlist
Copilot CLI hits `api.github.com`, `api.individual.githubcopilot.com` (or `api.business/enterprise.githubcopilot.com` per tier), and `telemetry.individual.githubcopilot.com`. Allowlist in the template: `.githubcopilot.com`, `api.github.com`, `.github.com`, `*.githubusercontent.com`.
Not included in this PR
Test plan
🤖 Generated with Claude Code