Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
3ab7b2a
Fixes docker and pyproject.
Nov 13, 2025
672ecb6
Merge pull request #3 from cymis/feature/acl
johnchase Feb 1, 2026
6096344
Adds pipeline parsing
Feb 17, 2026
290b212
Adds pipeline parsing
Feb 17, 2026
ee33e61
Fixes merge conflict in pyproject.toml
Feb 22, 2026
72c0122
Fixes merge conflict
Feb 22, 2026
51317a1
Adds pipeline cli interface
Feb 22, 2026
23e11e7
Adds arguments
Feb 22, 2026
cfa1f88
removes example
Feb 22, 2026
c88913b
Adds dummy progress
Feb 24, 2026
7d6223d
Adds dummy progress
Feb 24, 2026
b7c2948
Small refactor
Feb 24, 2026
50eea45
fix merge conflict
Feb 24, 2026
b315230
Adjusts progress bars
Feb 25, 2026
2394ada
Adds param grouping
Feb 25, 2026
18c57bd
Fixes runtime
Feb 26, 2026
b43259e
Adds serial qiime runner with docker
Feb 28, 2026
fba097e
Fizes output name issue
Mar 4, 2026
c543564
Merge pull request #4 from cymis/run-command
johnchase Mar 6, 2026
73fc12a
Merge pull request #5 from cymis/arguments-file
johnchase Mar 6, 2026
cc1ac17
Merge pull request #6 from cymis/progress-bars
johnchase Mar 6, 2026
a731f5e
removes example
Mar 6, 2026
740f781
restores qapi
Mar 7, 2026
97b5dc2
Merge pull request #8 from cymis/qapi
johnchase Mar 7, 2026
0ee4e28
Adds ability to add single plugins
Mar 8, 2026
17bbee7
Addresses github pr review
Mar 8, 2026
b7fbb28
Merge pull request #7 from cymis/run-fix
johnchase Mar 8, 2026
f35e155
Merge pull request #9 from cymis/single-plugin-add
johnchase Mar 8, 2026
a841ff1
Fixes pipeline/config merge
Mar 10, 2026
09b04ed
Merge pull request #10 from cymis/fix-config
johnchase Mar 10, 2026
77ee15d
Removes uv lock
Mar 10, 2026
a4ded9d
Merge branch 'dev' of github.com:cymis/adagio-cli into dev
Mar 10, 2026
a2e21c8
Adds readme
Mar 10, 2026
3236e7f
Merge pull request #11 from cymis/improve-readme
johnchase Mar 10, 2026
79ac361
feat(execution): add modular task-environment executor with Docker la…
Mar 13, 2026
0c9d6dc
Refactor
Mar 13, 2026
9c1911f
Add cache-backed task result reuse
Mar 20, 2026
afdec7e
Adds caching
Mar 20, 2026
36987d6
Merge pull request #12 from cymis/task-containers-x
johnchase Mar 20, 2026
08f6d47
Fixes remove cache command
Mar 20, 2026
a7e9a74
Fixes error formatting
Mar 20, 2026
d93767f
Merge pull request #14 from cymis/fix-formatting
johnchase Mar 20, 2026
96c050a
Adds config toml
Mar 20, 2026
980f316
Adds explicit architecture check
Mar 24, 2026
b45559c
Temporary move imports to be lazy
Mar 25, 2026
61e5d05
Adds apptainer support
Mar 25, 2026
5ee9aee
Fizes bug where host packages were injected into container
Mar 26, 2026
0dabb01
Adds param descriptions to help
Mar 27, 2026
31a7077
Adds pipeline summary commmand
Mar 27, 2026
adaa841
Writes files to disk as they are created
Mar 27, 2026
6dd6b17
Adds collection support
Mar 31, 2026
6a425ba
fix flicker
Apr 3, 2026
421eee8
Second attempt at fixing flicker
Apr 3, 2026
1c5d563
Next attempt at render
Apr 3, 2026
05d7bb8
Fix flickering tty
Apr 8, 2026
4e1295c
Merge pull request #13 from cymis/codex/adagio-cli-cache-reuse
johnchase Apr 8, 2026
c302dad
Merge pull request #15 from cymis/config-toml
johnchase Apr 8, 2026
e442815
Merge pull request #16 from cymis/add-param-description
johnchase Apr 8, 2026
9b52517
Merge pull request #17 from cymis/show-pipeline
johnchase Apr 8, 2026
34cb7cc
Merge pull request #18 from cymis/collections
johnchase Apr 8, 2026
90276c3
Merge pull request #19 from cymis/apptainer
johnchase Apr 8, 2026
d004981
Adds qapi token submission
Apr 8, 2026
00c7368
Merge pull request #20 from cymis/qapi-token
johnchase Apr 8, 2026
14c30db
Adds community pipeline support
Apr 14, 2026
eab8170
Removes user configuration
May 5, 2026
49eea49
Fixed merge conflicts
May 5, 2026
d147a18
remove formatting
May 5, 2026
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
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,27 @@ Set up the project and run the test suite with:
uv sync --group dev
uv run pytest
```

## Catalog pipelines

Run a pipeline from the Adagio pipeline catalog:

```bash
adagio pipeline show @adagio/microbial-diversity
adagio run @adagio/microbial-diversity --cache-dir /path/to/cache --arguments run-arguments.json
```

`@adagio/<slug>` first resolves against a nearby local `adagio-pipelines`
checkout when one is available. If no local catalog is found, Adagio fetches
`pipeline.adg` from `cymis/adagio-pipelines` on GitHub, checking `official`
before `community`.

During `adagio run`, remote catalog pipelines are downloaded under the selected
`--cache-dir` and reused by source name and slug on later runs. `adagio pipeline
show` uses a temporary download when it fetches from GitHub because it does not
take a cache directory.

Private GitHub access is explicit: set `GITHUB_TOKEN` or `GH_TOKEN` to a token
that can read `cymis/adagio-pipelines`; with a token, the CLI fetches through
the GitHub contents API. The CLI does not read browser, git, or `gh` credentials
automatically.
4 changes: 2 additions & 2 deletions src/adagio/cli/dynamic.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ def build_dynamic_run(
CliParameter(
name=("--pipeline", "-p"),
group=command_group,
help="Path to the pipeline JSON file.",
help="Path to the pipeline file or a catalog reference like @adagio/slug.",
),
]
}
Expand Down Expand Up @@ -589,7 +589,7 @@ def run(
run.__doc__ = (
"Run an Adagio pipeline.\n\n"
"Dynamic inputs, parameters, and outputs are loaded from the pipeline file and exposed as CLI options.\n"
"Use: adagio run --pipeline PATH --help"
"Use: adagio run --pipeline PATH-OR-@ADAGIO/SLUG --help"
)
return run

Expand Down
117 changes: 82 additions & 35 deletions src/adagio/cli/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
import sys
from contextlib import ExitStack
from functools import partial
from pathlib import Path
from typing import Annotated, Any
Expand All @@ -13,11 +14,16 @@
from ..app.parsers.pipeline import Output as OutputSpec
from ..app.parsers.pipeline import Parameter as ParamSpec
from ..app.parsers.pipeline import parse_inputs, parse_outputs, parse_parameters
from ..executors.cache_support import CACHE_DIR_HELP, REUSE_HELP
from ..executors.cache_support import CACHE_DIR_HELP, REUSE_HELP, resolve_cache_dir_path
from .args import ShowParamsMode, extract_flag_value, promote_positional_pipeline
from .config import load_run_config
from .dynamic import build_dynamic_run
from .pipeline import run_pipeline_cli
from .pipeline_sources import (
PipelineResolution,
PipelineResolutionError,
resolve_pipeline_reference_details,
)
from .qapi import run_qapi
from .runner import run_pipeline_from_kwargs

Expand Down Expand Up @@ -56,6 +62,7 @@ def main(argv: list[str] | None = None) -> None:

argv, positional_pipeline = promote_positional_pipeline(argv)
pipeline_str = extract_flag_value(argv, "--pipeline", "-p")
cache_dir_str = extract_flag_value(argv, "--cache-dir")
show_mode_str = extract_flag_value(argv, "--show-params")
try:
show_mode = (
Expand Down Expand Up @@ -108,7 +115,7 @@ def run(
Parameter(
name=("--pipeline", "-p"),
group=command_group,
help="Path to the pipeline JSON file.",
help="Path to the pipeline file or a catalog reference like @adagio/slug.",
),
],
arguments: Annotated[
Expand Down Expand Up @@ -155,45 +162,61 @@ def run(
):
"""Run a pipeline (requires --pipeline; dynamic options come from that file)."""
_ = (config, show_params, cache_dir, reuse)
console.print(CycloptsPanel("Missing --pipeline. Try:\n adagio run --pipeline pipeline.json --help"))
console.print(
CycloptsPanel(
"Missing --pipeline. Try:\n"
" adagio run --pipeline pipeline.adg --help\n"
" adagio run @adagio/microbial-diversity --help"
)
)
sys.exit(1)

app(argv)
return

pipeline_path = Path(pipeline_str)
data = json.loads(pipeline_path.read_text(encoding="utf-8"))
input_specs = parse_inputs(data)
param_specs = parse_parameters(data)
output_specs = parse_outputs(data)
arguments_path_str = extract_flag_value(argv, "--arguments")
config_path_str = extract_flag_value(argv, "--config")
arguments_data = (
_load_arguments_data(Path(arguments_path_str), console) if arguments_path_str else None
)
if config_path_str:
load_run_config(Path(config_path_str))
visible_inputs, visible_params, visible_outputs = _filter_visible_specs(
input_specs=input_specs,
param_specs=param_specs,
output_specs=output_specs,
show_mode=show_mode,
arguments_data=arguments_data,
)
with ExitStack() as exit_stack:
pipeline_resolution = _resolve_pipeline(
pipeline_str,
console=console,
exit_stack=exit_stack,
download_cache_dir=_resolve_download_cache_dir(cache_dir_str),
)
data = json.loads(pipeline_resolution.path.read_text(encoding="utf-8"))
input_specs = parse_inputs(data)
param_specs = parse_parameters(data)
output_specs = parse_outputs(data)
arguments_path_str = extract_flag_value(argv, "--arguments")
config_path_str = extract_flag_value(argv, "--config")
arguments_data = (
_load_arguments_data(Path(arguments_path_str), console) if arguments_path_str else None
)
if config_path_str:
load_run_config(Path(config_path_str))
visible_inputs, visible_params, visible_outputs = _filter_visible_specs(
input_specs=input_specs,
param_specs=param_specs,
output_specs=output_specs,
show_mode=show_mode,
arguments_data=arguments_data,
)

dynamic_run = build_dynamic_run(
input_specs=input_specs,
param_specs=param_specs,
output_specs=output_specs,
visible_input_names={spec.name for spec in visible_inputs},
visible_param_names={spec.name for spec in visible_params},
visible_output_names={spec.name for spec in visible_outputs},
argument_inputs=arguments_data.get("inputs", {}) if arguments_data else None,
argument_params=arguments_data.get("parameters", {}) if arguments_data else None,
run_handler=partial(run_pipeline_from_kwargs, console=console),
)
app.command(dynamic_run, name="run")
app(argv)
dynamic_run = build_dynamic_run(
input_specs=input_specs,
param_specs=param_specs,
output_specs=output_specs,
visible_input_names={spec.name for spec in visible_inputs},
visible_param_names={spec.name for spec in visible_params},
visible_output_names={spec.name for spec in visible_outputs},
argument_inputs=arguments_data.get("inputs", {}) if arguments_data else None,
argument_params=arguments_data.get("parameters", {}) if arguments_data else None,
run_handler=partial(
run_pipeline_from_kwargs,
console=console,
resolved_pipeline=pipeline_resolution,
),
)
app.command(dynamic_run, name="run")
app(argv)


def _filter_visible_specs(
Expand Down Expand Up @@ -262,5 +285,29 @@ def _is_missing(value: Any) -> bool:
return value is None or value == "" or value == "<fill me>" or value == [] or value == {}


def _resolve_pipeline(
reference: str,
*,
console: Console,
exit_stack: ExitStack,
download_cache_dir: Path | None = None,
) -> PipelineResolution:
try:
return resolve_pipeline_reference_details(
reference,
exit_stack=exit_stack,
download_cache_dir=download_cache_dir,
)
except PipelineResolutionError as error:
console.print(CycloptsPanel(str(error)))
sys.exit(1)


def _resolve_download_cache_dir(raw_value: str | None) -> Path | None:
if raw_value is None:
return None
return resolve_cache_dir_path(cwd=Path.cwd().resolve(), raw_value=raw_value)


if __name__ == "__main__":
main()
16 changes: 12 additions & 4 deletions src/adagio/cli/pipeline.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import json
from contextlib import ExitStack
from pathlib import Path

from cyclopts import App
from rich.console import Console

from ..describe import render_pipeline_text
from ..model.pipeline import AdagioPipeline
from .pipeline_sources import PipelineResolutionError, resolve_pipeline_reference

console = Console()

Expand All @@ -21,7 +23,13 @@ def run_pipeline_cli(argv: list[str]) -> None:

def show_pipeline(pipeline: Path) -> None:
"""Print a pipeline summary to the terminal."""
data = json.loads(pipeline.read_text(encoding="utf-8"))
pipeline_data = data.get("spec", data) if isinstance(data, dict) else data
parsed_pipeline = AdagioPipeline.model_validate(pipeline_data)
console.print(render_pipeline_text(parsed_pipeline), soft_wrap=True)
with ExitStack() as exit_stack:
try:
pipeline_path = resolve_pipeline_reference(pipeline, exit_stack=exit_stack)
except PipelineResolutionError as error:
raise SystemExit(str(error)) from error

data = json.loads(pipeline_path.read_text(encoding="utf-8"))
pipeline_data = data.get("spec", data) if isinstance(data, dict) else data
parsed_pipeline = AdagioPipeline.model_validate(pipeline_data)
console.print(render_pipeline_text(parsed_pipeline), soft_wrap=True)
Loading
Loading