orchestra-cli 0.3.5__tar.gz → 0.5.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 (63) hide show
  1. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/PKG-INFO +58 -3
  2. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/README.md +57 -2
  3. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/src/build_pipeline.py +20 -16
  4. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/src/cli.py +8 -0
  5. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/src/create_pipeline.py +7 -7
  6. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/src/delete_pipeline.py +7 -7
  7. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/src/fetch_pipelines.py +7 -8
  8. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/src/get_pipeline.py +7 -7
  9. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/src/import_pipeline.py +7 -7
  10. orchestra_cli-0.5.0/orchestra_cli/src/list_accounts.py +16 -0
  11. orchestra_cli-0.5.0/orchestra_cli/src/login.py +208 -0
  12. orchestra_cli-0.5.0/orchestra_cli/src/login_page.html +42 -0
  13. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/src/migrate_pipeline.py +19 -11
  14. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/src/run_pipeline.py +31 -30
  15. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/src/task_logs.py +24 -18
  16. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/src/update_pipeline.py +8 -8
  17. orchestra_cli-0.5.0/orchestra_cli/src/use_account.py +61 -0
  18. orchestra_cli-0.5.0/orchestra_cli/tests/conftest.py +41 -0
  19. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/tests/test_delete_pipeline.py +4 -15
  20. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/tests/test_fetch_pipelines.py +41 -0
  21. orchestra_cli-0.5.0/orchestra_cli/tests/test_list_accounts.py +84 -0
  22. orchestra_cli-0.5.0/orchestra_cli/tests/test_login.py +316 -0
  23. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/tests/test_run_pipeline.py +126 -0
  24. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/tests/test_task_logs.py +34 -0
  25. orchestra_cli-0.5.0/orchestra_cli/tests/test_use_account.py +214 -0
  26. orchestra_cli-0.5.0/orchestra_cli/tests/utils/test_api.py +229 -0
  27. orchestra_cli-0.5.0/orchestra_cli/tests/utils/test_picker.py +45 -0
  28. orchestra_cli-0.5.0/orchestra_cli/utils/accounts.py +44 -0
  29. orchestra_cli-0.5.0/orchestra_cli/utils/api.py +199 -0
  30. orchestra_cli-0.5.0/orchestra_cli/utils/credentials.py +49 -0
  31. orchestra_cli-0.5.0/orchestra_cli/utils/picker.py +58 -0
  32. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/utils/pipeline_lookup.py +3 -5
  33. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli.egg-info/PKG-INFO +58 -3
  34. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli.egg-info/SOURCES.txt +12 -0
  35. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/pyproject.toml +4 -1
  36. orchestra_cli-0.3.5/orchestra_cli/tests/conftest.py +0 -14
  37. orchestra_cli-0.3.5/orchestra_cli/utils/api.py +0 -65
  38. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/src/__init__.py +0 -0
  39. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/src/pipeline_upsert.py +0 -0
  40. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/src/validate_pipeline.py +0 -0
  41. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/tests/__init__.py +0 -0
  42. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/tests/test_build_pipeline.py +0 -0
  43. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/tests/test_cli.py +0 -0
  44. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/tests/test_create_pipeline.py +0 -0
  45. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/tests/test_get_pipeline.py +0 -0
  46. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/tests/test_import_pipeline.py +0 -0
  47. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/tests/test_migrate_pipeline.py +0 -0
  48. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/tests/test_pipeline_selector.py +0 -0
  49. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/tests/test_update_pipeline.py +0 -0
  50. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/tests/test_validate_pipeline.py +0 -0
  51. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/tests/utils/__init__.py +0 -0
  52. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/tests/utils/test_git.py +0 -0
  53. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/utils/constants.py +0 -0
  54. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/utils/git.py +0 -0
  55. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/utils/pipeline_selector.py +0 -0
  56. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/utils/pipeline_update.py +0 -0
  57. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/utils/styling.py +0 -0
  58. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli/utils/yaml_loader.py +0 -0
  59. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli.egg-info/dependency_links.txt +0 -0
  60. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli.egg-info/entry_points.txt +0 -0
  61. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli.egg-info/requires.txt +0 -0
  62. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/orchestra_cli.egg-info/top_level.txt +0 -0
  63. {orchestra_cli-0.3.5 → orchestra_cli-0.5.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orchestra-cli
3
- Version: 0.3.5
3
+ Version: 0.5.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
@@ -34,15 +34,17 @@ pipx install orchestra-cli
34
34
 
35
35
  ## Environment variables
36
36
 
37
- - `ORCHESTRA_API_KEY`: Required for actions that call the API (`pipeline import`, `pipeline new`, `pipeline update`, `pipeline migrate`, `pipeline get`, `pipeline list`, `pipeline delete`, `pipeline run`, `pipeline build`).
37
+ - `ORCHESTRA_API_KEY`: Credential for actions that call the API (`pipeline import`, `pipeline new`, `pipeline update`, `pipeline migrate`, `pipeline get`, `pipeline list`, `pipeline delete`, `pipeline run`, `pipeline build`, `accounts list`). Not needed once you have run `orchestra login`: a login takes precedence, and the key is used only when you are not logged in or your login has expired.
38
+ - `ORCHESTRA_ACCOUNT_ID`: Optional. The workspace to act in when your `orchestra login` covers several. Every `pipeline` and `task` command that calls the API also takes `--account-id`, which wins over the variable, and both win over the default saved by `orchestra accounts use`. API keys belong to one account and don't need it.
38
39
  - `BASE_URL`: Optional. Override the default Orchestra host (`https://app.getorchestra.io`) for non‑production/testing.
39
40
 
40
41
  ## Command structure
41
42
 
42
- Commands follow a `noun verb` shape. The current nouns are `pipeline` and `task`:
43
+ Commands follow a `noun verb` shape. The current nouns are `pipeline`, `task` and `accounts`:
43
44
 
44
45
  | Command | Description |
45
46
  | ------------------------------------ | ------------------------------------------------------------------------------------------- |
47
+ | `orchestra login` | Log in through your browser so later commands need no API key. |
46
48
  | `orchestra pipeline validate <file>` | Validate a pipeline YAML locally against the Orchestra API schema. |
47
49
  | `orchestra pipeline import` | Register a pipeline YAML (from a git repo) with Orchestra under an alias. |
48
50
  | `orchestra pipeline get` | Fetch one pipeline by path, alias, or pipeline ID. |
@@ -54,6 +56,8 @@ Commands follow a `noun verb` shape. The current nouns are `pipeline` and `task`
54
56
  | `orchestra pipeline run` | Start a pipeline run by selector, optionally pinning branch/commit and waiting. |
55
57
  | `orchestra pipeline build` | Validate local YAML, create or update a draft pipeline, and start that draft version. |
56
58
  | `orchestra task logs` | Fetch or follow logs for a single task run. |
59
+ | `orchestra accounts list` | List the workspaces your login or API key covers, with their ids, as JSON. |
60
+ | `orchestra accounts use [id\|name]` | Save the workspace later commands act in when no `--account-id` is given. |
57
61
 
58
62
  Commands that read a local pipeline file (`pipeline validate`, `import`, `new`, `update`, `build`, and `run --path` with `--task`) accept `.yaml`, `.yml` and `.oml` files; any other extension is rejected. `.oml` files are read as YAML, so only OML written in YAML or JSON syntax is supported. OML block terminators (`.`) and embeds (`%sql ... %%`) fail with an `Invalid YAML` error.
59
63
 
@@ -112,6 +116,20 @@ pipeline:
112
116
 
113
117
  ---
114
118
 
119
+ ## login
120
+
121
+ Log in through your browser instead of pasting an API key. The token is cached in `~/.orchestra/credentials.json` (readable only by you) and refreshed automatically, so later commands need no `ORCHESTRA_API_KEY`.
122
+
123
+ ```bash
124
+ orchestra login
125
+ ```
126
+
127
+ Logins are per host: a login made with `BASE_URL` pointing at another environment is only used against that environment. CI and scripts, where nobody is logged in, keep using `ORCHESTRA_API_KEY`.
128
+
129
+ After logging in it saves a default workspace, as `accounts use` does: the only one if your login covers one, or the one you pick from a list if it covers several. Esc skips the default, and without a terminal it prints a hint to run `orchestra accounts use` instead. If the accounts can't be listed, the login still succeeds with no default set.
130
+
131
+ ---
132
+
115
133
  ## pipeline import
116
134
 
117
135
  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.
@@ -400,6 +418,43 @@ Behavior
400
418
 
401
419
  ---
402
420
 
421
+ ## accounts list
422
+
423
+ List the workspaces the current credential covers. An `orchestra login` token can cover several workspaces; an API key lists just its own.
424
+
425
+ ```bash
426
+ orchestra accounts list
427
+ ```
428
+
429
+ Behavior
430
+
431
+ - Sends `GET /public/v1/accounts`, with no account header.
432
+ - Prints the `[{id, name}]` response as pretty JSON, adding `default: true` to the account saved by `accounts use` and `false` to the rest.
433
+ - Exit codes: `0` on success, `1` on failure.
434
+
435
+ ---
436
+
437
+ ## accounts use
438
+
439
+ Save a default workspace for an `orchestra login` that covers several, so commands no longer need `--account-id`.
440
+
441
+ ```bash
442
+ orchestra accounts use 'some great account'
443
+ orchestra accounts use 00000000-0000-0000-0000-000000000000
444
+ orchestra accounts use # pick from a list
445
+ ```
446
+
447
+ Behavior
448
+
449
+ - Fetches the accounts as `accounts list` does, then matches an exact id first and a case-insensitive exact name second.
450
+ - Exits `1`, listing the valid names, when nothing matches, and asks for the id when several workspaces share the name.
451
+ - With no argument, lists the workspaces with the cursor on the current default: ↑/↓ moves, Enter saves, and Esc, Ctrl+C or Ctrl+D exits `1` leaving the default unchanged. Without a terminal (CI, piped input) it exits `1` and asks for an id or name instead.
452
+ - Saves the account into the login's entry in `~/.orchestra/credentials.json`. It survives token refreshes; running `orchestra login` again resets it.
453
+ - `--account-id` and `ORCHESTRA_ACCOUNT_ID` still override the default. It is never sent with an API key.
454
+ - Exits `1` when you are using an API key rather than a login, since a key covers one account.
455
+
456
+ ---
457
+
403
458
  ## task logs
404
459
 
405
460
  Fetch or follow logs for a single task run.
@@ -20,15 +20,17 @@ pipx install orchestra-cli
20
20
 
21
21
  ## Environment variables
22
22
 
23
- - `ORCHESTRA_API_KEY`: Required for actions that call the API (`pipeline import`, `pipeline new`, `pipeline update`, `pipeline migrate`, `pipeline get`, `pipeline list`, `pipeline delete`, `pipeline run`, `pipeline build`).
23
+ - `ORCHESTRA_API_KEY`: Credential for actions that call the API (`pipeline import`, `pipeline new`, `pipeline update`, `pipeline migrate`, `pipeline get`, `pipeline list`, `pipeline delete`, `pipeline run`, `pipeline build`, `accounts list`). Not needed once you have run `orchestra login`: a login takes precedence, and the key is used only when you are not logged in or your login has expired.
24
+ - `ORCHESTRA_ACCOUNT_ID`: Optional. The workspace to act in when your `orchestra login` covers several. Every `pipeline` and `task` command that calls the API also takes `--account-id`, which wins over the variable, and both win over the default saved by `orchestra accounts use`. API keys belong to one account and don't need it.
24
25
  - `BASE_URL`: Optional. Override the default Orchestra host (`https://app.getorchestra.io`) for non‑production/testing.
25
26
 
26
27
  ## Command structure
27
28
 
28
- Commands follow a `noun verb` shape. The current nouns are `pipeline` and `task`:
29
+ Commands follow a `noun verb` shape. The current nouns are `pipeline`, `task` and `accounts`:
29
30
 
30
31
  | Command | Description |
31
32
  | ------------------------------------ | ------------------------------------------------------------------------------------------- |
33
+ | `orchestra login` | Log in through your browser so later commands need no API key. |
32
34
  | `orchestra pipeline validate <file>` | Validate a pipeline YAML locally against the Orchestra API schema. |
33
35
  | `orchestra pipeline import` | Register a pipeline YAML (from a git repo) with Orchestra under an alias. |
34
36
  | `orchestra pipeline get` | Fetch one pipeline by path, alias, or pipeline ID. |
@@ -40,6 +42,8 @@ Commands follow a `noun verb` shape. The current nouns are `pipeline` and `task`
40
42
  | `orchestra pipeline run` | Start a pipeline run by selector, optionally pinning branch/commit and waiting. |
41
43
  | `orchestra pipeline build` | Validate local YAML, create or update a draft pipeline, and start that draft version. |
42
44
  | `orchestra task logs` | Fetch or follow logs for a single task run. |
45
+ | `orchestra accounts list` | List the workspaces your login or API key covers, with their ids, as JSON. |
46
+ | `orchestra accounts use [id\|name]` | Save the workspace later commands act in when no `--account-id` is given. |
43
47
 
44
48
  Commands that read a local pipeline file (`pipeline validate`, `import`, `new`, `update`, `build`, and `run --path` with `--task`) accept `.yaml`, `.yml` and `.oml` files; any other extension is rejected. `.oml` files are read as YAML, so only OML written in YAML or JSON syntax is supported. OML block terminators (`.`) and embeds (`%sql ... %%`) fail with an `Invalid YAML` error.
45
49
 
@@ -98,6 +102,20 @@ pipeline:
98
102
 
99
103
  ---
100
104
 
105
+ ## login
106
+
107
+ Log in through your browser instead of pasting an API key. The token is cached in `~/.orchestra/credentials.json` (readable only by you) and refreshed automatically, so later commands need no `ORCHESTRA_API_KEY`.
108
+
109
+ ```bash
110
+ orchestra login
111
+ ```
112
+
113
+ Logins are per host: a login made with `BASE_URL` pointing at another environment is only used against that environment. CI and scripts, where nobody is logged in, keep using `ORCHESTRA_API_KEY`.
114
+
115
+ After logging in it saves a default workspace, as `accounts use` does: the only one if your login covers one, or the one you pick from a list if it covers several. Esc skips the default, and without a terminal it prints a hint to run `orchestra accounts use` instead. If the accounts can't be listed, the login still succeeds with no default set.
116
+
117
+ ---
118
+
101
119
  ## pipeline import
102
120
 
103
121
  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.
@@ -386,6 +404,43 @@ Behavior
386
404
 
387
405
  ---
388
406
 
407
+ ## accounts list
408
+
409
+ List the workspaces the current credential covers. An `orchestra login` token can cover several workspaces; an API key lists just its own.
410
+
411
+ ```bash
412
+ orchestra accounts list
413
+ ```
414
+
415
+ Behavior
416
+
417
+ - Sends `GET /public/v1/accounts`, with no account header.
418
+ - Prints the `[{id, name}]` response as pretty JSON, adding `default: true` to the account saved by `accounts use` and `false` to the rest.
419
+ - Exit codes: `0` on success, `1` on failure.
420
+
421
+ ---
422
+
423
+ ## accounts use
424
+
425
+ Save a default workspace for an `orchestra login` that covers several, so commands no longer need `--account-id`.
426
+
427
+ ```bash
428
+ orchestra accounts use 'some great account'
429
+ orchestra accounts use 00000000-0000-0000-0000-000000000000
430
+ orchestra accounts use # pick from a list
431
+ ```
432
+
433
+ Behavior
434
+
435
+ - Fetches the accounts as `accounts list` does, then matches an exact id first and a case-insensitive exact name second.
436
+ - Exits `1`, listing the valid names, when nothing matches, and asks for the id when several workspaces share the name.
437
+ - With no argument, lists the workspaces with the cursor on the current default: ↑/↓ moves, Enter saves, and Esc, Ctrl+C or Ctrl+D exits `1` leaving the default unchanged. Without a terminal (CI, piped input) it exits `1` and asks for an id or name instead.
438
+ - Saves the account into the login's entry in `~/.orchestra/credentials.json`. It survives token refreshes; running `orchestra login` again resets it.
439
+ - `--account-id` and `ORCHESTRA_ACCOUNT_ID` still override the default. It is never sent with an API key.
440
+ - Exits `1` when you are using an API key rather than a login, since a key covers one account.
441
+
442
+ ---
443
+
389
444
  ## task logs
390
445
 
391
446
  Fetch or follow logs for a single task run.
@@ -3,7 +3,13 @@ from pathlib import Path
3
3
  import httpx
4
4
  import typer
5
5
 
6
- from ..utils.api import auth_headers, fail_with_response, request_or_exit, require_api_key
6
+ from ..utils.api import (
7
+ account_id_option,
8
+ api_client,
9
+ fail_with_response,
10
+ request_or_exit,
11
+ require_credential,
12
+ )
7
13
  from ..utils.constants import get_create_pipeline_url, get_update_pipeline_url
8
14
  from ..utils.git import confirm_git_warnings_or_exit, prepare_git_backed_run_target
9
15
  from ..utils.pipeline_lookup import lookup_existing_pipeline
@@ -64,22 +70,20 @@ def _build_create_selector(
64
70
 
65
71
 
66
72
  def _create_draft_pipeline(
73
+ client: httpx.Client,
67
74
  path: Path,
68
75
  lookup_selector: PipelineSelector,
69
76
  pipeline_data: dict[str, object],
70
77
  force: bool,
71
- api_key: str,
72
78
  ) -> tuple[PipelineSelector, int]:
73
79
  create_selector = _build_create_selector(path, lookup_selector, force)
74
80
  payload = build_upsert_payload(pipeline_data, publish=False, selector=create_selector)
75
81
 
76
82
  typer.echo(f"Creating draft pipeline ({create_selector.display()})")
77
83
  create_response = request_or_exit(
78
- httpx.post,
84
+ client.post,
79
85
  get_create_pipeline_url(),
80
86
  json=payload,
81
- timeout=30,
82
- headers=auth_headers(api_key),
83
87
  )
84
88
 
85
89
  if create_response.status_code == 201:
@@ -96,20 +100,18 @@ def _create_draft_pipeline(
96
100
 
97
101
 
98
102
  def _update_draft_pipeline(
103
+ client: httpx.Client,
99
104
  existing_pipeline: dict[str, object],
100
105
  pipeline_data: dict[str, object],
101
- api_key: str,
102
106
  ) -> tuple[PipelineSelector, int]:
103
107
  update_selector = build_update_selector(existing_pipeline, "Build")
104
108
  payload = build_upsert_payload(pipeline_data, publish=False, selector=update_selector)
105
109
 
106
110
  typer.echo(f"Updating draft pipeline ({update_selector.display()})")
107
111
  update_response = request_or_exit(
108
- httpx.put,
112
+ client.put,
109
113
  get_update_pipeline_url(),
110
114
  json=payload,
111
- timeout=30,
112
- headers=auth_headers(api_key),
113
115
  )
114
116
 
115
117
  if update_response.status_code == 200:
@@ -141,11 +143,13 @@ def build_pipeline(
141
143
  "--force/--no-force",
142
144
  help="Ignore any warnings and run the pipeline anyway",
143
145
  ),
146
+ account_id: str | None = account_id_option(),
144
147
  ) -> None:
145
148
  """
146
149
  Validate local YAML, create or update a draft pipeline, and start the draft version.
147
150
  """
148
- api_key = require_api_key()
151
+ require_credential()
152
+ client = api_client(account_id)
149
153
  if path is None:
150
154
  typer.echo(
151
155
  red("A pipeline YAML file path is required (use -p or --path with your YAML file)"),
@@ -155,19 +159,19 @@ def build_pipeline(
155
159
 
156
160
  confirm_git_warnings_or_exit(force, path)
157
161
  pipeline_data = load_validated_pipeline_data(path)
158
- existing_pipeline = lookup_existing_pipeline(lookup_selector, api_key, "Build", allow_404=True)
162
+ existing_pipeline = lookup_existing_pipeline(client, lookup_selector, "Build", allow_404=True)
159
163
 
160
164
  if existing_pipeline is None:
161
165
  run_selector, version_number = _create_draft_pipeline(
166
+ client=client,
162
167
  path=path,
163
168
  lookup_selector=lookup_selector,
164
169
  pipeline_data=pipeline_data,
165
170
  force=force,
166
- api_key=api_key,
167
171
  )
168
172
  start_pipeline_run(
173
+ client=client,
169
174
  selector=run_selector,
170
- api_key=api_key,
171
175
  payload=build_run_payload(
172
176
  branch=branch,
173
177
  commit=commit,
@@ -181,14 +185,14 @@ def build_pipeline(
181
185
  pipeline_storage_provider = storage_provider(existing_pipeline)
182
186
  if pipeline_storage_provider is None or pipeline_storage_provider == "ORCHESTRA":
183
187
  run_selector, version_number = _update_draft_pipeline(
188
+ client=client,
184
189
  existing_pipeline=existing_pipeline,
185
190
  pipeline_data=pipeline_data,
186
- api_key=api_key,
187
191
  )
188
192
 
189
193
  start_pipeline_run(
194
+ client=client,
190
195
  selector=run_selector,
191
- api_key=api_key,
192
196
  payload=build_run_payload(
193
197
  branch=branch,
194
198
  commit=commit,
@@ -221,8 +225,8 @@ def build_pipeline(
221
225
  )
222
226
 
223
227
  start_pipeline_run(
228
+ client=client,
224
229
  selector=run_selector,
225
- api_key=api_key,
226
230
  payload=build_run_payload(
227
231
  branch=git_branch,
228
232
  commit=git_commit,
@@ -6,10 +6,13 @@ from .delete_pipeline import delete_pipeline
6
6
  from .fetch_pipelines import fetch_pipelines
7
7
  from .get_pipeline import get_pipeline
8
8
  from .import_pipeline import import_pipeline
9
+ from .list_accounts import list_accounts
10
+ from .login import login
9
11
  from .migrate_pipeline import migrate_pipeline
10
12
  from .run_pipeline import run_pipeline
11
13
  from .task_logs import task_logs
12
14
  from .update_pipeline import update_pipeline
15
+ from .use_account import use_account
13
16
  from .validate_pipeline import validate
14
17
 
15
18
  app = typer.Typer(help="Orchestra CLI – perform operations with Orchestra locally.")
@@ -18,6 +21,9 @@ pipeline_app = typer.Typer(help="Manage Orchestra pipelines (validate, import, r
18
21
  app.add_typer(pipeline_app, name="pipeline")
19
22
  task_app = typer.Typer(help="Inspect Orchestra task runs.")
20
23
  app.add_typer(task_app, name="task")
24
+ app.command(name="login")(login)
25
+ accounts_app = typer.Typer(help="List and choose the workspaces your credential covers.")
26
+ app.add_typer(accounts_app, name="accounts")
21
27
 
22
28
  pipeline_app.command(name="validate")(validate)
23
29
  pipeline_app.command(name="import")(import_pipeline)
@@ -30,6 +36,8 @@ pipeline_app.command(name="build")(build_pipeline)
30
36
  pipeline_app.command(name="delete")(delete_pipeline)
31
37
  pipeline_app.command(name="migrate")(migrate_pipeline)
32
38
  task_app.command(name="logs")(task_logs)
39
+ accounts_app.command(name="list")(list_accounts)
40
+ accounts_app.command(name="use")(use_account)
33
41
 
34
42
  # Legacy top-level aliases (hidden) - keep the old `orchestra <command>` syntax working
35
43
  # so existing scripts and CI pipelines do not break. Hidden from `--help` to keep the
@@ -1,13 +1,13 @@
1
1
  from pathlib import Path
2
2
 
3
- import httpx
4
3
  import typer
5
4
 
6
5
  from ..utils.api import (
7
- auth_headers,
6
+ account_id_option,
7
+ api_client,
8
8
  fail_with_response,
9
9
  request_or_exit,
10
- require_api_key,
10
+ require_credential,
11
11
  )
12
12
  from ..utils.constants import get_create_pipeline_url
13
13
  from ..utils.pipeline_selector import (
@@ -32,11 +32,13 @@ def create_pipeline(
32
32
  "--publish/--no-publish",
33
33
  help="Whether the pipeline is published and can be triggered",
34
34
  ),
35
+ account_id: str | None = account_id_option(),
35
36
  ):
36
37
  """
37
38
  Create an Orchestra-backed pipeline from a local YAML file.
38
39
  """
39
- api_key = require_api_key()
40
+ require_credential()
41
+ client = api_client(account_id)
40
42
  if path is None:
41
43
  typer.echo(red("Provide --path to create a pipeline from YAML"))
42
44
  raise typer.Exit(code=1)
@@ -50,11 +52,9 @@ def create_pipeline(
50
52
  payload = build_upsert_payload(data, publish, selector)
51
53
 
52
54
  response = request_or_exit(
53
- httpx.post,
55
+ client.post,
54
56
  get_create_pipeline_url(),
55
57
  json=payload,
56
- timeout=30,
57
- headers=auth_headers(api_key),
58
58
  )
59
59
 
60
60
  if response.status_code == 201:
@@ -1,13 +1,13 @@
1
1
  from pathlib import Path
2
2
 
3
- import httpx
4
3
  import typer
5
4
 
6
5
  from ..utils.api import (
7
- auth_headers,
6
+ account_id_option,
7
+ api_client,
8
8
  fail_with_response,
9
9
  request_or_exit,
10
- require_api_key,
10
+ require_credential,
11
11
  )
12
12
  from ..utils.constants import get_delete_pipeline_url
13
13
  from ..utils.pipeline_selector import (
@@ -23,11 +23,13 @@ def delete_pipeline(
23
23
  path: Path | None = pipeline_path_option(),
24
24
  alias: str | None = pipeline_alias_option(),
25
25
  pipeline_id: str | None = pipeline_id_option(),
26
+ account_id: str | None = account_id_option(),
26
27
  ):
27
28
  """
28
29
  Delete a pipeline by selector.
29
30
  """
30
- api_key = require_api_key()
31
+ require_credential()
32
+ client = api_client(account_id)
31
33
  selector = resolve_pipeline_selector(alias, pipeline_id, path)
32
34
 
33
35
  if not typer.confirm(f"Delete pipeline ({selector.display()})?"):
@@ -35,11 +37,9 @@ def delete_pipeline(
35
37
  raise typer.Exit(code=1)
36
38
 
37
39
  response = request_or_exit(
38
- httpx.delete,
40
+ client.delete,
39
41
  get_delete_pipeline_url(),
40
42
  params=selector.to_payload(),
41
- timeout=30,
42
- headers=auth_headers(api_key),
43
43
  )
44
44
 
45
45
  if response.status_code == 204:
@@ -1,31 +1,30 @@
1
1
  import json
2
2
 
3
- import httpx
4
3
  import typer
5
4
 
6
5
  from ..utils.api import (
7
- auth_headers,
6
+ account_id_option,
7
+ api_client,
8
8
  fail_with_response,
9
9
  request_or_exit,
10
- require_api_key,
10
+ require_credential,
11
11
  )
12
12
  from ..utils.constants import get_api_url
13
13
  from ..utils.styling import indent_message, red, yellow
14
14
 
15
15
 
16
- def fetch_pipelines():
16
+ def fetch_pipelines(account_id: str | None = account_id_option()):
17
17
  """
18
18
  Fetch pipelines available to the current Orchestra API key.
19
19
 
20
20
  The API always includes each pipeline's latest run metadata.
21
21
  """
22
- api_key = require_api_key()
22
+ require_credential()
23
+ client = api_client(account_id)
23
24
 
24
25
  response = request_or_exit(
25
- httpx.get,
26
+ client.get,
26
27
  get_api_url("pipelines"),
27
- timeout=30,
28
- headers=auth_headers(api_key),
29
28
  )
30
29
 
31
30
  if response.status_code == 200:
@@ -3,14 +3,14 @@ import re
3
3
  from pathlib import Path
4
4
  from typing import cast
5
5
 
6
- import httpx
7
6
  import typer
8
7
 
9
8
  from ..utils.api import (
10
- auth_headers,
9
+ account_id_option,
10
+ api_client,
11
11
  fail_with_response,
12
12
  request_or_exit,
13
- require_api_key,
13
+ require_credential,
14
14
  )
15
15
  from ..utils.constants import get_pipeline_url
16
16
  from ..utils.pipeline_selector import (
@@ -46,19 +46,19 @@ def get_pipeline(
46
46
  path: Path | None = pipeline_path_option(),
47
47
  alias: str | None = pipeline_alias_option(),
48
48
  pipeline_id: str | None = pipeline_id_option(),
49
+ account_id: str | None = account_id_option(),
49
50
  ):
50
51
  """
51
52
  Fetch one pipeline using the shared selector model.
52
53
  """
53
- api_key = require_api_key()
54
+ require_credential()
55
+ client = api_client(account_id)
54
56
  selector = resolve_pipeline_selector(alias, pipeline_id, path)
55
57
 
56
58
  response = request_or_exit(
57
- httpx.get,
59
+ client.get,
58
60
  get_pipeline_url(),
59
61
  params=selector.to_payload(),
60
- timeout=30,
61
- headers=auth_headers(api_key),
62
62
  )
63
63
 
64
64
  if response.status_code == 200:
@@ -1,14 +1,14 @@
1
1
  import json
2
2
  from pathlib import Path
3
3
 
4
- import httpx
5
4
  import typer
6
5
 
7
6
  from ..utils.api import (
8
- auth_headers,
7
+ account_id_option,
8
+ api_client,
9
9
  fail_with_response,
10
10
  request_or_exit,
11
- require_api_key,
11
+ require_credential,
12
12
  )
13
13
  from ..utils.constants import get_api_url
14
14
  from ..utils.git import (
@@ -39,11 +39,13 @@ def import_pipeline(
39
39
  "-w",
40
40
  help="Git branch to use for the imported pipeline (defaults to current local branch)",
41
41
  ),
42
+ account_id: str | None = account_id_option(),
42
43
  ):
43
44
  """
44
45
  Create a pipeline in Orchestra by referencing a YAML file in your git repository.
45
46
  """
46
- api_key = require_api_key()
47
+ require_credential()
48
+ client = api_client(account_id)
47
49
  if path is None:
48
50
  typer.echo(red("Provide --path to import a pipeline from git"))
49
51
  raise typer.Exit(code=1)
@@ -98,11 +100,9 @@ def import_pipeline(
98
100
  payload["alias"] = alias
99
101
 
100
102
  response = request_or_exit(
101
- httpx.post,
103
+ client.post,
102
104
  get_api_url("pipelines/import"),
103
105
  json=payload,
104
- timeout=30,
105
- headers=auth_headers(api_key),
106
106
  )
107
107
 
108
108
  if response.status_code == 201:
@@ -0,0 +1,16 @@
1
+ import json
2
+
3
+ import typer
4
+
5
+ from ..utils.accounts import fetch_accounts
6
+ from ..utils.api import api_client
7
+ from ..utils.credentials import load_credentials
8
+
9
+
10
+ def list_accounts():
11
+ """List the Orchestra workspaces the current login or API key covers."""
12
+ accounts = fetch_accounts(api_client(scoped=False))
13
+ # Read after fetching, which forgets a dead login whose default no longer applies.
14
+ default_id = (load_credentials() or {}).get("account_id")
15
+ accounts = [{**account, "default": account["id"] == default_id} for account in accounts]
16
+ typer.echo(json.dumps(accounts, indent=2))