Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
without waiting for a table refresh; providers whose SDK exposes no pricing
(e.g. the Anthropic API) fall back to the table.
([#265](https://github.com/microsoft/conductor/issues/265))
- **`conductor doctor` — provider & environment diagnostics** — a safe,
read-only command that reports which providers are installed, their
capability tier (`stable` / `experimental`), which credential environment
variables are detected (presence only — values are never printed), plus
Conductor version / update status and configured registries. Offline by
default; `--check` tests provider connections, `--models` lists available
models, `--provider NAME` scopes to one provider, and `--json` emits
machine-readable output for CI. Exit code is `1` only when `--check` is set
and the scoped provider (default `copilot`) fails to connect. Also adds a
public `list_models()` method to the provider interface (implemented for
Copilot and Claude). ([#274](https://github.com/microsoft/conductor/issues/274))

### Fixed

Expand Down
83 changes: 83 additions & 0 deletions docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Complete command-line reference for Conductor.
- [`conductor stop`](#conductor-stop)
- [`conductor gate-respond`](#conductor-gate-respond)
- [`conductor validate`](#conductor-validate)
- [`conductor doctor`](#conductor-doctor)
- [`conductor registry`](#conductor-registry)

## `conductor run`
Expand Down Expand Up @@ -325,6 +326,88 @@ for f in examples/*.yaml; do conductor validate "$f"; done
**Warnings** (validation passes with notes):
- **Undeclared dependencies in explicit mode** — agent prompt references `{{ a.output.val }}` but doesn't declare `a.output` in its `input:` list

## `conductor doctor`

Report provider and environment diagnostics — a safe, read-only health check
for your Conductor setup. Answers "is my setup healthy?" without running a
workflow: which providers are installed, their stability tier, which
credential environment variables are detected, plus Conductor version /
update status and configured registries.

```bash
conductor doctor [SECTION] [OPTIONS]
```

`SECTION` (optional positional) limits output to one of `providers`,
`registries`, or `env`. When omitted, all three sections are shown.

**Offline by default** — no providers are instantiated and no credentials are
required. The only default network access is the GitHub-releases update check
in the `env` section (cache-first, short timeout, silent, and skipped when
`CONDUCTOR_NO_UPDATE_CHECK` is set).

### Options

| Option | Short | Description |
|--------|-------|-------------|
| `--check` | | Instantiate each provider and test its connection via `validate_connection()` (performs network I/O). |
| `--models` | | List each provider's available models (implies `--check`). |
| `--provider NAME` | `-p` | Scope the providers section to a single provider. |
| `--json` | | Emit machine-readable JSON instead of Rich tables (for CI). |

### Sections

- **env** — Conductor version, Python version, OS/platform, and update
availability.
- **providers** — for each known provider (`copilot`, `claude`,
`claude-agent-sdk`, `hermes`, `openai-agents`): whether the SDK is
installed, the capability tier (`stable` / `experimental`), which
credential environment variables are **present** (presence only — values
are never printed), and — with `--check` / `--models` — connection status
and available models. `openai-agents` is surfaced as "not yet implemented".
- **registries** — configured workflow registries and which is the default
(see [`conductor registry`](#conductor-registry)).

### Credential detection

Only the **presence** of credential environment variables is reported —
values are never read or printed. Detected variables per provider:

| Provider | Environment variables |
|----------|-----------------------|
| `copilot` | `GITHUB_TOKEN`, `GH_TOKEN`, `COPILOT_PROVIDER_API_KEY`, `COPILOT_PROVIDER_BEARER_TOKEN` |
| `claude` | `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN` |
| `claude-agent-sdk` | `ANTHROPIC_API_KEY` |
| `hermes` | *(none — endpoint / API key are passed explicitly)* |

### Exit codes

- `0` — success (the default for offline runs; missing credentials for an
*optional* provider never fail the command).
- `1` — an invalid `SECTION`/`--provider` was given, **or** `--check` was set
and the **scoped** provider failed to connect. The scoped provider is the
one named by `--provider`, or `copilot` (the default) when `--provider` is
omitted.

### Examples

```bash
# Full offline report (all sections)
conductor doctor

# Just the providers section
conductor doctor providers

# Actually test provider connections (network)
conductor doctor --check

# List available models for a single provider
conductor doctor --models --provider claude

# Machine-readable output for CI
conductor doctor --json
```

## `conductor registry`

Manage workflow registries — named sources (GitHub repos or local directories) for shared workflows.
Expand Down
85 changes: 83 additions & 2 deletions src/conductor/cli/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,10 +251,12 @@ def main(
if console.is_terminal and verbosity != ConsoleVerbosity.SILENT:
import sys

# Skip when the subcommand is 'update'
# Skip when the subcommand is 'update' or 'doctor' — both surface
# update status in their own output (doctor in its env section), so
# the startup hint would be redundant noise.
args = sys.argv[1:]
subcommand = next((a for a in args if not a.startswith("-")), None)
if subcommand != "update":
if subcommand not in ("update", "doctor"):
from conductor.cli.update import check_for_update_hint

check_for_update_hint(console)
Expand Down Expand Up @@ -1439,3 +1441,82 @@ def update(
except Exception as e:
print_error(e)
raise typer.Exit(code=1) from None


@app.command()
def doctor(
section: Annotated[
str | None,
typer.Argument(
help="Section to show: providers | registries | env. Default: all sections.",
),
] = None,
check: Annotated[
bool,
typer.Option(
"--check",
help="Instantiate providers and test their connections (network).",
),
] = False,
models: Annotated[
bool,
typer.Option(
"--models",
help="List available models for each provider (implies --check).",
),
] = False,
provider: Annotated[
str | None,
typer.Option(
"--provider",
"-p",
help="Scope the providers section to a single provider.",
),
] = None,
as_json: Annotated[
bool,
typer.Option(
"--json",
help="Emit machine-readable JSON instead of tables.",
),
] = False,
) -> None:
"""Report provider & environment diagnostics.

A safe, read-only health check for your Conductor setup: which providers
are installed, their stability tier, which credential environment
variables are detected (presence only — values are never printed), plus
Conductor version / update status and configured registries.

Offline by default — no providers are instantiated and no credentials are
required. (The default env section does a cache-first GitHub update check;
set CONDUCTOR_NO_UPDATE_CHECK to disable it.) Use --check to actually test
provider connections, and --models to list each provider's available
models.

\b
Examples:
conductor doctor # all sections
conductor doctor providers # providers section only
conductor doctor --check # test provider connections
conductor doctor --models -p claude # list Claude's models
conductor doctor --json # machine-readable output
"""
from conductor.cli.doctor import run_doctor

try:
exit_code = run_doctor(
section=section,
provider=provider,
check=check,
models=models,
as_json=as_json,
console=output_console,
err_console=console,
)
except Exception as e:
print_error(e)
raise typer.Exit(code=1) from None

if exit_code != 0:
raise typer.Exit(code=exit_code)
Loading
Loading