diff --git a/.gitignore b/.gitignore index 9dc547c9c..244d10d5b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ docker-compose.extra.yml dist *.bun-build pnpm-lock.yaml +package-lock.json bun.lock bun.lockb coverage diff --git a/CHANGELOG.md b/CHANGELOG.md index 5909c9899..734dfd856 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Docs: https://docs.molt.bot Status: beta. ### Changes +- Agents: add GitHub Copilot CLI (`copilot-cli`) as a built-in CLI backend for text-only fallback. - Rebrand: rename the npm package/CLI to `moltbot`, add a `moltbot` compatibility shim, and move extensions to the `@moltbot/*` scope. - Commands: group /help and /commands output with Telegram paging. (#2504) Thanks @hougangdev. - macOS: limit project-local `node_modules/.bin` PATH preference to debug builds (reduce PATH hijacking risk). diff --git a/docs/docs.json b/docs/docs.json index a463479aa..2870eac63 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1008,6 +1008,8 @@ "providers/models", "providers/openai", "providers/anthropic", + "providers/github-copilot", + "providers/github-copilot-cli", "bedrock", "providers/moonshot", "providers/minimax", diff --git a/docs/gateway/cli-backends.md b/docs/gateway/cli-backends.md index b80683a16..4db4556ce 100644 --- a/docs/gateway/cli-backends.md +++ b/docs/gateway/cli-backends.md @@ -32,6 +32,12 @@ Codex CLI also works out of the box: moltbot agent --message "hi" --model codex-cli/gpt-5.2-codex ``` +GitHub Copilot CLI works the same way (requires GitHub CLI auth): + +```bash +moltbot agent --message "hi" --model copilot-cli/gpt-4.1 +``` + If your gateway runs under launchd/systemd and PATH is minimal, add just the command path: @@ -200,6 +206,18 @@ Moltbot also ships a default for `codex-cli`: - `imageArg: "--image"` - `sessionMode: "existing"` +Moltbot also ships a default for `copilot-cli`: + +- `command: "copilot"` +- `args: ["-p"]` +- `resumeArgs: ["-p", "--resume", "{sessionId}"]` +- `output: "text"` +- `modelArg: "--model"` +- `sessionMode: "existing"` + +Note: Copilot CLI uses GitHub CLI (`gh`) for authentication and currently outputs +text only (JSON output is not yet supported). + Override only if needed (common: absolute `command` path). ## Limitations diff --git a/docs/gateway/configuration.md b/docs/gateway/configuration.md index 1d270974d..168fb549d 100644 --- a/docs/gateway/configuration.md +++ b/docs/gateway/configuration.md @@ -1668,6 +1668,9 @@ Example: "claude-cli": { command: "/opt/homebrew/bin/claude" }, + "copilot-cli": { + command: "/opt/homebrew/bin/copilot" + }, "my-cli": { command: "my-cli", args: ["--json"], diff --git a/docs/providers/github-copilot-cli.md b/docs/providers/github-copilot-cli.md new file mode 100644 index 000000000..34d975d85 --- /dev/null +++ b/docs/providers/github-copilot-cli.md @@ -0,0 +1,189 @@ +--- +summary: "Use GitHub Copilot CLI as a text-only fallback backend" +read_when: + - You want to use GitHub Copilot CLI as a model provider + - You need a fallback when API providers fail + - You already have GitHub Copilot CLI installed +--- +# GitHub Copilot CLI + +GitHub Copilot CLI (`copilot`) is GitHub's terminal-based AI assistant. Moltbot can use it as a +**CLI backend** for text-only fallback when API providers are unavailable. + +## Prerequisites + +- **GitHub account** (free tier works for `gpt-4.1`) +- **GitHub CLI** (`gh`) authenticated with your GitHub account + +Note: `gpt-4.1` is available on the free GitHub tier. Other models like `gpt-4o`, `claude-sonnet-4-5`, +and `claude-opus-4-5` require a paid GitHub Copilot subscription. + +## Installation + +Install the Copilot CLI via npm: + +```bash +npm install -g @github/copilot +``` + +Or via Homebrew: + +```bash +brew install github/copilot/copilot +``` + +Verify installation: + +```bash +copilot --help +``` + +## Quick start + +Use Copilot CLI directly with Moltbot: + +```bash +moltbot agent --message "hi" --model copilot-cli/gpt-4.1 +``` + +## Configuration + +### Minimal config (custom command path) + +If your gateway runs under launchd/systemd with a minimal PATH, specify the full path: + +```json5 +{ + agents: { + defaults: { + cliBackends: { + "copilot-cli": { + command: "/opt/homebrew/bin/copilot" + } + } + } + } +} +``` + +### Using as a fallback + +Add `copilot-cli` to your fallback list so it only runs when primary models fail: + +```json5 +{ + agents: { + defaults: { + model: { + primary: "github-copilot/gpt-4.1", + fallbacks: [ + "copilot-cli/gpt-4.1" + ] + }, + models: { + "github-copilot/gpt-4.1": { alias: "Copilot" }, + "copilot-cli/gpt-4.1": {} + } + } + } +} +``` + +## Supported models + +Copilot CLI supports these models (availability depends on your GitHub plan): + +**Free tier (GitHub Free):** +- `gpt-4.1` ← recommended default +- `gpt-4.1-mini` + +**Paid tier (GitHub Copilot subscription):** +- `gpt-4o` +- `gpt-4-turbo` +- `claude-sonnet-4-5` +- `claude-opus-4-5` + +Example model refs: + +``` +copilot-cli/gpt-4.1 +copilot-cli/gpt-4.1-mini +copilot-cli/gpt-4o +copilot-cli/claude-sonnet-4-5 +``` + +## Authentication + +Copilot CLI uses GitHub CLI authentication. Ensure you're logged in: + +```bash +gh auth status +``` + +If not logged in: + +```bash +gh auth login +``` + +## Limitations + +- **Text output only**: Copilot CLI does not support JSON output, so responses are plain text. +- **Tools disabled**: CLI backends never receive tool calls. The CLI may still run its own agent tooling internally. +- **No streaming**: CLI output is collected then returned. +- **Session resume**: Uses `--resume` flag with session ID when available. + +## Differences from the API provider + +| Aspect | `github-copilot` (API) | `copilot-cli` (CLI backend) | +|--------|------------------------|----------------------------| +| Output format | JSON (structured) | Text only | +| Tools | Moltbot tools work | Tools disabled | +| Streaming | Supported | Not supported | +| Auth | Device flow / token exchange | GitHub CLI (`gh auth`) | + +Choose the API provider (`github-copilot`) for full functionality. Use `copilot-cli` as a +fallback when you want "always works" text responses. + +## Troubleshooting + +### CLI not found + +Set the full command path in your config: + +```json5 +{ + agents: { + defaults: { + cliBackends: { + "copilot-cli": { + command: "/usr/local/bin/copilot" + } + } + } + } +} +``` + +### Authentication errors + +Re-authenticate with GitHub CLI: + +```bash +gh auth login +gh auth status +``` + +### Model not available + +Model availability depends on your GitHub Copilot plan. Try a different model: + +```bash +moltbot agent --message "hi" --model copilot-cli/gpt-4.1 +``` + +## See also + +- [GitHub Copilot (API provider)](/providers/github-copilot) +- [CLI backends](/gateway/cli-backends) +- [Configuration](/gateway/configuration) diff --git a/docs/providers/github-copilot.md b/docs/providers/github-copilot.md index c7b68d1bd..c5e461f60 100644 --- a/docs/providers/github-copilot.md +++ b/docs/providers/github-copilot.md @@ -50,21 +50,21 @@ moltbot models auth login-github-copilot --yes ## Set a default model ```bash -moltbot models set github-copilot/gpt-4o +moltbot models set github-copilot/gpt-4.1 ``` ### Config snippet ```json5 { - agents: { defaults: { model: { primary: "github-copilot/gpt-4o" } } } + agents: { defaults: { model: { primary: "github-copilot/gpt-4.1" } } } } ``` ## Notes - Requires an interactive TTY; run it directly in a terminal. -- Copilot model availability depends on your plan; if a model is rejected, try - another ID (for example `github-copilot/gpt-4.1`). +- `gpt-4.1` is available on the free GitHub tier. Other models like `gpt-4o` and + Claude models require a paid GitHub Copilot subscription. - The login stores a GitHub token in the auth profile store and exchanges it for a Copilot API token when Moltbot runs. diff --git a/docs/providers/index.md b/docs/providers/index.md index c18ad70fb..914cde692 100644 --- a/docs/providers/index.md +++ b/docs/providers/index.md @@ -35,6 +35,7 @@ See [Venice AI](/providers/venice). - [OpenAI (API + Codex)](/providers/openai) - [Anthropic (API + Claude Code CLI)](/providers/anthropic) +- [GitHub Copilot (API + CLI)](/providers/github-copilot) - [Qwen (OAuth)](/providers/qwen) - [OpenRouter](/providers/openrouter) - [Vercel AI Gateway](/providers/vercel-ai-gateway) diff --git a/docs/testing.md b/docs/testing.md index a990c4c23..d79fc8dd1 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -188,6 +188,7 @@ CLAWDBOT_LIVE_SETUP_TOKEN=1 CLAWDBOT_LIVE_SETUP_TOKEN_PROFILE=anthropic:setup-to - Overrides (optional): - `CLAWDBOT_LIVE_CLI_BACKEND_MODEL="claude-cli/claude-opus-4-5"` - `CLAWDBOT_LIVE_CLI_BACKEND_MODEL="codex-cli/gpt-5.2-codex"` + - `CLAWDBOT_LIVE_CLI_BACKEND_MODEL="copilot-cli/gpt-4.1"` - `CLAWDBOT_LIVE_CLI_BACKEND_COMMAND="/full/path/to/claude"` - `CLAWDBOT_LIVE_CLI_BACKEND_ARGS='["-p","--output-format","json","--permission-mode","bypassPermissions"]'` - `CLAWDBOT_LIVE_CLI_BACKEND_CLEAR_ENV='["ANTHROPIC_API_KEY","ANTHROPIC_API_KEY_OLD"]'` @@ -205,6 +206,17 @@ CLAWDBOT_LIVE_CLI_BACKEND=1 \ pnpm test:live src/gateway/gateway-cli-backend.live.test.ts ``` +### Copilot CLI + +```bash +CLAWDBOT_LIVE_CLI_BACKEND=1 \ + CLAWDBOT_LIVE_CLI_BACKEND_MODEL="copilot-cli/gpt-4.1" \ + pnpm test:live src/gateway/gateway-cli-backend.live.test.ts +``` + +Note: Copilot CLI requires GitHub CLI auth (`gh auth status`). Output is text-only (no JSON). +`gpt-4.1` is available on the GitHub Free tier; other models require a paid subscription. + ### Recommended live recipes Narrow, explicit allowlists are fastest and least flaky: diff --git a/src/agents/cli-backends.ts b/src/agents/cli-backends.ts index 55344dedc..f448cf04b 100644 --- a/src/agents/cli-backends.ts +++ b/src/agents/cli-backends.ts @@ -25,6 +25,15 @@ const CLAUDE_MODEL_ALIASES: Record = { "claude-haiku-3-5": "haiku", }; +const COPILOT_CLI_MODEL_ALIASES: Record = { + "gpt-4o": "gpt-4o", + "gpt-4.1": "gpt-4.1", + "gpt-4.1-mini": "gpt-4.1-mini", + "gpt-4-turbo": "gpt-4-turbo", + "claude-sonnet-4-5": "claude-sonnet-4-5", + "claude-opus-4-5": "claude-opus-4-5", +}; + const DEFAULT_CLAUDE_BACKEND: CliBackendConfig = { command: "claude", args: ["-p", "--output-format", "json", "--dangerously-skip-permissions"], @@ -74,6 +83,20 @@ const DEFAULT_CODEX_BACKEND: CliBackendConfig = { serialize: true, }; +const DEFAULT_COPILOT_CLI_BACKEND: CliBackendConfig = { + command: "copilot", + args: ["--allow-all-tools"], + resumeArgs: ["--allow-all-tools", "--resume", "{sessionId}"], + output: "text", + input: "arg", + promptArg: "-p", + modelArg: "--model", + modelAliases: COPILOT_CLI_MODEL_ALIASES, + sessionIdFields: ["session_id", "sessionId"], + sessionMode: "existing", + serialize: true, +}; + function normalizeBackendKey(key: string): string { return normalizeProviderId(key); } @@ -107,6 +130,7 @@ export function resolveCliBackendIds(cfg?: MoltbotConfig): Set { const ids = new Set([ normalizeBackendKey("claude-cli"), normalizeBackendKey("codex-cli"), + normalizeBackendKey("copilot-cli"), ]); const configured = cfg?.agents?.defaults?.cliBackends ?? {}; for (const key of Object.keys(configured)) { @@ -135,6 +159,12 @@ export function resolveCliBackendConfig( if (!command) return null; return { id: normalized, config: { ...merged, command } }; } + if (normalized === "copilot-cli") { + const merged = mergeBackendConfig(DEFAULT_COPILOT_CLI_BACKEND, override); + const command = merged.command?.trim(); + if (!command) return null; + return { id: normalized, config: { ...merged, command } }; + } if (!override) return null; const command = override.command?.trim(); diff --git a/src/agents/cli-runner/helpers.ts b/src/agents/cli-runner/helpers.ts index 833771a1f..23cd26968 100644 --- a/src/agents/cli-runner/helpers.ts +++ b/src/agents/cli-runner/helpers.ts @@ -445,7 +445,11 @@ export function buildCliArgs(params: { } } if (params.promptArg !== undefined) { - args.push(params.promptArg); + if (params.backend.promptArg) { + args.push(params.backend.promptArg, params.promptArg); + } else { + args.push(params.promptArg); + } } return args; } diff --git a/src/agents/model-selection.ts b/src/agents/model-selection.ts index 8d6db36de..d21c95db2 100644 --- a/src/agents/model-selection.ts +++ b/src/agents/model-selection.ts @@ -36,6 +36,7 @@ export function isCliProvider(provider: string, cfg?: MoltbotConfig): boolean { const normalized = normalizeProviderId(provider); if (normalized === "claude-cli") return true; if (normalized === "codex-cli") return true; + if (normalized === "copilot-cli") return true; const backends = cfg?.agents?.defaults?.cliBackends ?? {}; return Object.keys(backends).some((key) => normalizeProviderId(key) === normalized); } diff --git a/src/config/types.agent-defaults.ts b/src/config/types.agent-defaults.ts index 9c6ce0211..45544926c 100644 --- a/src/config/types.agent-defaults.ts +++ b/src/config/types.agent-defaults.ts @@ -87,6 +87,8 @@ export type CliBackendConfig = { imageArg?: string; /** How to pass multiple images. */ imageMode?: "repeat" | "list"; + /** Flag used to pass the prompt (e.g. -p, --prompt). */ + promptArg?: string; /** Serialize runs for this CLI. */ serialize?: boolean; }; diff --git a/src/gateway/gateway-cli-backend.live.test.ts b/src/gateway/gateway-cli-backend.live.test.ts index c39b34b91..baf64f4d1 100644 --- a/src/gateway/gateway-cli-backend.live.test.ts +++ b/src/gateway/gateway-cli-backend.live.test.ts @@ -29,6 +29,7 @@ const DEFAULT_CODEX_ARGS = [ "read-only", "--skip-git-repo-check", ]; +const DEFAULT_COPILOT_CLI_ARGS = ["-p"]; const DEFAULT_CLEAR_ENV = ["ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY_OLD"]; function randomImageProbeCode(len = 6): string { @@ -216,7 +217,9 @@ describeLive("gateway live (cli backend)", () => { ? { command: "claude", args: DEFAULT_CLAUDE_ARGS } : providerId === "codex-cli" ? { command: "codex", args: DEFAULT_CODEX_ARGS } - : null; + : providerId === "copilot-cli" + ? { command: "copilot", args: DEFAULT_COPILOT_CLI_ARGS } + : null; const cliCommand = process.env.CLAWDBOT_LIVE_CLI_BACKEND_COMMAND ?? providerDefaults?.command; if (!cliCommand) {