{
  "schemaVersion": 1,
  "cliVersion": "1.57.1",
  "languages": [
    {
      "name": "dotnet",
      "displayName": ".NET",
      "description": "The .NET SDK via the upstream devcontainer feature. Pin a version with `dotnet:<version>`.",
      "options": [],
      "defaultVersion": "latest",
      "versions": [
        "latest",
        "lts",
        "10",
        "9",
        "8",
        "7",
        "6"
      ]
    },
    {
      "name": "go",
      "displayName": "Go",
      "description": "The Go toolchain plus golangci-lint via the upstream devcontainer feature. Pin a version with `go:<version>`.",
      "options": [],
      "defaultVersion": "latest",
      "versions": [
        "latest",
        "1.24",
        "1.23"
      ]
    },
    {
      "name": "java",
      "displayName": "Java",
      "description": "A JDK plus Maven and Gradle by default, where the upstream feature installs only the JDK, so this is build-ready as it stands. The JDK major is the `version:` field in the yml (`latest`, 21, 17, 11, 8); on the command line it is `java:21`.",
      "options": [
        {
          "key": "installGradle",
          "type": "boolean",
          "surface": "yml",
          "default": true
        },
        {
          "key": "installMaven",
          "type": "boolean",
          "surface": "yml",
          "default": true
        }
      ],
      "defaultVersion": "latest",
      "versions": [
        "latest",
        "21",
        "17",
        "11",
        "8"
      ]
    },
    {
      "name": "node",
      "displayName": "Node.js",
      "description": "Built into the runtime image. A bare `node` installs nothing extra; pin a different major with `node:<version>`.",
      "options": [],
      "defaultVersion": "22",
      "versions": [
        "lts",
        "latest",
        "22",
        "20"
      ]
    },
    {
      "name": "python",
      "displayName": "Python",
      "description": "CPython plus pip and common dev tooling via the upstream devcontainer feature. Pin a version with `python:<version>`.",
      "options": [],
      "defaultVersion": "latest",
      "versions": [
        "latest",
        "3.12",
        "3.11",
        "3.1",
        "3.9",
        "3.8"
      ]
    },
    {
      "name": "rust",
      "displayName": "Rust",
      "description": "rustup plus the stable toolchain (rust-analyzer, rust-src, rustfmt, clippy) via the upstream feature. Pin a version with `rust:<version>`.",
      "options": [],
      "defaultVersion": "latest",
      "versions": [
        "latest",
        "1.87"
      ]
    }
  ],
  "services": [
    {
      "name": "caddy",
      "displayName": "Caddy",
      "description": "Reverse proxy for local dev, so your app and the services behind it answer under one address. Reachable in-container as host `caddy`. Its configuration is a Caddyfile you keep in the repo and mount through the commented volumes below.",
      "options": [],
      "documentationURL": "https://caddyserver.com/docs/caddyfile",
      "defaultPort": 81
    },
    {
      "name": "keycloak",
      "displayName": "Keycloak",
      "description": "Identity and access management (OAuth2 / OpenID Connect / SAML) for local dev. Reachable in-container as host `keycloak`. Starts in dev mode and imports realm JSON from a repo you mount, through the commented volumes below.",
      "options": [
        {
          "key": "KC_BOOTSTRAP_ADMIN_PASSWORD",
          "type": "string",
          "surface": "env",
          "default": "admin"
        },
        {
          "key": "KC_BOOTSTRAP_ADMIN_USERNAME",
          "type": "string",
          "surface": "env",
          "default": "admin"
        }
      ],
      "documentationURL": "https://www.keycloak.org/",
      "defaultPort": 8080
    },
    {
      "name": "mailpit",
      "displayName": "Mailpit",
      "description": "SMTP catcher for local dev, so apps send mail to it and nothing leaves the machine. SMTP on `mailpit:1025`; web UI + API on 8025, reachable from the host via `monoceros tunnel <name> mailpit:8025` and from another device via `monoceros share`.",
      "options": [],
      "defaultPort": 1025
    },
    {
      "name": "mongodb",
      "displayName": "MongoDB",
      "description": "Document database with seeded dev credentials and a readiness healthcheck. Reachable in-container as host `mongodb`.",
      "options": [
        {
          "key": "MONGO_INITDB_DATABASE",
          "type": "string",
          "surface": "env",
          "default": "monoceros"
        },
        {
          "key": "MONGO_INITDB_ROOT_PASSWORD",
          "type": "string",
          "surface": "env",
          "default": "monoceros"
        },
        {
          "key": "MONGO_INITDB_ROOT_USERNAME",
          "type": "string",
          "surface": "env",
          "default": "monoceros"
        }
      ],
      "defaultPort": 27017
    },
    {
      "name": "mysql",
      "displayName": "MySQL",
      "description": "Relational database with seeded dev credentials and a readiness healthcheck. Reachable in-container as host `mysql`.",
      "options": [
        {
          "key": "MYSQL_DATABASE",
          "type": "string",
          "surface": "env",
          "default": "monoceros"
        },
        {
          "key": "MYSQL_ROOT_PASSWORD",
          "type": "string",
          "surface": "env",
          "default": "monoceros"
        }
      ],
      "defaultPort": 3306
    },
    {
      "name": "pgvector",
      "displayName": "pgvector (PostgreSQL + vectors)",
      "description": "Relational database with the pgvector extension for embeddings and vector search, a drop-in Postgres. Reachable in-container as host `pgvector`. Enable the extension once with `CREATE EXTENSION vector;` (or in your migration).",
      "options": [
        {
          "key": "POSTGRES_DB",
          "type": "string",
          "surface": "env",
          "default": "monoceros"
        },
        {
          "key": "POSTGRES_PASSWORD",
          "type": "string",
          "surface": "env",
          "default": "monoceros"
        },
        {
          "key": "POSTGRES_USER",
          "type": "string",
          "surface": "env",
          "default": "monoceros"
        }
      ],
      "defaultPort": 5432
    },
    {
      "name": "postgres",
      "displayName": "PostgreSQL",
      "description": "Relational database with seeded dev credentials and a readiness healthcheck. Reachable in-container as host `postgres`.",
      "options": [
        {
          "key": "POSTGRES_DB",
          "type": "string",
          "surface": "env",
          "default": "monoceros"
        },
        {
          "key": "POSTGRES_PASSWORD",
          "type": "string",
          "surface": "env",
          "default": "monoceros"
        },
        {
          "key": "POSTGRES_USER",
          "type": "string",
          "surface": "env",
          "default": "monoceros"
        }
      ],
      "defaultPort": 5432
    },
    {
      "name": "redis",
      "displayName": "Redis",
      "description": "In-memory key-value store on the default port with a readiness healthcheck. Reachable in-container as host `redis`.",
      "options": [],
      "defaultPort": 6379
    },
    {
      "name": "rustfs",
      "displayName": "RustFS",
      "description": "S3-compatible object storage (MinIO-compatible API). S3 API on `rustfs:9000`; web console on 9001 (enable with `RUSTFS_CONSOLE_ENABLE=true`), reachable from the host via `monoceros tunnel <name> rustfs:9001` and from another device via `monoceros share`.",
      "options": [
        {
          "key": "RUSTFS_ACCESS_KEY",
          "type": "string",
          "surface": "env",
          "default": "rustfsadmin"
        },
        {
          "key": "RUSTFS_SECRET_KEY",
          "type": "string",
          "surface": "env",
          "default": "rustfsadmin"
        }
      ],
      "defaultPort": 9000
    }
  ],
  "features": [
    {
      "name": "atlassian",
      "displayName": "Atlassian",
      "description": "Rovo Dev (`acli rovodev`), Teamwork Graph (`twg`) and Forge (`forge`) CLIs sharing one Atlassian account. Auth persists across rebuilds.",
      "options": [
        {
          "key": "apiToken",
          "type": "string",
          "surface": "env",
          "default": "",
          "description": "Atlassian API token for twg and Forge (id.atlassian.com, Security, API tokens). Rovo Dev does not accept this one, see `rovodevToken`."
        },
        {
          "key": "bitbucketToken",
          "type": "string",
          "surface": "env",
          "default": "",
          "description": "Bitbucket access for twg and repo clone: an Atlassian API token with `read/write:repository:bitbucket` scopes (app passwords retire 2026-06-09). Leave empty to reuse `apiToken` when that token already carries the Bitbucket scopes."
        },
        {
          "key": "email",
          "type": "string",
          "surface": "env",
          "default": "",
          "description": "Atlassian account email; used with `apiToken` for non-interactive login."
        },
        {
          "key": "forge",
          "type": "boolean",
          "surface": "yml",
          "default": true,
          "description": "Install the Forge CLI (`forge`) for building Atlassian apps."
        },
        {
          "key": "instance",
          "type": "string",
          "surface": "env",
          "default": "",
          "description": "Atlassian site host (`yoursite.atlassian.net`); required by twg."
        },
        {
          "key": "rovodev",
          "type": "boolean",
          "surface": "yml",
          "default": true,
          "description": "Install acli (with the Rovo Dev agent)."
        },
        {
          "key": "rovodevToken",
          "type": "string",
          "surface": "env",
          "default": "",
          "description": "Rovo Dev scoped API token, which is a different token from `apiToken` (id.atlassian.com, Security, create an API token with scopes). acli rejects a classic token with a bare \"authentication failed\", so the two cannot share one variable."
        },
        {
          "key": "twg",
          "type": "boolean",
          "surface": "yml",
          "default": true,
          "description": "Install twg (Teamwork Graph CLI)."
        }
      ],
      "documentationURL": "https://developer.atlassian.com/cloud/",
      "presets": [
        "forge",
        "rovodev",
        "twg"
      ]
    },
    {
      "name": "claude",
      "displayName": "Claude Code",
      "description": "Anthropic's CLI coding assistant. OAuth/subscription login persists across container rebuilds.",
      "options": [
        {
          "key": "apiKey",
          "type": "string",
          "surface": "env",
          "default": "",
          "description": "`sk-ant-…` for API auth; empty for OAuth login on first run."
        },
        {
          "key": "permissionMode",
          "type": "string",
          "surface": "yml",
          "default": "auto",
          "description": "Default Claude permission mode in this container. `auto` = Auto Mode: no per-action prompts and no recurring warning (a background classifier vets actions) — the comfortable default for an isolated container. `ask` prompts as usual; `edits` auto-accepts file edits but prompts for other commands; `bypass` skips all prompts (its one-time warning is pre-accepted). Monoceros writes this to ~/.claude/settings.json on apply; override per-project or with a CLI flag.",
          "proposals": [
            "auto",
            "ask",
            "edits",
            "bypass"
          ]
        }
      ],
      "documentationURL": "https://docs.anthropic.com/en/docs/claude-code",
      "presets": []
    },
    {
      "name": "claude-code-roles",
      "displayName": "Claude Code roles (plan / implement / review)",
      "description": "Three Claude Code roles that split a task: a planner writes a plan file, an implementer executes it, a reviewer checks the result against it. Adds `/monoceros-plan`, `/monoceros-ship` and `/monoceros-review`. Requires the `claude-code` feature.",
      "options": [
        {
          "key": "implementEffort",
          "type": "string",
          "surface": "yml",
          "default": "",
          "description": "Effort level for the implementing role. It follows a plan that already names the files and the acceptance command, so it needs less deliberation than the other two: a good place to save. Empty: same fallback as `plannerEffort`."
        },
        {
          "key": "implementModel",
          "type": "string",
          "surface": "yml",
          "default": "",
          "description": "Model for the implementing role. This is the role that runs most often, so a cheaper model belongs here - but it must call tools reliably, or it burns more in retries than it saves. Empty: same fallback as `plannerModel`."
        },
        {
          "key": "plannerEffort",
          "type": "string",
          "surface": "yml",
          "default": "",
          "description": "Effort level for the planning role: `low`, `medium`, `high`, `xhigh` or `max`. Higher means the role thinks longer before it acts, and costs more. Planning is where thinking pays, so this is the last one to turn down. Empty: the role inherits whatever effort the session runs on."
        },
        {
          "key": "plannerModel",
          "type": "string",
          "surface": "yml",
          "default": "",
          "description": "Model for the planning role: `opus`, `sonnet`, `haiku`, `fable`, or a full model id. Planning carries the reasoning: a strong model here pays for itself, because the plan decides how small the implementing model can be. Empty: the role runs on whatever the session runs on."
        },
        {
          "key": "reviewEffort",
          "type": "string",
          "surface": "yml",
          "default": "",
          "description": "Effort level for the reviewing role. It has to notice a step that was skipped and a security hole nobody asked about, which is thinking work. Empty: same fallback as `plannerEffort`."
        },
        {
          "key": "reviewModel",
          "type": "string",
          "surface": "yml",
          "default": "",
          "description": "Model for the reviewing role. Reviewing needs the same class of model as planning: it has to notice a step that was skipped, not just read a diff. Empty: same fallback as `plannerModel`."
        }
      ],
      "documentationURL": "https://getmonoceros.build/docs/features/claude-code-roles/",
      "presets": []
    },
    {
      "name": "github",
      "displayName": "GitHub CLI",
      "description": "The official `gh` CLI. Login persists across container rebuilds.",
      "options": [
        {
          "key": "apiToken",
          "type": "string",
          "surface": "env",
          "default": "",
          "description": "GitHub PAT (scopes: repo, read:org, gist, read:user); empty for `gh auth login` on first run."
        }
      ],
      "documentationURL": "https://cli.github.com/",
      "presets": []
    },
    {
      "name": "gitlab",
      "displayName": "GitLab CLI",
      "description": "The official GitLab CLI (`glab`) for merge requests, issues, CI/CD pipelines and releases. Targets gitlab.com or a self-managed host. Login persists across container rebuilds.",
      "options": [
        {
          "key": "apiToken",
          "type": "string",
          "surface": "env",
          "default": "",
          "description": "GitLab personal access token (scopes: `api`, `write_repository`); exported as GITLAB_TOKEN. Empty for `glab auth login` on first run."
        },
        {
          "key": "host",
          "type": "string",
          "surface": "yml",
          "default": "",
          "description": "Self-managed/Dedicated GitLab host (e.g. `gitlab.example.com`); empty targets gitlab.com. Exported as GITLAB_HOST, so every `glab` command uses it without `--hostname`."
        }
      ],
      "documentationURL": "https://gitlab.com/gitlab-org/cli",
      "presets": []
    },
    {
      "name": "graphify",
      "displayName": "graphify",
      "description": "Turns the workspace into a queryable knowledge graph the agent can ask instead of grepping. Code is parsed locally with tree-sitter, no model and no account involved.",
      "options": [
        {
          "key": "extras",
          "type": "string",
          "surface": "yml",
          "default": "sql,pdf,leiden",
          "description": "Comma-separated extras installed with the package. The default covers SQL schema extraction, PDF text and the Leiden community labels. Also available: `office`, `svg`, `postgres`, `neo4j`, `falkordb`, `watch`, `terraform`, `chinese`, `pascal`, and the standalone-CLI model backends `gemini`, `openai`, `anthropic`, `bedrock`, `ollama`, `kimi`. Left out on purpose: `video` (pulls faster-whisper plus yt-dlp), `dm` (compiles, needs a C toolchain) and `mcp` (the server duplicates the CLI this feature already installs)."
        },
        {
          "key": "strict",
          "type": "boolean",
          "surface": "yml",
          "default": false,
          "description": "Block the first raw file read of a Claude Code session until one `graphify query` has run, instead of only reminding. Upstream ties this to a project-scoped install; Monoceros wires the hook into the container instead, so nothing is written to your repository. It fires at most once per session and then falls back to the reminder, so it cannot strand an agent mid-task. Toggle it for a single session with `GRAPHIFY_HOOK_STRICT=0`."
        }
      ],
      "documentationURL": "https://github.com/Graphify-Labs/graphify",
      "presets": []
    },
    {
      "name": "opencode",
      "displayName": "OpenCode",
      "description": "sst's open-source, provider-agnostic AI coding agent (TUI). Model + provider key configurable; auth and session state persist across container rebuilds.",
      "options": [
        {
          "key": "apiToken",
          "type": "string",
          "surface": "env",
          "default": "",
          "description": "API key for the model provider (derived from `model`); written to `provider.<x>.options.apiKey` in opencode.json. Empty for `opencode auth login` on first run."
        },
        {
          "key": "lsp",
          "type": "boolean",
          "surface": "yml",
          "default": false,
          "description": "Start OpenCode's built-in language servers, so the agent sees type errors and unresolved imports right after an edit instead of when the tests run. Off by default, as upstream. A server whose toolchain is missing does not start."
        },
        {
          "key": "model",
          "type": "string",
          "surface": "yml",
          "default": "",
          "description": "Default model as `provider/model-id` (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-4o-mini`). The provider is derived from the prefix before `/`. Empty: pick interactively on first run."
        },
        {
          "key": "theme",
          "type": "string",
          "surface": "yml",
          "default": "",
          "description": "TUI theme, e.g. `system` (follows your terminal), `tokyonight`, `catppuccin`. Written to `~/.config/opencode/tui.json`, so a new workbench looks like the last one. Empty: whatever you pick with `/theme` in the TUI."
        }
      ],
      "documentationURL": "https://opencode.ai/docs/",
      "presets": []
    },
    {
      "name": "opencode-roles",
      "displayName": "OpenCode roles (plan / implement / review)",
      "description": "Three OpenCode agents that split a task: a planner writes a plan file, an implementer executes it, a reviewer checks the result against it. Adds `/monoceros-plan`, `/monoceros-ship` and `/monoceros-review`. Requires the `opencode` feature.",
      "options": [
        {
          "key": "implementEffort",
          "type": "string",
          "surface": "yml",
          "default": "",
          "description": "Model variant for the implementing role. It follows a plan that already names the files and the acceptance command, so it needs less deliberation than the other two. Empty: same as `plannerEffort`."
        },
        {
          "key": "implementModel",
          "type": "string",
          "surface": "yml",
          "default": "",
          "description": "Model for the implementing role. This is the role that runs most often, so a cheaper model belongs here - but it must call tools reliably, or it burns more in retries than it saves. Empty: same fallback as `plannerModel`."
        },
        {
          "key": "plannerEffort",
          "type": "string",
          "surface": "yml",
          "default": "",
          "description": "Model variant for the planning role, which is how OpenCode exposes reasoning effort. The accepted values come from the model, not from Monoceros: Kimi K3 takes `low`, `high` and `max`, other models differ. Empty: whatever the model does on its own."
        },
        {
          "key": "plannerModel",
          "type": "string",
          "surface": "yml",
          "default": "",
          "description": "Model for the planning role as `provider/model-id`. Planning carries the reasoning: a strong model here pays for itself, because the plan decides how small the implementing model can be. Empty: fall back to the `opencode` feature's `model`, and to whatever OpenCode would pick when that is empty too."
        },
        {
          "key": "reviewEffort",
          "type": "string",
          "surface": "yml",
          "default": "",
          "description": "Model variant for the reviewing role. It has to notice a step that was skipped and a security hole nobody asked about, which is thinking work. Empty: same as `plannerEffort`."
        },
        {
          "key": "reviewModel",
          "type": "string",
          "surface": "yml",
          "default": "",
          "description": "Model for the reviewing role. Reviewing needs the same class of model as planning: it has to notice a step that was skipped, not just read a diff. Empty: same fallback as `plannerModel`."
        }
      ],
      "documentationURL": "https://getmonoceros.build/docs/features/opencode-roles/",
      "presets": []
    }
  ],
  "mcpServers": [
    {
      "name": "context7",
      "displayName": "Context7",
      "description": "Current documentation for the libraries a project uses, so the agent stops writing APIs from memory.",
      "options": [
        {
          "key": "apiKey",
          "type": "string",
          "surface": "env",
          "default": "",
          "description": "Context7 API key from context7.com. The public endpoint is heavily rate limited without one, so Monoceros requires it rather than registering a server that fails partway through a run."
        }
      ],
      "documentationURL": "https://context7.com",
      "transport": "http"
    },
    {
      "name": "figma",
      "displayName": "Figma",
      "description": "The design read from the Figma file: layout, components and design tokens, instead of a screenshot to guess from.",
      "options": [],
      "documentationURL": "https://help.figma.com/hc/en-us/articles/32132100833559-Guide-to-the-Figma-MCP-server",
      "transport": "http",
      "auth": "oauth"
    },
    {
      "name": "forge",
      "displayName": "Atlassian Forge",
      "description": "Atlassian's own guidance for building a Forge app: which module fits, what the manifest needs, how the UI Kit works.",
      "options": [],
      "documentationURL": "https://developer.atlassian.com/platform/forge/ai-development-toolkit/forge-mcp/",
      "transport": "http"
    },
    {
      "name": "linear",
      "displayName": "Linear",
      "description": "Issues, projects and cycles, so the work an agent picks up is the work the team actually planned.",
      "options": [],
      "documentationURL": "https://linear.app/docs/mcp",
      "transport": "http",
      "auth": "oauth"
    },
    {
      "name": "microsoft-learn",
      "displayName": "Microsoft Learn",
      "description": "First-party .NET and Azure documentation with runnable code samples. Microsoft ground only: for every other library, Context7 is the one to ask.",
      "options": [],
      "documentationURL": "https://learn.microsoft.com/training/support/mcp",
      "transport": "http"
    },
    {
      "name": "notion",
      "displayName": "Notion",
      "description": "Pages and databases, so requirements written in Notion reach the agent that has to build them.",
      "options": [],
      "documentationURL": "https://developers.notion.com/guides/mcp/overview",
      "transport": "http",
      "auth": "oauth"
    },
    {
      "name": "rovo",
      "displayName": "Atlassian Rovo",
      "description": "Jira issues and Confluence pages, so an agent reads the story it is implementing instead of waiting for someone to paste it in.",
      "options": [],
      "documentationURL": "https://www.atlassian.com/platform/rovo-mcp",
      "transport": "http",
      "auth": "oauth"
    }
  ]
}
