Merge branch 'main' into feat/copilot-converter-target
This commit is contained in:
@@ -0,0 +1,370 @@
|
||||
---
|
||||
title: Add Gemini CLI as a Target Provider
|
||||
type: feat
|
||||
status: completed
|
||||
completed_date: 2026-02-14
|
||||
completed_by: "Claude Opus 4.6"
|
||||
actual_effort: "Completed in one session"
|
||||
date: 2026-02-14
|
||||
---
|
||||
|
||||
# Add Gemini CLI as a Target Provider
|
||||
|
||||
## Overview
|
||||
|
||||
Add `gemini` as a sixth target provider in the converter CLI, alongside `opencode`, `codex`, `droid`, `cursor`, and `pi`. This enables `--to gemini` for both `convert` and `install` commands, converting Claude Code plugins into Gemini CLI-compatible format.
|
||||
|
||||
Gemini CLI ([google-gemini/gemini-cli](https://github.com/google-gemini/gemini-cli)) is Google's open-source AI agent for the terminal. It supports GEMINI.md context files, custom commands (TOML format), agent skills (SKILL.md standard), MCP servers, and extensions -- making it a strong conversion target with good coverage of Claude Code plugin concepts.
|
||||
|
||||
## Component Mapping
|
||||
|
||||
| Claude Code | Gemini Equivalent | Notes |
|
||||
|---|---|---|
|
||||
| `agents/*.md` | `.gemini/skills/*/SKILL.md` | Agents become skills -- Gemini activates them on demand via `activate_skill` tool based on description matching |
|
||||
| `commands/*.md` | `.gemini/commands/*.toml` | TOML format with `prompt` and `description` fields; namespaced via directory structure |
|
||||
| `skills/*/SKILL.md` | `.gemini/skills/*/SKILL.md` | **Identical standard** -- copy directly |
|
||||
| MCP servers | `settings.json` `mcpServers` | Same MCP protocol; different config location (`settings.json` vs `.mcp.json`) |
|
||||
| `hooks/` | `settings.json` hooks | Gemini has hooks (`BeforeTool`, `AfterTool`, `SessionStart`, etc.) but different format; emit `console.warn` and skip for now |
|
||||
| `.claude/` paths | `.gemini/` paths | Content rewriting needed |
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
**1. Agents become skills (not GEMINI.md context)**
|
||||
|
||||
With 29 agents, dumping them into GEMINI.md would flood every session's context. Instead, agents convert to skills -- Gemini autonomously activates them based on the skill description when relevant. This matches how Claude Code agents are invoked on demand via the Task tool.
|
||||
|
||||
**2. Commands use TOML format with directory-based namespacing**
|
||||
|
||||
Gemini CLI commands are `.toml` files where the path determines the command name: `.gemini/commands/git/commit.toml` becomes `/git:commit`. This maps cleanly from Claude Code's colon-namespaced commands (`workflows:plan` -> `.gemini/commands/workflows/plan.toml`).
|
||||
|
||||
**3. Commands use `{{args}}` placeholder**
|
||||
|
||||
Gemini's TOML commands support `{{args}}` for argument injection, mapping from Claude Code's `argument-hint` field. Commands with `argument-hint` get `{{args}}` appended to the prompt.
|
||||
|
||||
**4. MCP servers go into project-level settings.json**
|
||||
|
||||
Gemini CLI reads MCP config from `.gemini/settings.json` under the `mcpServers` key. The format is compatible -- same `command`, `args`, `env` fields, plus Gemini-specific `cwd`, `timeout`, `trust`, `includeTools`, `excludeTools`.
|
||||
|
||||
**5. Skills pass through unchanged**
|
||||
|
||||
Gemini adopted the same SKILL.md standard (YAML frontmatter with `name` and `description`, markdown body). Skills copy directly.
|
||||
|
||||
### TOML Command Format
|
||||
|
||||
```toml
|
||||
description = "Brief description of the command"
|
||||
prompt = """
|
||||
The prompt content that will be sent to Gemini.
|
||||
|
||||
User request: {{args}}
|
||||
"""
|
||||
```
|
||||
|
||||
- `description` (string): One-line description shown in `/help`
|
||||
- `prompt` (string): The prompt sent to the model; supports `{{args}}`, `!{shell}`, `@{file}` placeholders
|
||||
|
||||
### Skill (SKILL.md) Format
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: skill-name
|
||||
description: When and how Gemini should use this skill
|
||||
---
|
||||
|
||||
# Skill Title
|
||||
|
||||
Detailed instructions...
|
||||
```
|
||||
|
||||
Identical to Claude Code's format. The `description` field is critical -- Gemini uses it to decide when to activate the skill.
|
||||
|
||||
### MCP Server Format (settings.json)
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"server-name": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "package-name"],
|
||||
"env": { "KEY": "value" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] `bun run src/index.ts convert --to gemini ./plugins/compound-engineering` produces valid Gemini config
|
||||
- [x] Agents convert to `.gemini/skills/*/SKILL.md` with populated `description` in frontmatter
|
||||
- [x] Commands convert to `.gemini/commands/*.toml` with `prompt` and `description` fields
|
||||
- [x] Namespaced commands create directory structure (`workflows:plan` -> `commands/workflows/plan.toml`)
|
||||
- [x] Commands with `argument-hint` include `{{args}}` placeholder in prompt
|
||||
- [x] Commands with `disable-model-invocation: true` are still included (TOML commands are prompts, not code)
|
||||
- [x] Skills copied to `.gemini/skills/` (identical format)
|
||||
- [x] MCP servers written to `.gemini/settings.json` under `mcpServers` key
|
||||
- [x] Existing `.gemini/settings.json` is backed up before overwrite, and MCP config is merged (not clobbered)
|
||||
- [x] Content transformation rewrites `.claude/` and `~/.claude/` paths to `.gemini/` and `~/.gemini/`
|
||||
- [x] `/workflows:plan` transformed to `/workflows:plan` (Gemini preserves colon namespacing via directories)
|
||||
- [x] `Task agent-name(args)` transformed to `Use the agent-name skill to: args`
|
||||
- [x] Plugins with hooks emit `console.warn` about format differences
|
||||
- [x] Writer does not double-nest `.gemini/.gemini/`
|
||||
- [x] `model` and `allowedTools` fields silently dropped (no Gemini equivalent in skills/commands)
|
||||
- [x] Converter and writer tests pass
|
||||
- [x] Existing tests still pass (`bun test`)
|
||||
|
||||
## Implementation
|
||||
|
||||
### Phase 1: Types
|
||||
|
||||
**Create `src/types/gemini.ts`**
|
||||
|
||||
```typescript
|
||||
export type GeminiSkill = {
|
||||
name: string
|
||||
content: string // Full SKILL.md with YAML frontmatter
|
||||
}
|
||||
|
||||
export type GeminiSkillDir = {
|
||||
name: string
|
||||
sourceDir: string
|
||||
}
|
||||
|
||||
export type GeminiCommand = {
|
||||
name: string // e.g. "plan" or "workflows/plan"
|
||||
content: string // Full TOML content
|
||||
}
|
||||
|
||||
export type GeminiBundle = {
|
||||
generatedSkills: GeminiSkill[] // From agents
|
||||
skillDirs: GeminiSkillDir[] // From skills (pass-through)
|
||||
commands: GeminiCommand[]
|
||||
mcpServers?: Record<string, {
|
||||
command?: string
|
||||
args?: string[]
|
||||
env?: Record<string, string>
|
||||
url?: string
|
||||
headers?: Record<string, string>
|
||||
}>
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 2: Converter
|
||||
|
||||
**Create `src/converters/claude-to-gemini.ts`**
|
||||
|
||||
Core functions:
|
||||
|
||||
1. **`convertClaudeToGemini(plugin, options)`** -- main entry point
|
||||
- Convert each agent to a skill via `convertAgentToSkill()`
|
||||
- Convert each command via `convertCommand()`
|
||||
- Pass skills through as directory references
|
||||
- Convert MCP servers to settings-compatible object
|
||||
- Emit `console.warn` if `plugin.hooks` has entries
|
||||
|
||||
2. **`convertAgentToSkill(agent)`** -- agent -> SKILL.md
|
||||
- Frontmatter: `name` (from agent name), `description` (from agent description, max ~300 chars)
|
||||
- Body: agent body with content transformations applied
|
||||
- Prepend capabilities section if present
|
||||
- Silently drop `model` field (no Gemini equivalent)
|
||||
- If description is empty, generate from agent name: `"Use this skill for ${agent.name} tasks"`
|
||||
|
||||
3. **`convertCommand(command, usedNames)`** -- command -> TOML file
|
||||
- Preserve namespace structure: `workflows:plan` -> path `workflows/plan`
|
||||
- `description` field from command description
|
||||
- `prompt` field from command body with content transformations
|
||||
- If command has `argument-hint`, append `\n\nUser request: {{args}}` to prompt
|
||||
- Body: apply `transformContentForGemini()` transformations
|
||||
- Silently drop `allowedTools` (no Gemini equivalent)
|
||||
|
||||
4. **`transformContentForGemini(body)`** -- content rewriting
|
||||
- `.claude/` -> `.gemini/` and `~/.claude/` -> `~/.gemini/`
|
||||
- `Task agent-name(args)` -> `Use the agent-name skill to: args`
|
||||
- `@agent-name` references -> `the agent-name skill`
|
||||
- Skip file paths (containing `/`) and common non-command patterns
|
||||
|
||||
5. **`convertMcpServers(servers)`** -- MCP config
|
||||
- Map each `ClaudeMcpServer` entry to Gemini-compatible JSON
|
||||
- Pass through: `command`, `args`, `env`, `url`, `headers`
|
||||
- Drop `type` field (Gemini infers transport)
|
||||
|
||||
6. **`toToml(description, prompt)`** -- TOML serializer
|
||||
- Escape TOML strings properly
|
||||
- Use multi-line strings (`"""`) for prompt field
|
||||
- Simple string for description
|
||||
|
||||
### Phase 3: Writer
|
||||
|
||||
**Create `src/targets/gemini.ts`**
|
||||
|
||||
Output structure:
|
||||
|
||||
```
|
||||
.gemini/
|
||||
├── commands/
|
||||
│ ├── plan.toml
|
||||
│ └── workflows/
|
||||
│ └── plan.toml
|
||||
├── skills/
|
||||
│ ├── agent-name-1/
|
||||
│ │ └── SKILL.md
|
||||
│ ├── agent-name-2/
|
||||
│ │ └── SKILL.md
|
||||
│ └── original-skill/
|
||||
│ └── SKILL.md
|
||||
└── settings.json (only mcpServers key)
|
||||
```
|
||||
|
||||
Core function: `writeGeminiBundle(outputRoot, bundle)`
|
||||
|
||||
- `resolveGeminiPaths(outputRoot)` -- detect if path already ends in `.gemini` to avoid double-nesting (follow droid writer pattern)
|
||||
- Write generated skills to `skills/<name>/SKILL.md`
|
||||
- Copy original skill directories to `skills/` via `copyDir()`
|
||||
- Write commands to `commands/` as `.toml` files, creating subdirectories for namespaced commands
|
||||
- Write `settings.json` with `{ "mcpServers": {...} }` via `writeJson()` with `backupFile()` for existing files
|
||||
- If settings.json exists, read it first and merge `mcpServers` key (don't clobber other settings)
|
||||
|
||||
### Phase 4: Wire into CLI
|
||||
|
||||
**Modify `src/targets/index.ts`**
|
||||
|
||||
```typescript
|
||||
import { convertClaudeToGemini } from "../converters/claude-to-gemini"
|
||||
import { writeGeminiBundle } from "./gemini"
|
||||
import type { GeminiBundle } from "../types/gemini"
|
||||
|
||||
// Add to targets:
|
||||
gemini: {
|
||||
name: "gemini",
|
||||
implemented: true,
|
||||
convert: convertClaudeToGemini as TargetHandler<GeminiBundle>["convert"],
|
||||
write: writeGeminiBundle as TargetHandler<GeminiBundle>["write"],
|
||||
},
|
||||
```
|
||||
|
||||
**Modify `src/commands/convert.ts`**
|
||||
|
||||
- Update `--to` description: `"Target format (opencode | codex | droid | cursor | pi | gemini)"`
|
||||
- Add to `resolveTargetOutputRoot`: `if (targetName === "gemini") return path.join(outputRoot, ".gemini")`
|
||||
|
||||
**Modify `src/commands/install.ts`**
|
||||
|
||||
- Same two changes as convert.ts
|
||||
|
||||
### Phase 5: Tests
|
||||
|
||||
**Create `tests/gemini-converter.test.ts`**
|
||||
|
||||
Test cases (use inline `ClaudePlugin` fixtures, following existing converter test patterns):
|
||||
|
||||
- Agent converts to skill with SKILL.md frontmatter (`name` and `description` populated)
|
||||
- Agent with empty description gets default description text
|
||||
- Agent with capabilities prepended to body
|
||||
- Agent `model` field silently dropped
|
||||
- Agent with empty body gets default body text
|
||||
- Command converts to TOML with `prompt` and `description` fields
|
||||
- Namespaced command creates correct path (`workflows:plan` -> `workflows/plan`)
|
||||
- Command with `disable-model-invocation` is still included
|
||||
- Command `allowedTools` silently dropped
|
||||
- Command with `argument-hint` gets `{{args}}` placeholder in prompt
|
||||
- Skills pass through as directory references
|
||||
- MCP servers convert to settings.json-compatible config
|
||||
- Content transformation: `.claude/` paths -> `.gemini/`
|
||||
- Content transformation: `~/.claude/` paths -> `~/.gemini/`
|
||||
- Content transformation: `Task agent(args)` -> natural language skill reference
|
||||
- Hooks present -> `console.warn` emitted
|
||||
- Plugin with zero agents produces empty generatedSkills array
|
||||
- Plugin with only skills works correctly
|
||||
- TOML output is valid (description and prompt properly escaped)
|
||||
|
||||
**Create `tests/gemini-writer.test.ts`**
|
||||
|
||||
Test cases (use temp directories, following existing writer test patterns):
|
||||
|
||||
- Full bundle writes skills, commands, settings.json
|
||||
- Generated skills written as `skills/<name>/SKILL.md`
|
||||
- Original skills copied to `skills/` directory
|
||||
- Commands written as `.toml` files in `commands/` directory
|
||||
- Namespaced commands create subdirectories (`commands/workflows/plan.toml`)
|
||||
- MCP config written as valid JSON `settings.json` with `mcpServers` key
|
||||
- Existing `settings.json` is backed up before overwrite
|
||||
- Output root already ending in `.gemini` does NOT double-nest
|
||||
- Empty bundle produces no output
|
||||
|
||||
### Phase 6: Documentation
|
||||
|
||||
**Create `docs/specs/gemini.md`**
|
||||
|
||||
Document the Gemini CLI spec as reference, following existing `docs/specs/codex.md` pattern:
|
||||
|
||||
- GEMINI.md context file format
|
||||
- Custom commands format (TOML with `prompt`, `description`)
|
||||
- Skills format (identical SKILL.md standard)
|
||||
- MCP server configuration (`settings.json`)
|
||||
- Extensions system (for reference, not converted)
|
||||
- Hooks system (for reference, format differences noted)
|
||||
- Config file locations (user-level `~/.gemini/` vs project-level `.gemini/`)
|
||||
- Directory layout conventions
|
||||
|
||||
**Update `README.md`**
|
||||
|
||||
Add `gemini` to the supported targets in the CLI usage section.
|
||||
|
||||
## What We're NOT Doing
|
||||
|
||||
- Not converting hooks (Gemini has hooks but different format -- `BeforeTool`/`AfterTool` with matchers -- warn and skip)
|
||||
- Not generating full `settings.json` (only `mcpServers` key -- user-specific settings like `model`, `tools.sandbox` are out of scope)
|
||||
- Not creating extensions (extension format is for distributing packages, not for converted plugins)
|
||||
- Not using `@{file}` or `!{shell}` placeholders in converted commands (would require analyzing command intent)
|
||||
- Not transforming content inside copied SKILL.md files (known limitation -- skills may reference `.claude/` paths internally)
|
||||
- Not clearing old output before writing (matches existing target behavior)
|
||||
- Not merging into existing settings.json intelligently beyond `mcpServers` key (too risky to modify user config)
|
||||
|
||||
## Complexity Assessment
|
||||
|
||||
This is a **medium change**. The converter architecture is well-established with five existing targets, so this is mostly pattern-following. The key novelties are:
|
||||
|
||||
1. The TOML command format (unique among all targets -- need simple TOML serializer)
|
||||
2. Agents map to skills rather than a direct 1:1 concept (but this is the same pattern as codex)
|
||||
3. Namespaced commands use directory structure (new approach vs flattening in cursor/codex)
|
||||
4. MCP config goes into a broader `settings.json` file (need to merge, not clobber)
|
||||
|
||||
Skills being identical across platforms simplifies things significantly. The TOML serialization is simple (only two fields: `description` string and `prompt` multi-line string).
|
||||
|
||||
## References
|
||||
|
||||
- [Gemini CLI Repository](https://github.com/google-gemini/gemini-cli)
|
||||
- [Gemini CLI Configuration](https://geminicli.com/docs/get-started/configuration/)
|
||||
- [Custom Commands (TOML)](https://geminicli.com/docs/cli/custom-commands/)
|
||||
- [Agent Skills](https://geminicli.com/docs/cli/skills/)
|
||||
- [Creating Skills](https://geminicli.com/docs/cli/creating-skills/)
|
||||
- [Extensions](https://geminicli.com/docs/extensions/writing-extensions/)
|
||||
- [MCP Servers](https://google-gemini.github.io/gemini-cli/docs/tools/mcp-server.html)
|
||||
- Existing cursor plan: `docs/plans/2026-02-12-feat-add-cursor-cli-target-provider-plan.md`
|
||||
- Existing codex converter: `src/converters/claude-to-codex.ts` (has `uniqueName()` and skill generation patterns)
|
||||
- Existing droid writer: `src/targets/droid.ts` (has double-nesting guard pattern)
|
||||
- Target registry: `src/targets/index.ts`
|
||||
|
||||
## Completion Summary
|
||||
|
||||
### What Was Delivered
|
||||
- [x] Phase 1: Types (`src/types/gemini.ts`)
|
||||
- [x] Phase 2: Converter (`src/converters/claude-to-gemini.ts`)
|
||||
- [x] Phase 3: Writer (`src/targets/gemini.ts`)
|
||||
- [x] Phase 4: CLI wiring (`src/targets/index.ts`, `src/commands/convert.ts`, `src/commands/install.ts`)
|
||||
- [x] Phase 5: Tests (`tests/gemini-converter.test.ts`, `tests/gemini-writer.test.ts`)
|
||||
- [x] Phase 6: Documentation (`docs/specs/gemini.md`, `README.md`)
|
||||
|
||||
### Implementation Statistics
|
||||
- 10 files changed
|
||||
- 27 new tests added (129 total, all passing)
|
||||
- 148 output files generated from compound-engineering plugin conversion
|
||||
- 0 dependencies added
|
||||
|
||||
### Git Commits
|
||||
- `201ad6d` feat(gemini): add Gemini CLI as sixth target provider
|
||||
- `8351851` docs: add Gemini CLI spec and update README with gemini target
|
||||
|
||||
### Completion Details
|
||||
- **Completed By:** Claude Opus 4.6
|
||||
- **Date:** 2026-02-14
|
||||
- **Session:** Single session
|
||||
122
docs/specs/gemini.md
Normal file
122
docs/specs/gemini.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Gemini CLI Spec (GEMINI.md, Commands, Skills, MCP, Settings)
|
||||
|
||||
Last verified: 2026-02-14
|
||||
|
||||
## Primary sources
|
||||
|
||||
```
|
||||
https://github.com/google-gemini/gemini-cli
|
||||
https://geminicli.com/docs/get-started/configuration/
|
||||
https://geminicli.com/docs/cli/custom-commands/
|
||||
https://geminicli.com/docs/cli/skills/
|
||||
https://geminicli.com/docs/cli/creating-skills/
|
||||
https://geminicli.com/docs/extensions/writing-extensions/
|
||||
https://google-gemini.github.io/gemini-cli/docs/tools/mcp-server.html
|
||||
```
|
||||
|
||||
## Config locations
|
||||
|
||||
- User-level config: `~/.gemini/settings.json`
|
||||
- Project-level config: `.gemini/settings.json`
|
||||
- Project-level takes precedence over user-level for most settings.
|
||||
- GEMINI.md context file lives at project root (similar to CLAUDE.md).
|
||||
|
||||
## GEMINI.md context file
|
||||
|
||||
- A markdown file at project root loaded into every session's context.
|
||||
- Used for project-wide instructions, coding standards, and conventions.
|
||||
- Equivalent to Claude Code's CLAUDE.md.
|
||||
|
||||
## Custom commands (TOML format)
|
||||
|
||||
- Custom commands are TOML files stored in `.gemini/commands/`.
|
||||
- Command name is derived from the file path: `.gemini/commands/git/commit.toml` becomes `/git:commit`.
|
||||
- Directory-based namespacing: subdirectories create namespaced commands.
|
||||
- Each command file has two fields:
|
||||
- `description` (string): One-line description shown in `/help`
|
||||
- `prompt` (string): The prompt sent to the model
|
||||
- Supports placeholders:
|
||||
- `{{args}}` — user-provided arguments
|
||||
- `!{shell}` — output of a shell command
|
||||
- `@{file}` — contents of a file
|
||||
- Example:
|
||||
|
||||
```toml
|
||||
description = "Create a git commit with a good message"
|
||||
prompt = """
|
||||
Look at the current git diff and create a commit with a descriptive message.
|
||||
|
||||
User request: {{args}}
|
||||
"""
|
||||
```
|
||||
|
||||
## Skills (SKILL.md standard)
|
||||
|
||||
- A skill is a folder containing `SKILL.md` plus optional supporting files.
|
||||
- Skills live in `.gemini/skills/`.
|
||||
- `SKILL.md` uses YAML frontmatter with `name` and `description` fields.
|
||||
- Gemini activates skills on demand via `activate_skill` tool based on description matching.
|
||||
- The `description` field is critical — Gemini uses it to decide when to activate the skill.
|
||||
- Format is identical to Claude Code's SKILL.md standard.
|
||||
- Example:
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: security-reviewer
|
||||
description: Review code for security vulnerabilities and OWASP compliance
|
||||
---
|
||||
|
||||
# Security Reviewer
|
||||
|
||||
Detailed instructions for security review...
|
||||
```
|
||||
|
||||
## MCP server configuration
|
||||
|
||||
- MCP servers are configured in `settings.json` under the `mcpServers` key.
|
||||
- Same MCP protocol as Claude Code; different config location.
|
||||
- Supports `command`, `args`, `env` for stdio transport.
|
||||
- Supports `url`, `headers` for HTTP/SSE transport.
|
||||
- Additional Gemini-specific fields: `cwd`, `timeout`, `trust`, `includeTools`, `excludeTools`.
|
||||
- Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"context7": {
|
||||
"url": "https://mcp.context7.com/mcp"
|
||||
},
|
||||
"playwright": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@anthropic/mcp-playwright"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Hooks
|
||||
|
||||
- Gemini supports hooks: `BeforeTool`, `AfterTool`, `SessionStart`, etc.
|
||||
- Hooks use a different format from Claude Code hooks (matchers-based).
|
||||
- Not converted by the plugin converter — a warning is emitted.
|
||||
|
||||
## Extensions
|
||||
|
||||
- Extensions are distributable packages for Gemini CLI.
|
||||
- They extend functionality with custom tools, hooks, and commands.
|
||||
- Not used for plugin conversion (different purpose from Claude Code plugins).
|
||||
|
||||
## Settings.json structure
|
||||
|
||||
```json
|
||||
{
|
||||
"model": "gemini-2.5-pro",
|
||||
"mcpServers": { ... },
|
||||
"tools": {
|
||||
"sandbox": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Only the `mcpServers` key is written during plugin conversion.
|
||||
- Other settings (model, tools, sandbox) are user-specific and out of scope.
|
||||
Reference in New Issue
Block a user