devops_utils.agent.install module

Install bundled skills and wire the MCP server into an agent’s config.

Framework-agnostic helpers used by the devops-utils setup CLI. Kept out of the Click layer so the copy/merge logic stays importable and testable on its own (mirrors how devops_utils.agent.tools stays free of any framework import).

The bundled skill markdown lives at devops_utils/agent/skills/ — either a flat *.md or a <dir>/SKILL.md with reference files beside it — and is read through importlib.resources, so it resolves the same way from a source checkout and from an installed wheel.

class devops_utils.agent.install.BundledSkill(name: str, source: str, files: dict[str, str])[source]

Bases: object

One skill bundled with the package.

Variables:
  • name (str) – Frontmatter name: (falls back to the file/dir stem).

  • source (str) – Bundled entry name — foo.md for a single-file skill, foo for a directory skill.

  • files (dict[str, str]) – {relative_path: text}. A single-file skill has one entry, SKILL.md; a directory skill carries SKILL.md plus its reference files (e.g. reference/operations.md), relative paths always /-separated.

files: dict[str, str]
name: str
source: str
property text: str

The SKILL.md body (the part loaded when the skill triggers).

devops_utils.agent.install.ConfirmOverwrite

Callback deciding whether an existing target may be overwritten. Returning True writes the new content; False leaves the file untouched. The install helpers stay UI-free — the Click layer supplies the prompt.

alias of Callable[[OverwriteRequest], bool]

devops_utils.agent.install.MARKETPLACE_NAME = 'carneirofc'

Marketplace name users add before installing the plugin (/plugin install devops-utils@<MARKETPLACE_NAME>).

devops_utils.agent.install.MCP_COMMAND = 'devops-utils-mcp'

Console script that launches the stdio MCP server (see [project.scripts]).

devops_utils.agent.install.MCP_SERVER_NAME = 'devops-utils'

Name under which the MCP server is registered in an agent’s config.

class devops_utils.agent.install.OverwriteRequest(path: Path, new_text: str, existing_text: str, label: str)[source]

Bases: object

One pending overwrite, handed to a ConfirmOverwrite callback.

Variables:
  • path (pathlib.Path) – File that would be written.

  • new_text (str) – Content that would replace what is on disk.

  • existing_text (str) – Content currently on disk.

  • label (str) – Human-facing name for the item (usually str(path), but for a merged JSON config it names the entry, not just the file).

existing_text: str
label: str
new_text: str
path: Path
devops_utils.agent.install.PLUGIN_NAME = 'devops-utils'

Claude Code plugin name. The harness namespaces a plugin’s skills/agents as <plugin-name>:<component>, so with this name the bundled skills/agents list as devops-utils:azure-devops-research, devops-utils:azdo-workitem-analyst, etc. — distinguishable at a glance from unrelated skills/agents.

devops_utils.agent.install.env_template() → str[source]

Return an .env scaffold for the Azure DevOps credentials.

devops_utils.agent.install.install_agents(dest: Path, force: bool = False, confirm: Callable[[OverwriteRequest], bool] | None = None) → tuple[list[Path], list[Path]][source]

Copy the bundled Claude Code agents into dest/agents/<name>.md.

Claude Code discovers subagents as single .md files in an agents/ directory (user scope ~/.claude/agents, project scope .claude/agents).

Parameters:
  • dest – Base directory to install into (e.g. ~/.claude).

  • force – Overwrite existing files instead of skipping them.

  • confirm – Asked per existing file when force is not set; None skips existing files outright.

Returns:

A (written, skipped) tuple of destination paths.

devops_utils.agent.install.install_plugin(repo_root: Path, force: bool = False, confirm: Callable[[OverwriteRequest], bool] | None = None) → tuple[list[Path], list[Path]][source]

Generate the Claude Code plugin + marketplace tree under repo_root.

Lays the bundled skills/agents out where Claude Code discovers them for a plugin named PLUGIN_NAME, and writes the two manifests:

  • repo_root/plugins/devops-utils/skills/<name>/SKILL.md (via install_skills() with the "claude" layout)

  • repo_root/plugins/devops-utils/agents/<name>.md (via install_agents())

  • repo_root/plugins/devops-utils/.claude-plugin/plugin.json

  • repo_root/.claude-plugin/marketplace.json

The skill/agent markdown is copied byte-for-byte from the bundled sources; the <plugin>:<name> namespace is derived by Claude Code from this layout, so no frontmatter rewriting is needed.

Parameters:
  • repo_root – Repository root to generate the plugin tree into.

  • force – Overwrite existing files instead of skipping them.

  • confirm – Asked per existing file when force is not set; None skips existing files outright.

Returns:

A (written, skipped) tuple of destination paths.

devops_utils.agent.install.install_skills(dest: Path, layout: str = 'claude', force: bool = False, confirm: Callable[[OverwriteRequest], bool] | None = None) → tuple[list[Path], list[Path]][source]

Copy the bundled skills into dest.

Parameters:
  • dest – Base directory to install into.

  • layout – "claude" writes dest/skills/<name>/SKILL.md (Claude Code discovery layout) plus any reference files beside it; "flat" writes a single-file skill as dest/<filename> and a directory skill as dest/<dir>/....

  • force – Overwrite existing files instead of skipping them.

  • confirm – Asked per existing file when force is not set; None skips existing files outright.

Returns:

A (written, skipped) tuple of destination paths.

devops_utils.agent.install.install_tracker(dest: Path, project_name: str, done_state: str = 'Closed', org_url: str | None = None, parent_epic: int | None = None, area_path: str | None = None, default_tags: list[str] | None = None, force: bool = False, confirm: Callable[[OverwriteRequest], bool] | None = None) → tuple[list[Path], list[Path]][source]

Write the Azure DevOps tracker config for mattpocock-style skills.

Renders the bundled templates (agent/trackers/*.md) into dest/docs/agents/, substituting the placeholders computed by _tracker_substitutions(). The resulting issue-tracker.md / triage-labels.md are the config files skills like mattpocock/skills read to learn how to talk to the issue tracker.

Parameters:
  • dest – Repository root to install into (files go to docs/agents/).

  • project_name – Azure DevOps team project name.

  • done_state – System.State value that means “closed” in the project’s process template (e.g. Closed, Done, Resolved).

  • org_url – Azure DevOps organization/collection URL recorded in the defaults table (the env var stays authoritative at runtime).

  • parent_epic – Work-item id of the Epic new items are parented under.

  • area_path – Default System.AreaPath for creates and queries.

  • default_tags – Tags applied to every created item and used to scope queries.

  • force – Overwrite existing files instead of skipping them.

  • confirm – Asked per existing file when force is not set; None skips existing files outright.

Returns:

A (written, skipped) tuple of destination paths.

devops_utils.agent.install.iter_bundled_agents() → list[tuple[str, str, str]][source]

List the Claude Code agent files bundled with the package.

Returns:

A list of (agent_name, filename, text) tuples, sorted by filename. agent_name comes from the frontmatter name: (falling back to the file stem).

devops_utils.agent.install.iter_bundled_skills() → list[BundledSkill][source]

List the skills bundled with the package, sorted by source name.

A skill is either a flat <stem>.md or a <stem>/ directory holding SKILL.md plus reference files it links to — the progressive-disclosure shape for skills too long to load in one go.

devops_utils.agent.install.marketplace_manifest() → dict[str, object][source]

Return the .claude-plugin/marketplace.json contents for the repo.

Declares a single-plugin marketplace whose source is the relative path ./plugins/devops-utils (Claude Code resolves it from the marketplace root, i.e. the repo root). Users install with:

/plugin marketplace add carneirofc/devops-utils
/plugin install devops-utils@carneirofc
devops_utils.agent.install.mcp_server_entry(use_uvx: bool = True) → dict[str, object][source]

Return the MCP mcpServers entry for the stdio server.

Parameters:

use_uvx – When True (default) the entry launches the server through uvx --from "devops-utils[mcp]" devops-utils-mcp — zero-install, nothing needs to be on PATH beyond uv. When False the entry is the bare devops-utils-mcp console script, for environments where the package is installed (pip install "devops-utils[mcp]") and uv is not available.

devops_utils.agent.install.merge_mcp_config(path: Path, name: str = 'devops-utils', force: bool = False, use_uvx: bool = True, confirm: Callable[[OverwriteRequest], bool] | None = None) → tuple[Path, bool][source]

Register the MCP server in a JSON config, preserving other servers.

Loads path if it exists, sets mcpServers[name] to mcp_server_entry() without touching sibling servers, and writes it back pretty-printed. Works for both ~/.claude.json (user) and .mcp.json (project).

Parameters:
  • path – Path to the JSON config file (created if missing).

  • name – Server key to register under.

  • force – Overwrite an existing entry for name. When False and the entry already exists, the file is left unchanged.

  • use_uvx – Passed to mcp_server_entry() — True (default) writes the zero-install uvx launcher, False the on-PATH devops-utils-mcp console script.

  • confirm – Asked before replacing an existing entry when force is not set; None leaves the entry alone. The request compares the entry JSON, not the whole file — sibling servers are never at stake.

Returns:

A (path, changed) tuple. changed is False when an entry for name already existed and neither force nor confirm allowed replacing it.

devops_utils.agent.install.plugin_manifest() → dict[str, object][source]

Return the .claude-plugin/plugin.json contents for the bundled plugin.

The plugin name drives Claude Code’s namespacing: skills and agents shipped under it list as devops-utils:<component> (see PLUGIN_NAME). The MCP server is intentionally not declared here — a plugin-bundled MCP server is re-scoped to mcp__plugin_<plugin>_<server>__<tool>, which would break the agents’ tools: frontmatter that targets mcp__devops-utils__azdo_*. MCP stays wired via merge_mcp_config() / setup mcp instead.

version is deliberately omitted (it is optional): the package uses VCS-derived versioning, so a value here would change on every commit and make the committed manifest churn and drift from a regeneration.

devops_utils.agent.install.write_env_scaffold(path: Path, force: bool = False, confirm: Callable[[OverwriteRequest], bool] | None = None) → Path | None[source]

Write the env scaffold to path (skipped if it exists and not force).