diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md
index 189a3ece..88a79802 100644
--- a/packages/coding-agent/CHANGELOG.md
+++ b/packages/coding-agent/CHANGELOG.md
@@ -211,6 +211,7 @@ Total color count increased from 46 to 50. See [docs/theme.md](docs/theme.md) fo
- **Session file validation**: `findMostRecentSession()` now validates session headers before returning, preventing non-session JSONL files from being loaded
- **Compaction error handling**: `generateSummary()` and `generateTurnPrefixSummary()` now throw on LLM errors instead of returning empty strings
- **Compaction with branched sessions**: Fixed compaction incorrectly including entries from abandoned branches, causing token overflow errors. Compaction now uses `sessionManager.getPath()` to work only on the current branch path, eliminating 80+ lines of duplicate entry collection logic between `prepareCompaction()` and `compact()`
+- **enabledModels glob patterns**: `--models` and `enabledModels` now support glob patterns like `github-copilot/*` or `*sonnet*`. Previously, patterns were only matched literally or via substring search. ([#337](https://github.com/badlogic/pi-mono/issues/337))
## [0.30.2] - 2025-12-26
diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md
index 58278f2f..2f734254 100644
--- a/packages/coding-agent/README.md
+++ b/packages/coding-agent/README.md
@@ -497,7 +497,7 @@ Global `~/.pi/agent/settings.json` stores persistent preferences:
"defaultProvider": "anthropic",
"defaultModel": "claude-sonnet-4-20250514",
"defaultThinkingLevel": "medium",
- "enabledModels": ["claude-sonnet", "gpt-4o", "gemini-2.5-pro:high"],
+ "enabledModels": ["anthropic/*", "*gpt*", "gemini-2.5-pro:high"],
"queueMode": "one-at-a-time",
"shellPath": "C:\\path\\to\\bash.exe",
"hideThinkingBlock": false,
@@ -529,7 +529,7 @@ Global `~/.pi/agent/settings.json` stores persistent preferences:
| `defaultProvider` | Default model provider | - |
| `defaultModel` | Default model ID | - |
| `defaultThinkingLevel` | Thinking level: `off`, `minimal`, `low`, `medium`, `high`, `xhigh` | - |
-| `enabledModels` | Model patterns for cycling (same as `--models` CLI flag) | - |
+| `enabledModels` | Model patterns for cycling. Supports glob patterns (`github-copilot/*`, `*sonnet*`) and fuzzy matching. Same as `--models` CLI flag | - |
| `queueMode` | Message queue mode: `all` or `one-at-a-time` | `one-at-a-time` |
| `shellPath` | Custom bash path (Windows) | auto-detected |
| `hideThinkingBlock` | Hide thinking blocks in output (Ctrl+T to toggle) | `false` |
@@ -788,7 +788,7 @@ pi [options] [@files...] [messages...]
| `--session-dir
` | Directory for session storage and lookup |
| `--continue`, `-c` | Continue most recent session |
| `--resume`, `-r` | Select session to resume |
-| `--models ` | Comma-separated patterns for Ctrl+P cycling (e.g., `sonnet:high,haiku:low`) |
+| `--models ` | Comma-separated patterns for Ctrl+P cycling. Supports glob patterns (e.g., `anthropic/*`, `*sonnet*:high`) and fuzzy matching (e.g., `sonnet,haiku:low`) |
| `--tools ` | Comma-separated tool list (default: `read,bash,edit,write`) |
| `--thinking ` | Thinking level: `off`, `minimal`, `low`, `medium`, `high` |
| `--hook ` | Load a hook file (can be used multiple times) |
@@ -840,6 +840,9 @@ pi --provider openai --model gpt-4o "Help me refactor"
# Model cycling with thinking levels
pi --models sonnet:high,haiku:low
+# Limit to specific provider with glob pattern
+pi --models "github-copilot/*"
+
# Read-only mode
pi --tools read,grep,find,ls -p "Review the architecture"
diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts
index dda8b805..7094ab54 100644
--- a/packages/coding-agent/src/cli/args.ts
+++ b/packages/coding-agent/src/cli/args.ts
@@ -158,7 +158,8 @@ ${chalk.bold("Options:")}
--session Use specific session file
--session-dir Directory for session storage and lookup
--no-session Don't save session (ephemeral)
- --models Comma-separated model patterns for quick cycling with Ctrl+P
+ --models Comma-separated model patterns for Ctrl+P cycling
+ Supports globs (anthropic/*, *sonnet*) and fuzzy matching
--tools Comma-separated list of tools to enable (default: read,bash,edit,write)
Available: read, bash, edit, write, grep, find, ls
--thinking Set thinking level: off, minimal, low, medium, high, xhigh
@@ -196,6 +197,9 @@ ${chalk.bold("Examples:")}
# Limit model cycling to specific models
${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o
+ # Limit to a specific provider with glob pattern
+ ${APP_NAME} --models "github-copilot/*"
+
# Cycle models with fixed thinking levels
${APP_NAME} --models sonnet:high,haiku:low
diff --git a/packages/coding-agent/src/core/model-resolver.ts b/packages/coding-agent/src/core/model-resolver.ts
index 981f11f2..d2124413 100644
--- a/packages/coding-agent/src/core/model-resolver.ts
+++ b/packages/coding-agent/src/core/model-resolver.ts
@@ -5,6 +5,7 @@
import type { ThinkingLevel } from "@mariozechner/pi-agent-core";
import { type Api, type KnownProvider, type Model, modelsAreEqual } from "@mariozechner/pi-ai";
import chalk from "chalk";
+import { minimatch } from "minimatch";
import { isValidThinkingLevel } from "../cli/args.js";
import type { ModelRegistry } from "./model-registry.js";
@@ -172,6 +173,41 @@ export async function resolveModelScope(patterns: string[], modelRegistry: Model
const scopedModels: ScopedModel[] = [];
for (const pattern of patterns) {
+ // Check if pattern contains glob characters
+ if (pattern.includes("*") || pattern.includes("?") || pattern.includes("[")) {
+ // Extract optional thinking level suffix (e.g., "provider/*:high")
+ const colonIdx = pattern.lastIndexOf(":");
+ let globPattern = pattern;
+ let thinkingLevel: ThinkingLevel = "off";
+
+ if (colonIdx !== -1) {
+ const suffix = pattern.substring(colonIdx + 1);
+ if (isValidThinkingLevel(suffix)) {
+ thinkingLevel = suffix;
+ globPattern = pattern.substring(0, colonIdx);
+ }
+ }
+
+ // Match against "provider/modelId" format OR just model ID
+ // This allows "*sonnet*" to match without requiring "anthropic/*sonnet*"
+ const matchingModels = availableModels.filter((m) => {
+ const fullId = `${m.provider}/${m.id}`;
+ return minimatch(fullId, globPattern, { nocase: true }) || minimatch(m.id, globPattern, { nocase: true });
+ });
+
+ if (matchingModels.length === 0) {
+ console.warn(chalk.yellow(`Warning: No models match pattern "${pattern}"`));
+ continue;
+ }
+
+ for (const model of matchingModels) {
+ if (!scopedModels.find((sm) => modelsAreEqual(sm.model, model))) {
+ scopedModels.push({ model, thinkingLevel });
+ }
+ }
+ continue;
+ }
+
const { model, thinkingLevel, warning } = parseModelPattern(pattern, availableModels);
if (warning) {