orchestra-cli 0.0.5__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. orchestra_cli-0.1.2/PKG-INFO +196 -0
  2. orchestra_cli-0.1.2/README.md +185 -0
  3. orchestra_cli-0.1.2/orchestra_cli/src/cli.py +12 -0
  4. orchestra_cli-0.1.2/orchestra_cli/src/import_pipeline.py +223 -0
  5. orchestra_cli-0.1.2/orchestra_cli/src/run_pipeline.py +182 -0
  6. orchestra_cli-0.0.5/orchestra_cli/src/cli.py → orchestra_cli-0.1.2/orchestra_cli/src/validate_pipeline.py +3 -44
  7. orchestra_cli-0.1.2/orchestra_cli/tests/__init__.py +0 -0
  8. orchestra_cli-0.1.2/orchestra_cli/tests/conftest.py +14 -0
  9. {orchestra_cli-0.0.5 → orchestra_cli-0.1.2}/orchestra_cli/tests/test_cli.py +0 -6
  10. orchestra_cli-0.1.2/orchestra_cli/tests/test_import_pipeline.py +264 -0
  11. orchestra_cli-0.1.2/orchestra_cli/tests/test_run_pipeline.py +260 -0
  12. orchestra_cli-0.1.2/orchestra_cli/tests/test_validate_pipeline.py +11 -0
  13. orchestra_cli-0.1.2/orchestra_cli/utils/constants.py +7 -0
  14. orchestra_cli-0.1.2/orchestra_cli/utils/git.py +50 -0
  15. orchestra_cli-0.1.2/orchestra_cli/utils/styling.py +22 -0
  16. orchestra_cli-0.1.2/orchestra_cli.egg-info/PKG-INFO +196 -0
  17. orchestra_cli-0.1.2/orchestra_cli.egg-info/SOURCES.txt +22 -0
  18. {orchestra_cli-0.0.5 → orchestra_cli-0.1.2}/pyproject.toml +2 -2
  19. orchestra_cli-0.0.5/PKG-INFO +0 -34
  20. orchestra_cli-0.0.5/README.md +0 -23
  21. orchestra_cli-0.0.5/orchestra_cli.egg-info/PKG-INFO +0 -34
  22. orchestra_cli-0.0.5/orchestra_cli.egg-info/SOURCES.txt +0 -11
  23. {orchestra_cli-0.0.5 → orchestra_cli-0.1.2}/orchestra_cli/src/__init__.py +0 -0
  24. {orchestra_cli-0.0.5 → orchestra_cli-0.1.2}/orchestra_cli.egg-info/dependency_links.txt +0 -0
  25. {orchestra_cli-0.0.5 → orchestra_cli-0.1.2}/orchestra_cli.egg-info/entry_points.txt +0 -0
  26. {orchestra_cli-0.0.5 → orchestra_cli-0.1.2}/orchestra_cli.egg-info/requires.txt +0 -0
  27. {orchestra_cli-0.0.5 → orchestra_cli-0.1.2}/orchestra_cli.egg-info/top_level.txt +0 -0
  28. {orchestra_cli-0.0.5 → orchestra_cli-0.1.2}/setup.cfg +0 -0
@@ -0,0 +1,196 @@
1
+ Metadata-Version: 2.4
2
+ Name: orchestra-cli
3
+ Version: 0.1.2
4
+ Summary: Orchestra CLI. Perform operations with Orchestra locally.
5
+ Author-email: Orchestra Technologies <support@getorchestra.io>
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: httpx>=0.28.1
9
+ Requires-Dist: pyyaml>=6.0.2
10
+ Requires-Dist: typer>=0.16.0
11
+
12
+ # orchestra-cli
13
+
14
+ Orchestra CLI for working with Orchestra pipelines from your terminal.
15
+
16
+ Two entrypoints are available: `orchestra` and `orchestra-cli` (they are equivalent).
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pip install orchestra-cli
22
+ ```
23
+
24
+ Or with pipx:
25
+
26
+ ```bash
27
+ pipx install orchestra-cli
28
+ ```
29
+
30
+ ## Environment variables
31
+
32
+ - `ORCHESTRA_API_KEY`: Required for actions that call the API (`import`, `run`).
33
+ - `BASE_URL`: Optional. Override the default API base
34
+ (`https://app.getorchestra.io/api/engine/public/pipelines/{}`) for non‑production/testing.
35
+
36
+ ## Commands overview
37
+
38
+ - `validate`: Validate a pipeline YAML locally against the Orchestra API schema.
39
+ - `import`: Register a pipeline YAML (from a git repo) with Orchestra under an alias.
40
+ - `run`: Start a pipeline run by alias, optionally pinning branch/commit and waiting for completion.
41
+
42
+ Use `orchestra --help` or `orchestra <command> --help` for built-in help.
43
+
44
+ ---
45
+
46
+ ## validate
47
+
48
+ Validate a YAML file against the Orchestra API schema.
49
+
50
+ ```bash
51
+ orchestra validate path/to/pipeline.yaml
52
+ # or
53
+ orchestra-cli validate path/to/pipeline.yaml
54
+ ```
55
+
56
+ Options
57
+
58
+ - `file` (positional): Path to the YAML file to validate.
59
+
60
+ Behavior
61
+
62
+ - Prints a success message on valid input.
63
+ - On validation errors, prints the failing location(s), readable messages, and a YAML snippet when possible.
64
+ - Exit codes: `0` on success, `1` on invalid file/validation failure/HTTP error.
65
+
66
+ Example output (failure)
67
+
68
+ ```text
69
+ ❌ Validation failed with status 422
70
+ Error at: pipeline.tasks[0].type
71
+ Invalid task type "foo"
72
+
73
+ YAML snippet:
74
+ pipeline:
75
+ tasks:
76
+ - type: foo
77
+ ```
78
+
79
+ ---
80
+
81
+ ## import
82
+
83
+ Create (import) a pipeline in Orchestra by referencing a YAML file inside a git repository. The command infers your repository host/provider, default branch, and YAML path relative to the repo root.
84
+
85
+ ```bash
86
+ export ORCHESTRA_API_KEY=... # required
87
+
88
+ orchestra import \
89
+ --alias my-pipeline \
90
+ --path ./pipelines/pipeline.yaml \
91
+ --working-branch my-feature-branch # optional; defaults to current local branch
92
+ # or
93
+ orchestra-cli import -a my-pipeline -p ./pipelines/pipeline.yaml
94
+ ```
95
+
96
+ Options
97
+
98
+ - `-a, --alias` (required): The alias you want to register the pipeline under.
99
+ - `-p, --path` (required): Path to the YAML file. Must be inside a git repository.
100
+ - `-w, --working-branch` (optional): Branch to associate as the working branch for this pipeline import. If omitted, the current local git branch is used.
101
+
102
+ Notes
103
+
104
+ - The YAML is validated with the API before import; failures are printed clearly.
105
+ - Git details are detected automatically:
106
+ - Supported providers: GitHub, GitLab, Azure DevOps.
107
+ - The default branch is detected from `origin`.
108
+ - The working branch defaults to your current local branch when not specified.
109
+ - The YAML path is computed relative to the repository root.
110
+ - On success, the command prints the created pipeline ID (or a success message).
111
+ - Exit codes: `0` on success, `1` on failure.
112
+
113
+ Common errors
114
+
115
+ - Missing `ORCHESTRA_API_KEY`.
116
+ - Not running inside a git repository, or no `origin` remote configured.
117
+ - Could not detect storage provider or default branch.
118
+
119
+ ---
120
+
121
+ ## run
122
+
123
+ Start a pipeline run by alias. Optionally specify a branch and/or commit. By default, the command waits and polls the run status until completion.
124
+
125
+ ```bash
126
+ export ORCHESTRA_API_KEY=...
127
+
128
+ # Start and wait for completion
129
+ orchestra run --alias my-pipeline
130
+
131
+ # Start without waiting (prints run id and exits)
132
+ orchestra run -a my-pipeline --no-wait
133
+
134
+ # Start for a specific branch/commit
135
+ orchestra run -a my-pipeline -b feature/my-change -c 0123abc
136
+ ```
137
+
138
+ Options
139
+
140
+ - `-a, --alias` (required): Pipeline alias to run.
141
+ - `-b, --branch` (optional): Git branch name to associate with this run.
142
+ - `-c, --commit` (optional): Commit SHA to associate with this run.
143
+ - `--wait/--no-wait` (default: `--wait`): Poll until the run ends.
144
+ - `--force/--no-force` (default: `--no-force`): Skip confirmation if local git warnings are detected.
145
+
146
+ Behavior
147
+
148
+ - Prints the run ID when known and a link to the run lineage page.
149
+ - When waiting, polls status every ~5s until a terminal state:
150
+ - `SUCCEEDED` (exit `0`), `WARNING` (exit `0`), `SKIPPED` (exit `0`)
151
+ - `FAILED` or `CANCELLED` (exit `1`)
152
+ - When not waiting, exits after start and prints the run ID.
153
+
154
+ Non-interactive usage
155
+
156
+ - If your repo has warnings (e.g., uncommitted changes), the CLI prompts for confirmation unless `--force` is provided. For CI or scripts, pass `--force` or ensure a clean repo.
157
+
158
+ ---
159
+
160
+ ## Examples
161
+
162
+ ```bash
163
+ # Validate a pipeline file
164
+ orchestra validate ./examples/etl.yaml
165
+
166
+ # Import a pipeline and capture the created ID
167
+ PIPELINE_ID=$(orchestra import -a finance-etl -p ./pipelines/etl.yaml)
168
+
169
+ # Start a run and wait for completion
170
+ orchestra run -a finance-etl
171
+
172
+ # Start a run and exit immediately
173
+ orchestra run -a finance-etl --no-wait
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Development
179
+
180
+ - Make sure [uv](https://github.com/astral-sh/uv) is installed
181
+ - Use `uv pip install -e ".[dev]"` to install the CLI in editable mode for development
182
+ - For local development, run `uv run orchestra` to start the CLI
183
+ - you can use `uv run --env-file .env ...` to run the CLI with env vars
184
+ - For testing, run `uv run pytest`
185
+ - For linting, run `uv run ruff check .`
186
+ - For formatting, run `uv run black --check .`
187
+ - For type checking, run `uv run pyright`
188
+
189
+ ## Building and Releasing
190
+
191
+ - Bump the version in `pyproject.toml` or by running `uv version --bump <major/minor/patch>`
192
+ - Run `uv sync` to install the dependencies
193
+ - Run `uv build` to build the CLI
194
+ - Run `uv publish` to publish the CLI (you will need to pass the `--token` flag)
195
+
196
+ **Note: Failure to bump the version will result in a failed release.**
@@ -0,0 +1,185 @@
1
+ # orchestra-cli
2
+
3
+ Orchestra CLI for working with Orchestra pipelines from your terminal.
4
+
5
+ Two entrypoints are available: `orchestra` and `orchestra-cli` (they are equivalent).
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install orchestra-cli
11
+ ```
12
+
13
+ Or with pipx:
14
+
15
+ ```bash
16
+ pipx install orchestra-cli
17
+ ```
18
+
19
+ ## Environment variables
20
+
21
+ - `ORCHESTRA_API_KEY`: Required for actions that call the API (`import`, `run`).
22
+ - `BASE_URL`: Optional. Override the default API base
23
+ (`https://app.getorchestra.io/api/engine/public/pipelines/{}`) for non‑production/testing.
24
+
25
+ ## Commands overview
26
+
27
+ - `validate`: Validate a pipeline YAML locally against the Orchestra API schema.
28
+ - `import`: Register a pipeline YAML (from a git repo) with Orchestra under an alias.
29
+ - `run`: Start a pipeline run by alias, optionally pinning branch/commit and waiting for completion.
30
+
31
+ Use `orchestra --help` or `orchestra <command> --help` for built-in help.
32
+
33
+ ---
34
+
35
+ ## validate
36
+
37
+ Validate a YAML file against the Orchestra API schema.
38
+
39
+ ```bash
40
+ orchestra validate path/to/pipeline.yaml
41
+ # or
42
+ orchestra-cli validate path/to/pipeline.yaml
43
+ ```
44
+
45
+ Options
46
+
47
+ - `file` (positional): Path to the YAML file to validate.
48
+
49
+ Behavior
50
+
51
+ - Prints a success message on valid input.
52
+ - On validation errors, prints the failing location(s), readable messages, and a YAML snippet when possible.
53
+ - Exit codes: `0` on success, `1` on invalid file/validation failure/HTTP error.
54
+
55
+ Example output (failure)
56
+
57
+ ```text
58
+ ❌ Validation failed with status 422
59
+ Error at: pipeline.tasks[0].type
60
+ Invalid task type "foo"
61
+
62
+ YAML snippet:
63
+ pipeline:
64
+ tasks:
65
+ - type: foo
66
+ ```
67
+
68
+ ---
69
+
70
+ ## import
71
+
72
+ Create (import) a pipeline in Orchestra by referencing a YAML file inside a git repository. The command infers your repository host/provider, default branch, and YAML path relative to the repo root.
73
+
74
+ ```bash
75
+ export ORCHESTRA_API_KEY=... # required
76
+
77
+ orchestra import \
78
+ --alias my-pipeline \
79
+ --path ./pipelines/pipeline.yaml \
80
+ --working-branch my-feature-branch # optional; defaults to current local branch
81
+ # or
82
+ orchestra-cli import -a my-pipeline -p ./pipelines/pipeline.yaml
83
+ ```
84
+
85
+ Options
86
+
87
+ - `-a, --alias` (required): The alias you want to register the pipeline under.
88
+ - `-p, --path` (required): Path to the YAML file. Must be inside a git repository.
89
+ - `-w, --working-branch` (optional): Branch to associate as the working branch for this pipeline import. If omitted, the current local git branch is used.
90
+
91
+ Notes
92
+
93
+ - The YAML is validated with the API before import; failures are printed clearly.
94
+ - Git details are detected automatically:
95
+ - Supported providers: GitHub, GitLab, Azure DevOps.
96
+ - The default branch is detected from `origin`.
97
+ - The working branch defaults to your current local branch when not specified.
98
+ - The YAML path is computed relative to the repository root.
99
+ - On success, the command prints the created pipeline ID (or a success message).
100
+ - Exit codes: `0` on success, `1` on failure.
101
+
102
+ Common errors
103
+
104
+ - Missing `ORCHESTRA_API_KEY`.
105
+ - Not running inside a git repository, or no `origin` remote configured.
106
+ - Could not detect storage provider or default branch.
107
+
108
+ ---
109
+
110
+ ## run
111
+
112
+ Start a pipeline run by alias. Optionally specify a branch and/or commit. By default, the command waits and polls the run status until completion.
113
+
114
+ ```bash
115
+ export ORCHESTRA_API_KEY=...
116
+
117
+ # Start and wait for completion
118
+ orchestra run --alias my-pipeline
119
+
120
+ # Start without waiting (prints run id and exits)
121
+ orchestra run -a my-pipeline --no-wait
122
+
123
+ # Start for a specific branch/commit
124
+ orchestra run -a my-pipeline -b feature/my-change -c 0123abc
125
+ ```
126
+
127
+ Options
128
+
129
+ - `-a, --alias` (required): Pipeline alias to run.
130
+ - `-b, --branch` (optional): Git branch name to associate with this run.
131
+ - `-c, --commit` (optional): Commit SHA to associate with this run.
132
+ - `--wait/--no-wait` (default: `--wait`): Poll until the run ends.
133
+ - `--force/--no-force` (default: `--no-force`): Skip confirmation if local git warnings are detected.
134
+
135
+ Behavior
136
+
137
+ - Prints the run ID when known and a link to the run lineage page.
138
+ - When waiting, polls status every ~5s until a terminal state:
139
+ - `SUCCEEDED` (exit `0`), `WARNING` (exit `0`), `SKIPPED` (exit `0`)
140
+ - `FAILED` or `CANCELLED` (exit `1`)
141
+ - When not waiting, exits after start and prints the run ID.
142
+
143
+ Non-interactive usage
144
+
145
+ - If your repo has warnings (e.g., uncommitted changes), the CLI prompts for confirmation unless `--force` is provided. For CI or scripts, pass `--force` or ensure a clean repo.
146
+
147
+ ---
148
+
149
+ ## Examples
150
+
151
+ ```bash
152
+ # Validate a pipeline file
153
+ orchestra validate ./examples/etl.yaml
154
+
155
+ # Import a pipeline and capture the created ID
156
+ PIPELINE_ID=$(orchestra import -a finance-etl -p ./pipelines/etl.yaml)
157
+
158
+ # Start a run and wait for completion
159
+ orchestra run -a finance-etl
160
+
161
+ # Start a run and exit immediately
162
+ orchestra run -a finance-etl --no-wait
163
+ ```
164
+
165
+ ---
166
+
167
+ ## Development
168
+
169
+ - Make sure [uv](https://github.com/astral-sh/uv) is installed
170
+ - Use `uv pip install -e ".[dev]"` to install the CLI in editable mode for development
171
+ - For local development, run `uv run orchestra` to start the CLI
172
+ - you can use `uv run --env-file .env ...` to run the CLI with env vars
173
+ - For testing, run `uv run pytest`
174
+ - For linting, run `uv run ruff check .`
175
+ - For formatting, run `uv run black --check .`
176
+ - For type checking, run `uv run pyright`
177
+
178
+ ## Building and Releasing
179
+
180
+ - Bump the version in `pyproject.toml` or by running `uv version --bump <major/minor/patch>`
181
+ - Run `uv sync` to install the dependencies
182
+ - Run `uv build` to build the CLI
183
+ - Run `uv publish` to publish the CLI (you will need to pass the `--token` flag)
184
+
185
+ **Note: Failure to bump the version will result in a failed release.**
@@ -0,0 +1,12 @@
1
+ import typer
2
+
3
+ from .import_pipeline import import_pipeline
4
+ from .run_pipeline import run_pipeline
5
+ from .validate_pipeline import validate
6
+
7
+ app = typer.Typer(help="Orchestra CLI – perform operations with Orchestra locally.")
8
+
9
+
10
+ app.command(name="validate")(validate)
11
+ app.command(name="import")(import_pipeline)
12
+ app.command(name="run")(run_pipeline)
@@ -0,0 +1,223 @@
1
+ import json
2
+ import os
3
+ import re
4
+ from pathlib import Path
5
+
6
+ import httpx
7
+ import typer
8
+ import yaml
9
+
10
+ from ..utils.constants import get_api_url
11
+ from ..utils.git import detect_repo_root, git_warnings, run_git_command
12
+ from ..utils.styling import bold, green, indent_message, red, yellow
13
+
14
+
15
+ def _detect_repository_url(repo_root: Path) -> str | None:
16
+ ok, remote = run_git_command(["remote", "get-url", "origin"], repo_root)
17
+ if not ok or not remote:
18
+ return None
19
+
20
+ # First, handle special cases like Azure URLs by removing segments like /_git/
21
+ cleaned_remote = re.sub(r"/(?:_git|scm|v3)(?=/)", "", remote.strip())
22
+
23
+ # A single regex can then capture the owner/repo from the cleaned URL
24
+ pattern = r".*[:/]([^/]+)/([^/]+?)(?:\.git)?/?$"
25
+
26
+ if match := re.search(pattern, cleaned_remote):
27
+ return f"{match.group(1)}/{match.group(2)}"
28
+
29
+ return None
30
+
31
+
32
+ def _get_remote_url(repo_root: Path) -> str | None:
33
+ """Return the raw git remote URL for origin without transformation."""
34
+ ok, remote = run_git_command(["remote", "get-url", "origin"], repo_root)
35
+ if not ok:
36
+ return None
37
+ return remote
38
+
39
+
40
+ def _detect_default_branch(repo_root: Path) -> str | None:
41
+ # Try symbolic-ref of remote HEAD first
42
+ ok, out = run_git_command(["symbolic-ref", "refs/remotes/origin/HEAD"], repo_root)
43
+ if ok and out:
44
+ # refs/remotes/origin/main -> main
45
+ return out.split("/")[-1]
46
+ # Fallback to parsing `git remote show origin`
47
+ ok, out = run_git_command(["remote", "show", "origin"], repo_root)
48
+ if ok and out:
49
+ m = re.search(r"HEAD branch:\s*(\S+)", out)
50
+ if m:
51
+ return m.group(1)
52
+ return None
53
+
54
+
55
+ def _detect_current_branch(repo_root: Path) -> str | None:
56
+ ok, out = run_git_command(["rev-parse", "--abbrev-ref", "HEAD"], repo_root)
57
+ if ok and out:
58
+ return out
59
+ return None
60
+
61
+
62
+ def _detect_storage_provider(repository_url: str | None) -> str:
63
+ if not repository_url:
64
+ typer.echo(red("Could not detect storage provider - no repository URL"))
65
+ raise typer.Exit(code=1)
66
+ url = repository_url.lower()
67
+ if "github.com" in url:
68
+ return "GITHUB"
69
+ if "gitlab.com" in url:
70
+ return "GITLAB"
71
+ if any(host in url for host in ["dev.azure.com", "azure.com", "visualstudio.com"]):
72
+ return "AZURE_DEVOPS"
73
+ typer.echo(red("Could not detect storage provider - no matching host"))
74
+ raise typer.Exit(code=1)
75
+
76
+
77
+ def _load_yaml(file: Path) -> tuple[dict | None, str | None]:
78
+ try:
79
+ with file.open("r") as f:
80
+ data = yaml.safe_load(f)
81
+ return data, None
82
+ except Exception as e:
83
+ return None, str(e)
84
+
85
+
86
+ def _validate_yaml_with_api(data: dict) -> tuple[bool, str | None]:
87
+ try:
88
+ response = httpx.post(get_api_url("schema"), json=data, timeout=15)
89
+ except Exception as e:
90
+ return False, f"HTTP request failed: {e}"
91
+
92
+ if response.status_code == 200:
93
+ return True, None
94
+ try:
95
+ errors = response.json()
96
+ return False, json.dumps(errors, indent=2)
97
+ except Exception:
98
+ return False, response.text
99
+
100
+
101
+ def import_pipeline(
102
+ alias: str = typer.Option(..., "--alias", "-a", help="Pipeline alias"),
103
+ path: Path = typer.Option(
104
+ ...,
105
+ "--path",
106
+ "-p",
107
+ exists=True,
108
+ file_okay=True,
109
+ dir_okay=False,
110
+ readable=True,
111
+ resolve_path=True,
112
+ help="Path to pipeline YAML inside a git repository",
113
+ ),
114
+ working_branch: str | None = typer.Option(
115
+ None,
116
+ "--working-branch",
117
+ "-w",
118
+ help="Git branch to use for the imported pipeline (defaults to current local branch)",
119
+ ),
120
+ ):
121
+ """
122
+ Create a pipeline in Orchestra by referencing a YAML file in your git repository.
123
+ """
124
+
125
+ if not path.exists():
126
+ typer.echo(red(f"File not found: {path}"))
127
+ raise typer.Exit(code=1)
128
+
129
+ api_key = os.getenv("ORCHESTRA_API_KEY")
130
+ if not api_key:
131
+ typer.echo(red("ORCHESTRA_API_KEY is not set"))
132
+ raise typer.Exit(code=1)
133
+
134
+ # Load and validate YAML with API first
135
+ data, err = _load_yaml(path)
136
+ if err is not None:
137
+ typer.echo(red(f"Invalid YAML: {err}"))
138
+ raise typer.Exit(code=1)
139
+
140
+ ok, err_msg = _validate_yaml_with_api(data or {})
141
+ if not ok:
142
+ typer.echo(red("❌ Validation failed"))
143
+ if err_msg:
144
+ typer.echo(yellow(indent_message(err_msg)))
145
+ raise typer.Exit(code=1)
146
+
147
+ # Detect git repository info
148
+ repo_root = detect_repo_root(path.parent)
149
+ if repo_root is None:
150
+ typer.echo(red("Not a git repository (could not detect repository root)"))
151
+ raise typer.Exit(code=1)
152
+
153
+ repository_slug = _detect_repository_url(repo_root)
154
+ if not repository_slug:
155
+ typer.echo(red("Could not detect repository URL from git"))
156
+ raise typer.Exit(code=1)
157
+ default_branch = _detect_default_branch(repo_root)
158
+ if not default_branch:
159
+ typer.echo(red("Could not detect default branch from git"))
160
+ raise typer.Exit(code=1)
161
+
162
+ # Determine working branch (explicit option or current branch)
163
+ if working_branch is None:
164
+ working_branch = _detect_current_branch(repo_root)
165
+ if not working_branch:
166
+ typer.echo(red("Could not detect current branch from git"))
167
+ raise typer.Exit(code=1)
168
+
169
+ # Compute YAML path relative to repo root
170
+ try:
171
+ yaml_path = str(path.resolve().relative_to(repo_root.resolve()))
172
+ except Exception:
173
+ typer.echo(red("YAML file must be inside the git repository"))
174
+ raise typer.Exit(code=1)
175
+
176
+ # Show warnings
177
+ for w in git_warnings(repo_root):
178
+ typer.echo(yellow(f"⚠ {w}"))
179
+
180
+ payload = {
181
+ "storage_provider": _detect_storage_provider(_get_remote_url(repo_root)),
182
+ "repository": repository_slug,
183
+ "default_branch": default_branch,
184
+ "working_branch": working_branch,
185
+ "yaml_path": yaml_path,
186
+ "alias": alias,
187
+ }
188
+
189
+ try:
190
+ response = httpx.post(
191
+ get_api_url("import"),
192
+ json=payload,
193
+ timeout=30,
194
+ headers={"Authorization": f"Bearer {api_key}"},
195
+ )
196
+ except Exception as e:
197
+ typer.echo(red(f"HTTP request failed: {e}"))
198
+ raise typer.Exit(code=1)
199
+
200
+ if response.status_code == 201:
201
+ try:
202
+ body = response.json()
203
+ except Exception:
204
+ body = {}
205
+ pipeline_id = body.get("id")
206
+ if pipeline_id:
207
+ # Print only the pipeline id as requested
208
+ typer.echo(f"Pipeline with alias '{alias}' imported successfully: {pipeline_id}")
209
+ raise typer.Exit(code=0)
210
+ else:
211
+ # Fallback if server does not return a field we expect
212
+ typer.echo(green("✅ Pipeline imported successfully"))
213
+ if body:
214
+ typer.echo(bold(json.dumps(body)))
215
+ raise typer.Exit(code=0)
216
+
217
+ # Error handling for non-201 responses
218
+ typer.echo(red(f"❌ Import failed with status {response.status_code}"))
219
+ try:
220
+ typer.echo(yellow(indent_message(json.dumps(response.json(), indent=2))))
221
+ except Exception:
222
+ typer.echo(yellow(indent_message(response.text)))
223
+ raise typer.Exit(code=1)