orchestra-cli 0.1.3__tar.gz → 0.3.0__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 (35) hide show
  1. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/PKG-INFO +107 -4
  2. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/README.md +106 -3
  3. orchestra_cli-0.3.0/orchestra_cli/src/cli.py +20 -0
  4. orchestra_cli-0.3.0/orchestra_cli/src/create_pipeline.py +65 -0
  5. orchestra_cli-0.3.0/orchestra_cli/src/delete_pipeline.py +39 -0
  6. orchestra_cli-0.3.0/orchestra_cli/src/fetch_pipelines.py +45 -0
  7. orchestra_cli-0.3.0/orchestra_cli/src/pipeline_upsert.py +74 -0
  8. orchestra_cli-0.3.0/orchestra_cli/src/update_pipeline.py +65 -0
  9. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/orchestra_cli/tests/test_cli.py +4 -0
  10. orchestra_cli-0.3.0/orchestra_cli/tests/test_create_pipeline.py +177 -0
  11. orchestra_cli-0.3.0/orchestra_cli/tests/test_delete_pipeline.py +82 -0
  12. orchestra_cli-0.3.0/orchestra_cli/tests/test_fetch_pipelines.py +78 -0
  13. orchestra_cli-0.3.0/orchestra_cli/tests/test_update_pipeline.py +176 -0
  14. orchestra_cli-0.3.0/orchestra_cli/utils/constants.py +32 -0
  15. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/orchestra_cli.egg-info/PKG-INFO +107 -4
  16. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/orchestra_cli.egg-info/SOURCES.txt +9 -0
  17. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/pyproject.toml +1 -1
  18. orchestra_cli-0.1.3/orchestra_cli/src/cli.py +0 -12
  19. orchestra_cli-0.1.3/orchestra_cli/utils/constants.py +0 -7
  20. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/orchestra_cli/src/__init__.py +0 -0
  21. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/orchestra_cli/src/import_pipeline.py +0 -0
  22. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/orchestra_cli/src/run_pipeline.py +0 -0
  23. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/orchestra_cli/src/validate_pipeline.py +0 -0
  24. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/orchestra_cli/tests/__init__.py +0 -0
  25. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/orchestra_cli/tests/conftest.py +0 -0
  26. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/orchestra_cli/tests/test_import_pipeline.py +0 -0
  27. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/orchestra_cli/tests/test_run_pipeline.py +0 -0
  28. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/orchestra_cli/tests/test_validate_pipeline.py +0 -0
  29. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/orchestra_cli/utils/git.py +0 -0
  30. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/orchestra_cli/utils/styling.py +0 -0
  31. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/orchestra_cli.egg-info/dependency_links.txt +0 -0
  32. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/orchestra_cli.egg-info/entry_points.txt +0 -0
  33. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/orchestra_cli.egg-info/requires.txt +0 -0
  34. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/orchestra_cli.egg-info/top_level.txt +0 -0
  35. {orchestra_cli-0.1.3 → orchestra_cli-0.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orchestra-cli
3
- Version: 0.1.3
3
+ Version: 0.3.0
4
4
  Summary: Orchestra CLI. Perform operations with Orchestra locally.
5
5
  Author-email: Orchestra Technologies <support@getorchestra.io>
6
6
  Requires-Python: >=3.10
@@ -15,6 +15,8 @@ Orchestra CLI for working with Orchestra pipelines from your terminal.
15
15
 
16
16
  Two entrypoints are available: `orchestra` and `orchestra-cli` (they are equivalent).
17
17
 
18
+ See [`AGENTS.md`](AGENTS.md) for contributor and AI agent guidance (environment setup, code conventions, testing).
19
+
18
20
  ## Installation
19
21
 
20
22
  ```bash
@@ -29,14 +31,17 @@ pipx install orchestra-cli
29
31
 
30
32
  ## Environment variables
31
33
 
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.
34
+ - `ORCHESTRA_API_KEY`: Required for actions that call the API (`import`, `create-pipeline`, `update-pipeline`, `delete-pipeline`, `run`).
35
+ - `BASE_URL`: Optional. Override the default Orchestra host (`https://app.getorchestra.io`) for non‑production/testing.
35
36
 
36
37
  ## Commands overview
37
38
 
38
39
  - `validate`: Validate a pipeline YAML locally against the Orchestra API schema.
39
40
  - `import`: Register a pipeline YAML (from a git repo) with Orchestra under an alias.
41
+ - `fetch-pipelines`: Fetch the pipelines visible to the current API key as JSON.
42
+ - `create-pipeline`: Create an Orchestra-backed pipeline from a local YAML file.
43
+ - `update-pipeline`: Update an existing Orchestra-backed pipeline from a local YAML file.
44
+ - `delete-pipeline`: Delete an existing pipeline by alias.
40
45
  - `run`: Start a pipeline run by alias, optionally pinning branch/commit and waiting for completion.
41
46
 
42
47
  Use `orchestra --help` or `orchestra <command> --help` for built-in help.
@@ -118,6 +123,104 @@ Common errors
118
123
 
119
124
  ---
120
125
 
126
+ ## create-pipeline
127
+
128
+ Create an Orchestra-backed pipeline directly from a local YAML file.
129
+
130
+ ```bash
131
+ export ORCHESTRA_API_KEY=...
132
+
133
+ orchestra create-pipeline \
134
+ --alias my-pipeline \
135
+ --path ./pipelines/pipeline.yaml \
136
+ --publish # optional, defaults to --no-publish
137
+ ```
138
+
139
+ Options
140
+
141
+ - `-a, --alias` (required): Pipeline alias.
142
+ - `-p, --path` (required): Path to pipeline YAML file.
143
+ - `--publish/--no-publish` (optional, default `--no-publish`): Whether the pipeline is published.
144
+
145
+ Behavior
146
+
147
+ - Validates YAML against the Orchestra schema endpoint before creating.
148
+ - Sends pipeline data to `POST /pipelines` with `storage_provider=ORCHESTRA`.
149
+ - On success, prints the pipeline edit URL (`/pipelines/{pipeline_id}/edit`) when an ID is returned.
150
+ - Exit codes: `0` on success, `1` on failure.
151
+
152
+ ---
153
+
154
+ ## fetch-pipelines
155
+
156
+ Fetch the pipelines available to the current Orchestra API key.
157
+
158
+ ```bash
159
+ export ORCHESTRA_API_KEY=...
160
+
161
+ orchestra fetch-pipelines
162
+ ```
163
+
164
+ Behavior
165
+
166
+ - Sends `GET /api/engine/public/pipelines`. Responses always include each pipeline's latest run metadata.
167
+ - Prints the response payload as pretty JSON for scripting and inspection.
168
+ - Exit codes: `0` on success, `1` on failure.
169
+
170
+ ---
171
+
172
+ ## update-pipeline
173
+
174
+ Update an existing Orchestra-backed pipeline from a local YAML file.
175
+
176
+ ```bash
177
+ export ORCHESTRA_API_KEY=...
178
+
179
+ orchestra update-pipeline \
180
+ --alias my-pipeline \
181
+ --path ./pipelines/pipeline.yaml \
182
+ --no-publish # default
183
+ ```
184
+
185
+ Options
186
+
187
+ - `-a, --alias` (required): Pipeline alias to update.
188
+ - `-p, --path` (required): Path to pipeline YAML file.
189
+ - `--publish/--no-publish` (optional, default `--no-publish`): Whether the pipeline is published.
190
+
191
+ Behavior
192
+
193
+ - Validates YAML against the Orchestra schema endpoint before updating.
194
+ - Sends pipeline data to `PUT /pipelines/{alias}` with `storage_provider=ORCHESTRA`.
195
+ - Only Orchestra-backed pipelines can be updated via this endpoint (Git-backed pipelines are rejected).
196
+ - On success, prints the pipeline edit URL (`/pipelines/{pipeline_id}/edit`) when an ID is returned.
197
+ - Exit codes: `0` on success, `1` on failure.
198
+
199
+ ---
200
+
201
+ ## delete-pipeline
202
+
203
+ Delete a pipeline by alias.
204
+
205
+ ```bash
206
+ export ORCHESTRA_API_KEY=...
207
+
208
+ orchestra delete-pipeline --alias my-pipeline
209
+ ```
210
+
211
+ Options
212
+
213
+ - `-a, --alias` (required): Pipeline alias to delete.
214
+
215
+ Behavior
216
+
217
+ - Sends `DELETE /pipelines/{alias}` with API key authentication.
218
+ - Deletes the pipeline resolved by alias within the authenticated account.
219
+ - On success, exits with code `0` after printing a confirmation message.
220
+ - Exit codes: `0` on success, `1` on failure.
221
+
222
+ ---
223
+
121
224
  ## run
122
225
 
123
226
  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.
@@ -4,6 +4,8 @@ Orchestra CLI for working with Orchestra pipelines from your terminal.
4
4
 
5
5
  Two entrypoints are available: `orchestra` and `orchestra-cli` (they are equivalent).
6
6
 
7
+ See [`AGENTS.md`](AGENTS.md) for contributor and AI agent guidance (environment setup, code conventions, testing).
8
+
7
9
  ## Installation
8
10
 
9
11
  ```bash
@@ -18,14 +20,17 @@ pipx install orchestra-cli
18
20
 
19
21
  ## Environment variables
20
22
 
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.
23
+ - `ORCHESTRA_API_KEY`: Required for actions that call the API (`import`, `create-pipeline`, `update-pipeline`, `delete-pipeline`, `run`).
24
+ - `BASE_URL`: Optional. Override the default Orchestra host (`https://app.getorchestra.io`) for non‑production/testing.
24
25
 
25
26
  ## Commands overview
26
27
 
27
28
  - `validate`: Validate a pipeline YAML locally against the Orchestra API schema.
28
29
  - `import`: Register a pipeline YAML (from a git repo) with Orchestra under an alias.
30
+ - `fetch-pipelines`: Fetch the pipelines visible to the current API key as JSON.
31
+ - `create-pipeline`: Create an Orchestra-backed pipeline from a local YAML file.
32
+ - `update-pipeline`: Update an existing Orchestra-backed pipeline from a local YAML file.
33
+ - `delete-pipeline`: Delete an existing pipeline by alias.
29
34
  - `run`: Start a pipeline run by alias, optionally pinning branch/commit and waiting for completion.
30
35
 
31
36
  Use `orchestra --help` or `orchestra <command> --help` for built-in help.
@@ -107,6 +112,104 @@ Common errors
107
112
 
108
113
  ---
109
114
 
115
+ ## create-pipeline
116
+
117
+ Create an Orchestra-backed pipeline directly from a local YAML file.
118
+
119
+ ```bash
120
+ export ORCHESTRA_API_KEY=...
121
+
122
+ orchestra create-pipeline \
123
+ --alias my-pipeline \
124
+ --path ./pipelines/pipeline.yaml \
125
+ --publish # optional, defaults to --no-publish
126
+ ```
127
+
128
+ Options
129
+
130
+ - `-a, --alias` (required): Pipeline alias.
131
+ - `-p, --path` (required): Path to pipeline YAML file.
132
+ - `--publish/--no-publish` (optional, default `--no-publish`): Whether the pipeline is published.
133
+
134
+ Behavior
135
+
136
+ - Validates YAML against the Orchestra schema endpoint before creating.
137
+ - Sends pipeline data to `POST /pipelines` with `storage_provider=ORCHESTRA`.
138
+ - On success, prints the pipeline edit URL (`/pipelines/{pipeline_id}/edit`) when an ID is returned.
139
+ - Exit codes: `0` on success, `1` on failure.
140
+
141
+ ---
142
+
143
+ ## fetch-pipelines
144
+
145
+ Fetch the pipelines available to the current Orchestra API key.
146
+
147
+ ```bash
148
+ export ORCHESTRA_API_KEY=...
149
+
150
+ orchestra fetch-pipelines
151
+ ```
152
+
153
+ Behavior
154
+
155
+ - Sends `GET /api/engine/public/pipelines`. Responses always include each pipeline's latest run metadata.
156
+ - Prints the response payload as pretty JSON for scripting and inspection.
157
+ - Exit codes: `0` on success, `1` on failure.
158
+
159
+ ---
160
+
161
+ ## update-pipeline
162
+
163
+ Update an existing Orchestra-backed pipeline from a local YAML file.
164
+
165
+ ```bash
166
+ export ORCHESTRA_API_KEY=...
167
+
168
+ orchestra update-pipeline \
169
+ --alias my-pipeline \
170
+ --path ./pipelines/pipeline.yaml \
171
+ --no-publish # default
172
+ ```
173
+
174
+ Options
175
+
176
+ - `-a, --alias` (required): Pipeline alias to update.
177
+ - `-p, --path` (required): Path to pipeline YAML file.
178
+ - `--publish/--no-publish` (optional, default `--no-publish`): Whether the pipeline is published.
179
+
180
+ Behavior
181
+
182
+ - Validates YAML against the Orchestra schema endpoint before updating.
183
+ - Sends pipeline data to `PUT /pipelines/{alias}` with `storage_provider=ORCHESTRA`.
184
+ - Only Orchestra-backed pipelines can be updated via this endpoint (Git-backed pipelines are rejected).
185
+ - On success, prints the pipeline edit URL (`/pipelines/{pipeline_id}/edit`) when an ID is returned.
186
+ - Exit codes: `0` on success, `1` on failure.
187
+
188
+ ---
189
+
190
+ ## delete-pipeline
191
+
192
+ Delete a pipeline by alias.
193
+
194
+ ```bash
195
+ export ORCHESTRA_API_KEY=...
196
+
197
+ orchestra delete-pipeline --alias my-pipeline
198
+ ```
199
+
200
+ Options
201
+
202
+ - `-a, --alias` (required): Pipeline alias to delete.
203
+
204
+ Behavior
205
+
206
+ - Sends `DELETE /pipelines/{alias}` with API key authentication.
207
+ - Deletes the pipeline resolved by alias within the authenticated account.
208
+ - On success, exits with code `0` after printing a confirmation message.
209
+ - Exit codes: `0` on success, `1` on failure.
210
+
211
+ ---
212
+
110
213
  ## run
111
214
 
112
215
  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.
@@ -0,0 +1,20 @@
1
+ import typer
2
+
3
+ from .create_pipeline import create_pipeline
4
+ from .delete_pipeline import delete_pipeline
5
+ from .fetch_pipelines import fetch_pipelines
6
+ from .import_pipeline import import_pipeline
7
+ from .run_pipeline import run_pipeline
8
+ from .update_pipeline import update_pipeline
9
+ from .validate_pipeline import validate
10
+
11
+ app = typer.Typer(help="Orchestra CLI – perform operations with Orchestra locally.")
12
+
13
+
14
+ app.command(name="validate")(validate)
15
+ app.command(name="import")(import_pipeline)
16
+ app.command(name="run")(run_pipeline)
17
+ app.command(name="fetch-pipelines")(fetch_pipelines)
18
+ app.command(name="create-pipeline")(create_pipeline)
19
+ app.command(name="update-pipeline")(update_pipeline)
20
+ app.command(name="delete-pipeline")(delete_pipeline)
@@ -0,0 +1,65 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+ import httpx
5
+ import typer
6
+
7
+ from ..utils.constants import get_create_pipeline_url
8
+ from ..utils.styling import indent_message, red, yellow
9
+ from .pipeline_upsert import (
10
+ build_upsert_payload,
11
+ emit_success_with_edit_url,
12
+ load_validated_pipeline_data,
13
+ require_api_key,
14
+ require_pipeline_id_from_success_response,
15
+ )
16
+
17
+
18
+ def create_pipeline(
19
+ alias: str = typer.Option(..., "--alias", "-a", help="Pipeline alias"),
20
+ path: Path = typer.Option(
21
+ ...,
22
+ "--path",
23
+ "-p",
24
+ exists=True,
25
+ file_okay=True,
26
+ dir_okay=False,
27
+ readable=True,
28
+ resolve_path=True,
29
+ help="Path to pipeline YAML",
30
+ ),
31
+ publish: bool = typer.Option(
32
+ False,
33
+ "--publish/--no-publish",
34
+ help="Whether the pipeline is published and can be triggered",
35
+ ),
36
+ ):
37
+ """
38
+ Create an Orchestra-backed pipeline from a local YAML file.
39
+ """
40
+ api_key = require_api_key()
41
+ data = load_validated_pipeline_data(path)
42
+ payload = build_upsert_payload(data, publish, alias=alias)
43
+
44
+ try:
45
+ response = httpx.post(
46
+ get_create_pipeline_url(),
47
+ json=payload,
48
+ timeout=30,
49
+ headers={"Authorization": f"Bearer {api_key}"},
50
+ )
51
+ except Exception as e:
52
+ typer.echo(red(f"HTTP request failed: {e}"))
53
+ raise typer.Exit(code=1)
54
+
55
+ if response.status_code == 201:
56
+ pipeline_id = require_pipeline_id_from_success_response(response, "Create")
57
+ emit_success_with_edit_url(alias, "created", pipeline_id)
58
+ raise typer.Exit(code=0)
59
+
60
+ typer.echo(red(f"❌ Create failed with status {response.status_code}"))
61
+ try:
62
+ typer.echo(yellow(indent_message(json.dumps(response.json(), indent=2))))
63
+ except Exception:
64
+ typer.echo(yellow(indent_message(response.text)))
65
+ raise typer.Exit(code=1)
@@ -0,0 +1,39 @@
1
+ import json
2
+
3
+ import httpx
4
+ import typer
5
+
6
+ from ..utils.constants import get_delete_pipeline_url
7
+ from ..utils.styling import green, indent_message, red, yellow
8
+ from .pipeline_upsert import require_api_key
9
+
10
+
11
+ def delete_pipeline(
12
+ alias: str = typer.Option(..., "--alias", "-a", help="Pipeline alias"),
13
+ ):
14
+ """
15
+ Delete a pipeline by alias.
16
+ """
17
+ api_key = require_api_key()
18
+
19
+ try:
20
+ response = httpx.delete(
21
+ get_delete_pipeline_url(alias),
22
+ timeout=30,
23
+ headers={"Authorization": f"Bearer {api_key}"},
24
+ )
25
+ except Exception as e:
26
+ typer.echo(red(f"HTTP request failed: {e}"))
27
+ raise typer.Exit(code=1)
28
+
29
+ if response.status_code == 204:
30
+ typer.echo(green(f"✅ Pipeline '{alias}' deleted successfully"))
31
+ raise typer.Exit(code=0)
32
+
33
+ typer.echo(red(f"❌ Delete failed with status {response.status_code}"))
34
+ try:
35
+ typer.echo(yellow(indent_message(json.dumps(response.json(), indent=2))))
36
+ except Exception:
37
+ if response.text:
38
+ typer.echo(yellow(indent_message(response.text)))
39
+ raise typer.Exit(code=1)
@@ -0,0 +1,45 @@
1
+ import json
2
+
3
+ import httpx
4
+ import typer
5
+
6
+ from ..utils.constants import get_api_url
7
+ from ..utils.styling import indent_message, red, yellow
8
+ from .pipeline_upsert import require_api_key
9
+
10
+
11
+ def fetch_pipelines():
12
+ """
13
+ Fetch pipelines available to the current Orchestra API key.
14
+
15
+ The API always includes each pipeline's latest run metadata.
16
+ """
17
+ api_key = require_api_key()
18
+
19
+ try:
20
+ response = httpx.get(
21
+ get_api_url(""),
22
+ timeout=30,
23
+ headers={"Authorization": f"Bearer {api_key}"},
24
+ )
25
+ except Exception as e:
26
+ typer.echo(red(f"HTTP request failed: {e}"))
27
+ raise typer.Exit(code=1)
28
+
29
+ if response.status_code == 200:
30
+ try:
31
+ pipelines = response.json()
32
+ except Exception:
33
+ typer.echo(red("❌ Fetch pipelines failed: success response was not valid JSON"))
34
+ typer.echo(yellow(indent_message(response.text)))
35
+ raise typer.Exit(code=1)
36
+
37
+ typer.echo(json.dumps(pipelines, indent=2))
38
+ raise typer.Exit(code=0)
39
+
40
+ typer.echo(red(f"❌ Fetch pipelines failed with status {response.status_code}"))
41
+ try:
42
+ typer.echo(yellow(indent_message(json.dumps(response.json(), indent=2))))
43
+ except Exception:
44
+ typer.echo(yellow(indent_message(response.text)))
45
+ raise typer.Exit(code=1)
@@ -0,0 +1,74 @@
1
+ import json
2
+ import os
3
+ from pathlib import Path
4
+
5
+ import httpx
6
+ import typer
7
+
8
+ from ..utils.constants import get_pipeline_edit_url
9
+ from ..utils.styling import green, indent_message, red, yellow
10
+ from .import_pipeline import _load_yaml, _validate_yaml_with_api
11
+
12
+
13
+ def require_api_key() -> str:
14
+ api_key = os.getenv("ORCHESTRA_API_KEY")
15
+ if not api_key:
16
+ typer.echo(red("ORCHESTRA_API_KEY is not set"))
17
+ raise typer.Exit(code=1)
18
+ return api_key
19
+
20
+
21
+ def load_validated_pipeline_data(path: Path) -> dict:
22
+ if not path.exists():
23
+ typer.echo(red(f"File not found: {path}"))
24
+ raise typer.Exit(code=1)
25
+
26
+ data, err = _load_yaml(path)
27
+ if err is not None:
28
+ typer.echo(red(f"Invalid YAML: {err}"))
29
+ raise typer.Exit(code=1)
30
+
31
+ ok, err_msg = _validate_yaml_with_api(data or {})
32
+ if not ok:
33
+ typer.echo(red("❌ Validation failed"))
34
+ if err_msg:
35
+ typer.echo(yellow(indent_message(err_msg)))
36
+ raise typer.Exit(code=1)
37
+
38
+ return data or {}
39
+
40
+
41
+ def build_upsert_payload(data: dict, publish: bool, alias: str | None = None) -> dict:
42
+ payload: dict[str, object] = {
43
+ "data": data,
44
+ "published": publish,
45
+ "storage_provider": "ORCHESTRA",
46
+ }
47
+ if alias is not None:
48
+ payload["alias"] = alias
49
+ return payload
50
+
51
+
52
+ def require_pipeline_id_from_success_response(
53
+ response: httpx.Response,
54
+ action: str,
55
+ ) -> str:
56
+ try:
57
+ body = response.json()
58
+ except Exception:
59
+ typer.echo(red(f"❌ {action} failed: success response was not valid JSON"))
60
+ typer.echo(yellow(indent_message(response.text)))
61
+ raise typer.Exit(code=1)
62
+
63
+ pipeline_id = body.get("id")
64
+ if not pipeline_id:
65
+ typer.echo(red(f"❌ {action} failed: success response did not include pipeline id"))
66
+ typer.echo(yellow(indent_message(json.dumps(body, indent=2))))
67
+ raise typer.Exit(code=1)
68
+
69
+ return str(pipeline_id)
70
+
71
+
72
+ def emit_success_with_edit_url(alias: str, action: str, pipeline_id: str) -> None:
73
+ typer.echo(green(f"✅ Pipeline '{alias}' {action} successfully: {pipeline_id}"))
74
+ typer.echo(yellow(f"Edit URL: {get_pipeline_edit_url(pipeline_id)}"))
@@ -0,0 +1,65 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+ import httpx
5
+ import typer
6
+
7
+ from ..utils.constants import get_update_pipeline_url
8
+ from ..utils.styling import indent_message, red, yellow
9
+ from .pipeline_upsert import (
10
+ build_upsert_payload,
11
+ emit_success_with_edit_url,
12
+ load_validated_pipeline_data,
13
+ require_api_key,
14
+ require_pipeline_id_from_success_response,
15
+ )
16
+
17
+
18
+ def update_pipeline(
19
+ alias: str = typer.Option(..., "--alias", "-a", help="Pipeline alias"),
20
+ path: Path = typer.Option(
21
+ ...,
22
+ "--path",
23
+ "-p",
24
+ exists=True,
25
+ file_okay=True,
26
+ dir_okay=False,
27
+ readable=True,
28
+ resolve_path=True,
29
+ help="Path to pipeline YAML",
30
+ ),
31
+ publish: bool = typer.Option(
32
+ False,
33
+ "--publish/--no-publish",
34
+ help="Whether the pipeline is published and can be triggered",
35
+ ),
36
+ ):
37
+ """
38
+ Update an Orchestra-backed pipeline from a local YAML file.
39
+ """
40
+ api_key = require_api_key()
41
+ data = load_validated_pipeline_data(path)
42
+ payload = build_upsert_payload(data, publish)
43
+
44
+ try:
45
+ response = httpx.put(
46
+ get_update_pipeline_url(alias),
47
+ json=payload,
48
+ timeout=30,
49
+ headers={"Authorization": f"Bearer {api_key}"},
50
+ )
51
+ except Exception as e:
52
+ typer.echo(red(f"HTTP request failed: {e}"))
53
+ raise typer.Exit(code=1)
54
+
55
+ if response.status_code == 200:
56
+ pipeline_id = require_pipeline_id_from_success_response(response, "Update")
57
+ emit_success_with_edit_url(alias, "updated", pipeline_id)
58
+ raise typer.Exit(code=0)
59
+
60
+ typer.echo(red(f"❌ Update failed with status {response.status_code}"))
61
+ try:
62
+ typer.echo(yellow(indent_message(json.dumps(response.json(), indent=2))))
63
+ except Exception:
64
+ typer.echo(yellow(indent_message(response.text)))
65
+ raise typer.Exit(code=1)
@@ -10,3 +10,7 @@ def test_help():
10
10
  assert result.exit_code == 0
11
11
  assert "Orchestra CLI" in result.output
12
12
  assert "validate" in result.output
13
+ assert "fetch-pipelines" in result.output
14
+ assert "create-pipeline" in result.output
15
+ assert "delete-pipeline" in result.output
16
+ assert "update-pipeline" in result.output