tangle-cli 0.1.10__tar.gz → 0.1.12__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 (102) hide show
  1. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/PKG-INFO +107 -3
  2. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/README.md +106 -2
  3. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/__init__.py +1 -1
  4. tangle_cli-0.1.12/packages/tangle-cli/src/tangle_cli/args_container.py +518 -0
  5. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/cli_helpers.py +33 -0
  6. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/pipeline_runs_cli.py +411 -1
  7. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/pipelines_cli.py +9 -27
  8. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/pyproject.toml +1 -1
  9. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/pyproject.toml.orig +1 -1
  10. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/OSS-CONVENTIONS.md +2 -1
  11. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/references/tangle-tools.md +24 -1
  12. tangle_cli-0.1.10/packages/tangle-cli/src/tangle_cli/args_container.py +0 -244
  13. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/api_cli.py +0 -0
  14. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/api_schema.py +0 -0
  15. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/api_transport.py +0 -0
  16. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/artifacts.py +0 -0
  17. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/artifacts_cli.py +0 -0
  18. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/cli.py +0 -0
  19. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/cli_options.py +0 -0
  20. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/client.py +0 -0
  21. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/component_from_func.py +0 -0
  22. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/component_generator.py +0 -0
  23. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/component_inspector.py +0 -0
  24. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/component_publisher.py +0 -0
  25. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/components_cli.py +0 -0
  26. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/dynamic_discovery_client.py +0 -0
  27. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/generated_model_extensions.py +0 -0
  28. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/handler.py +0 -0
  29. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/hydration_trust.py +0 -0
  30. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/logger.py +0 -0
  31. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/models.py +0 -0
  32. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/module_bundler.py +0 -0
  33. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/openapi/__init__.py +0 -0
  34. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/openapi/codegen.py +0 -0
  35. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/openapi/parser.py +0 -0
  36. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/pipeline_compiler.py +0 -0
  37. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/pipeline_dehydrator.py +0 -0
  38. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/pipeline_hydrator.py +0 -0
  39. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/pipeline_run_annotations.py +0 -0
  40. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/pipeline_run_details.py +0 -0
  41. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/pipeline_run_manager.py +0 -0
  42. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/pipeline_run_search.py +0 -0
  43. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/pipeline_runner.py +0 -0
  44. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/pipeline_spec_utils.py +0 -0
  45. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/pipeline_validation.py +0 -0
  46. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/pipelines.py +0 -0
  47. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/published_components_cli.py +0 -0
  48. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/py.typed +0 -0
  49. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/python_pipeline/__init__.py +0 -0
  50. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/python_pipeline/cfg.py +0 -0
  51. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/python_pipeline/compiler_context.py +0 -0
  52. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/python_pipeline/dynamic_data.py +0 -0
  53. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/python_pipeline/emit.py +0 -0
  54. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/python_pipeline/errors.py +0 -0
  55. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/python_pipeline/graph.py +0 -0
  56. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/python_pipeline/ids.py +0 -0
  57. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/python_pipeline/pipeline.py +0 -0
  58. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/python_pipeline/placeholders.py +0 -0
  59. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/python_pipeline/raw.py +0 -0
  60. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/python_pipeline/ref.py +0 -0
  61. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/python_pipeline/registered.py +0 -0
  62. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/python_pipeline/subpipeline.py +0 -0
  63. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/python_pipeline/task.py +0 -0
  64. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/python_pipeline/task_env.py +0 -0
  65. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/python_pipeline/trace.py +0 -0
  66. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/python_pipeline/types.py +0 -0
  67. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/quickstart.py +0 -0
  68. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/schema_validation.py +0 -0
  69. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/schemas/__init__.py +0 -0
  70. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/schemas/dehydrated_pipeline_schema.json +0 -0
  71. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/schemas/pipeline_schema.json +0 -0
  72. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/secrets.py +0 -0
  73. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/secrets_cli.py +0 -0
  74. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/utils.py +0 -0
  75. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/packages/tangle-cli/src/tangle_cli/version_manager.py +0 -0
  76. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/PORT-README.md +0 -0
  77. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/SKILL.md +0 -0
  78. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/agents/auth-wizard.md +0 -0
  79. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/agents/builder.md +0 -0
  80. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/agents/debugger.md +0 -0
  81. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/agents/reporter.md +0 -0
  82. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/agents/researcher.md +0 -0
  83. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/agents/reviewer.md +0 -0
  84. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/agents/scenario-builder.md +0 -0
  85. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/references/data-sources.md +0 -0
  86. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/references/event-log.md +0 -0
  87. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/references/example-scenarios/01-mslr-ranking.md +0 -0
  88. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/references/example-scenarios/02-text-classification.md +0 -0
  89. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/references/example-scenarios/INDEX.md +0 -0
  90. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/references/iterating-on-runs.md +0 -0
  91. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/references/knowledge-corpus.md +0 -0
  92. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/references/secrets.md +0 -0
  93. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/references/setup.md +0 -0
  94. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/references/step-0-initialize.md +0 -0
  95. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/references/step-1-analyze.md +0 -0
  96. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/references/step-2-hypothesize.md +0 -0
  97. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/references/step-3-submit.md +0 -0
  98. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/references/step-4-monitor.md +0 -0
  99. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/references/step-5-evaluate.md +0 -0
  100. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/references/step-6-synthesize.md +0 -0
  101. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/references/step-7-decide.md +0 -0
  102. {tangle_cli-0.1.10 → tangle_cli-0.1.12}/skills/tangent/references/uploading-artifacts.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: tangle-cli
3
- Version: 0.1.10
3
+ Version: 0.1.12
4
4
  Summary: CLI for Tangle, the open-source ML pipeline orchestration platform
5
5
  Author: Alexey Volkov, Tangle authors, Volv Grebennikov
6
6
  Author-email: Alexey Volkov <alexey.volkov@ark-kun.com>, Volv Grebennikov <volv.grebennikov@shopify.com>
@@ -83,7 +83,7 @@ API-backed commands commonly accept these options. Explicit CLI options win over
83
83
  | `--token`, `TANGLE_API_TOKEN` | Bearer token shorthand. |
84
84
  | `--auth-header`, `TANGLE_API_AUTH_HEADER`, `TANGLE_AUTH_HEADER` | Full `Authorization` value such as `Bearer ...` or `Basic ...`. |
85
85
  | `-H`, `--header`, `TANGLE_API_HEADERS` | Extra headers. Repeatable as CLI flags; env accepts a JSON object or newline-separated `Name: value` entries. |
86
- | `--config` | YAML/JSON defaults. Many commands accept a single object, a list of objects, or `_defaults` + `configs`. |
86
+ | `--config` | YAML/JSON defaults. Many commands accept a single object, a list of objects, or `_defaults` + `configs`, optionally wrapped in a top-level `_select` environment selector. |
87
87
  | `--log-type` | SDK progress logs: `console`, `none`, or `file`. Logs go to stderr or a temp log file so structured stdout stays parseable. |
88
88
  | `TANGLE_VERBOSE=1` | Redacted HTTP request/response diagnostics only. This is separate from normal progress logging. |
89
89
  | `--ca-bundle` | Global CLI flag: path to a PEM CA bundle used as the TLS trust store for every transport. Overrides `TANGLE_API_CA_BUNDLE`. Place before the subcommand. |
@@ -256,6 +256,56 @@ uv run tangle sdk pipeline-runs submit --config submit.yaml
256
256
 
257
257
  For generated `tangle api` commands, config keys use generated CLI parameter names such as `base_url`, `schema_source`, `body`, and endpoint parameters like `limit`, `filter`, or `id`.
258
258
 
259
+ ### Environment-selected configs (`_select`)
260
+
261
+ Any command that accepts `--config` can pick one of several config documents from an environment variable by making `_select` the top-level node:
262
+
263
+ ```yaml
264
+ _shared: &shared
265
+ log_type: none
266
+
267
+ _select:
268
+ env: TANGLE_ENV
269
+ cases:
270
+ dev:
271
+ <<: *shared
272
+ base_url: https://api.dev
273
+ prod:
274
+ _defaults:
275
+ <<: *shared
276
+ base_url: https://api.prod
277
+ configs:
278
+ - filter: active
279
+ - filter: finished
280
+ ```
281
+
282
+ The selected branch is a complete config document — a single object, a list of objects, or `_defaults` + `configs` — and is then loaded exactly as if it had been written on its own. A branch may itself be another `_select` node, which composes multiple dimensions (for example environment and region).
283
+
284
+ Selection fails closed by default. To opt into a fallback, author an explicit `default` branch:
285
+
286
+ ```yaml
287
+ _select:
288
+ env: TANGLE_ENV
289
+ cases:
290
+ prod:
291
+ base_url: https://api.prod
292
+ default:
293
+ base_url: https://api.dev
294
+ ```
295
+
296
+ With `default`, an unset variable or a value matching no case resolves to that branch; an exact case match always wins over it. Without `default`, both remain errors. `default` is a sibling of `cases`, not an entry inside it: a case *named* `default` stays an ordinary exact-match case. Like any branch, `default` must be a complete valid config shape and may itself be another `_select`; an inner selector does not inherit the outer `default`.
297
+
298
+ Rules:
299
+
300
+ - `_select` is the only newly reserved key, and it is an exact key name rather than a prefix. Configs without `_select` are unchanged byte-for-byte and semantically.
301
+ - At a `_select` node, only `_select` and other underscore-prefixed helper keys (YAML anchor holders such as `_shared`) may appear; ordinary sibling keys are rejected.
302
+ - `_select` accepts only `env`, `cases`, and the optional `default`. There are no aliases: `else`, `fallback`, and `defaults` are rejected.
303
+ - The selector shape, the `env` name, every `cases` key, and every case and `default` branch are validated before the environment is read, so a malformed selector fails identically in every environment. Branches are checked as complete config documents with the same rules the loader applies to a whole file, and nested selectors are validated recursively.
304
+ - Only environment *lookups* are lazy. A dormant branch is fully shape-checked, but its `env` variable is never required unless that branch is actually selected. Command-specific field names and types are still validated later, against the selected branch only.
305
+ - Selector nesting is capped at 32 levels, which also stops a self-referential YAML alias. A node shared by several anchors is validated once, so anchor-heavy files stay fast.
306
+ - Matching uses `os.environ[NAME]` exactly: case sensitive, with no trimming, case folding, or interpolation.
307
+ - Fallback exists only where it is authored. Without `default`, an unset variable or an unmatched value is an error; there is never an implicit default or implicit production branch. The raw environment value is never echoed — diagnostics list only the configured case names.
308
+
259
309
  ## API schema cache and dynamic commands
260
310
 
261
311
  Refresh the local schema cache for a live backend with:
@@ -331,7 +381,7 @@ uv run tangle sdk published-components deprecate sha256:old --superseded-by sha2
331
381
 
332
382
  `publish` accepts `--image`, `--name`, `--description`, `--annotations` (JSON), `--dry-run`, `--published-by`, generic git metadata fields, generic API auth fields, `--log-type`, and `--config`. By default it scopes version checks and automatic old-version deprecation to the current authenticated user via `users_me()`; use `--published-by` to supply an explicit owner/publisher filter. Publishing fails closed if no owner can be determined.
333
383
 
334
- There is no separate OSS `publish-all` command. To publish multiple components, pass a YAML/JSON config list, or `_defaults` + `configs`, to the same `published-components publish` command; the command aggregates results and exits nonzero if any component errors.
384
+ There is no separate OSS `publish-all` command. To publish multiple components, pass a YAML/JSON config list, or `_defaults` + `configs`, to the same `published-components publish` command; the command aggregates results and exits nonzero if any component errors. A top-level `_select` node can choose between such documents per environment (see [Environment-selected configs](#environment-selected-configs-_select)).
335
385
 
336
386
  ```yaml
337
387
  _defaults:
@@ -362,6 +412,7 @@ Pipeline run API/submit commands live under `sdk pipeline-runs`:
362
412
  ```bash
363
413
  uv run tangle sdk pipeline-runs submit pipeline.yaml --dry-run
364
414
  uv run tangle sdk pipeline-runs submit pipeline.yaml --arg key=value --annotation owner=team
415
+ uv run tangle sdk pipeline-runs submit-from-python pipeline.py --arg key=value
365
416
  uv run tangle sdk pipeline-runs wait RUN_ID --max-wait 600 --poll-interval 10
366
417
  uv run tangle sdk pipeline-runs logs EXECUTION_ID
367
418
  uv run tangle sdk pipeline-runs annotations set RUN_ID key value
@@ -377,6 +428,59 @@ uv run tangle sdk pipelines compile pipeline.py -o pipeline.yaml
377
428
  uv run tangle sdk pipelines compile pipeline.py -o pipeline.yaml --pipeline pipeline_fn_name
378
429
  ```
379
430
 
431
+ To compile and submit in one step, without keeping the compiled YAML around, use
432
+ `pipeline-runs submit-from-python`:
433
+
434
+ ```bash
435
+ uv run tangle sdk pipeline-runs submit-from-python pipeline.py \
436
+ --override batch_size=100 \
437
+ --image eval-slim=registry.example/eval-slim@sha256:... \
438
+ --arg shop=acme --annotation owner=team
439
+ ```
440
+
441
+ It compiles the script, hydrates the bundle, submits the run, and removes the
442
+ compiled artifacts again — including on dry runs and failures. Its run-tier
443
+ flags are the same as `pipeline-runs submit`; the extra compile-tier flags are
444
+ `--pipeline`, repeatable `--override KEY=VALUE`, and repeatable `--image ID=REF`.
445
+ Use `pipelines compile` instead when the compiled YAML itself is what you want.
446
+
447
+ The two value tiers are distinct and easy to confuse:
448
+
449
+ | Flag | Tier | Meaning |
450
+ | --- | --- | --- |
451
+ | `--override KEY=VALUE` | compile | `cfg` value used while the graph is built |
452
+ | `--image ID=REF` | compile | resolves `@task(image_id=ID)` to a registry ref |
453
+ | `--arg` / `--args-json` / `--arg-secret` | run | pipeline arguments for the created run |
454
+
455
+ Notes:
456
+
457
+ - Hydration is always on: the compiled bundle references local sidecars
458
+ (`resolve://./<stem>.components.yaml#…`, `file://<stem>.subgraphs/…`) that the
459
+ server cannot read. There is no `--no-hydrate` here.
460
+ - The bundle is compiled next to the script (never in `/tmp`) under a unique
461
+ hidden `.tangle-submit-*.yaml` name, because relative refs such as
462
+ `ref(url="file://./component.yaml")` resolve against the *output* directory.
463
+ The name is allocated with an exclusive create, so concurrent compiles of the
464
+ same script never collide or overwrite a sibling YAML. A symlinked script
465
+ compiles next to its resolved target, where its `config.yaml` and relative
466
+ refs actually live.
467
+ - The bundle stays readable until every run has been submitted, so
468
+ run-lifecycle hooks still see an existing `pipeline_path`; cleanup then
469
+ removes exactly that stem's files (`.yaml`, `.components.yaml`,
470
+ `.subgraphs/`) and nothing else.
471
+ - Submission never waits; use `pipeline-runs wait RUN_ID`.
472
+ - With a multi-entry `--config` file, entries may carry different `--override` /
473
+ `--image` values, and **every entry is fully prepared before any run is
474
+ created**: each is compiled exactly once, hydrated, merged with its run
475
+ arguments and secrets, validated, and frozen into a submit body; only then are
476
+ the frozen bodies submitted in order (no recompilation). An unsupported config
477
+ key (`hydrate:`, `pipeline_path:`), a malformed `override` / `image` /
478
+ `arg-secret` value, an input given as both `--arg` and `--arg-secret`, a
479
+ missing script, or a compile/hydrate error in *any* entry therefore creates no
480
+ runs and leaves no artifacts behind. A runtime failure while submitting entry
481
+ N can still follow the successful submits of entries 1..N-1 — that is inherent
482
+ to creating N runs.
483
+
380
484
  A minimal graph uses `@pipeline` for the graph and `@task` for local Python components. `@task` functions are not executed at compile time; the compiler records call sites, emits a sibling `<output>.components.yaml` with `local_from_python` entries, and rewrites task component refs to that sidecar. Hydrate later regenerates the same component YAML from the Python source.
381
485
 
382
486
  ```python
@@ -57,7 +57,7 @@ API-backed commands commonly accept these options. Explicit CLI options win over
57
57
  | `--token`, `TANGLE_API_TOKEN` | Bearer token shorthand. |
58
58
  | `--auth-header`, `TANGLE_API_AUTH_HEADER`, `TANGLE_AUTH_HEADER` | Full `Authorization` value such as `Bearer ...` or `Basic ...`. |
59
59
  | `-H`, `--header`, `TANGLE_API_HEADERS` | Extra headers. Repeatable as CLI flags; env accepts a JSON object or newline-separated `Name: value` entries. |
60
- | `--config` | YAML/JSON defaults. Many commands accept a single object, a list of objects, or `_defaults` + `configs`. |
60
+ | `--config` | YAML/JSON defaults. Many commands accept a single object, a list of objects, or `_defaults` + `configs`, optionally wrapped in a top-level `_select` environment selector. |
61
61
  | `--log-type` | SDK progress logs: `console`, `none`, or `file`. Logs go to stderr or a temp log file so structured stdout stays parseable. |
62
62
  | `TANGLE_VERBOSE=1` | Redacted HTTP request/response diagnostics only. This is separate from normal progress logging. |
63
63
  | `--ca-bundle` | Global CLI flag: path to a PEM CA bundle used as the TLS trust store for every transport. Overrides `TANGLE_API_CA_BUNDLE`. Place before the subcommand. |
@@ -230,6 +230,56 @@ uv run tangle sdk pipeline-runs submit --config submit.yaml
230
230
 
231
231
  For generated `tangle api` commands, config keys use generated CLI parameter names such as `base_url`, `schema_source`, `body`, and endpoint parameters like `limit`, `filter`, or `id`.
232
232
 
233
+ ### Environment-selected configs (`_select`)
234
+
235
+ Any command that accepts `--config` can pick one of several config documents from an environment variable by making `_select` the top-level node:
236
+
237
+ ```yaml
238
+ _shared: &shared
239
+ log_type: none
240
+
241
+ _select:
242
+ env: TANGLE_ENV
243
+ cases:
244
+ dev:
245
+ <<: *shared
246
+ base_url: https://api.dev
247
+ prod:
248
+ _defaults:
249
+ <<: *shared
250
+ base_url: https://api.prod
251
+ configs:
252
+ - filter: active
253
+ - filter: finished
254
+ ```
255
+
256
+ The selected branch is a complete config document — a single object, a list of objects, or `_defaults` + `configs` — and is then loaded exactly as if it had been written on its own. A branch may itself be another `_select` node, which composes multiple dimensions (for example environment and region).
257
+
258
+ Selection fails closed by default. To opt into a fallback, author an explicit `default` branch:
259
+
260
+ ```yaml
261
+ _select:
262
+ env: TANGLE_ENV
263
+ cases:
264
+ prod:
265
+ base_url: https://api.prod
266
+ default:
267
+ base_url: https://api.dev
268
+ ```
269
+
270
+ With `default`, an unset variable or a value matching no case resolves to that branch; an exact case match always wins over it. Without `default`, both remain errors. `default` is a sibling of `cases`, not an entry inside it: a case *named* `default` stays an ordinary exact-match case. Like any branch, `default` must be a complete valid config shape and may itself be another `_select`; an inner selector does not inherit the outer `default`.
271
+
272
+ Rules:
273
+
274
+ - `_select` is the only newly reserved key, and it is an exact key name rather than a prefix. Configs without `_select` are unchanged byte-for-byte and semantically.
275
+ - At a `_select` node, only `_select` and other underscore-prefixed helper keys (YAML anchor holders such as `_shared`) may appear; ordinary sibling keys are rejected.
276
+ - `_select` accepts only `env`, `cases`, and the optional `default`. There are no aliases: `else`, `fallback`, and `defaults` are rejected.
277
+ - The selector shape, the `env` name, every `cases` key, and every case and `default` branch are validated before the environment is read, so a malformed selector fails identically in every environment. Branches are checked as complete config documents with the same rules the loader applies to a whole file, and nested selectors are validated recursively.
278
+ - Only environment *lookups* are lazy. A dormant branch is fully shape-checked, but its `env` variable is never required unless that branch is actually selected. Command-specific field names and types are still validated later, against the selected branch only.
279
+ - Selector nesting is capped at 32 levels, which also stops a self-referential YAML alias. A node shared by several anchors is validated once, so anchor-heavy files stay fast.
280
+ - Matching uses `os.environ[NAME]` exactly: case sensitive, with no trimming, case folding, or interpolation.
281
+ - Fallback exists only where it is authored. Without `default`, an unset variable or an unmatched value is an error; there is never an implicit default or implicit production branch. The raw environment value is never echoed — diagnostics list only the configured case names.
282
+
233
283
  ## API schema cache and dynamic commands
234
284
 
235
285
  Refresh the local schema cache for a live backend with:
@@ -305,7 +355,7 @@ uv run tangle sdk published-components deprecate sha256:old --superseded-by sha2
305
355
 
306
356
  `publish` accepts `--image`, `--name`, `--description`, `--annotations` (JSON), `--dry-run`, `--published-by`, generic git metadata fields, generic API auth fields, `--log-type`, and `--config`. By default it scopes version checks and automatic old-version deprecation to the current authenticated user via `users_me()`; use `--published-by` to supply an explicit owner/publisher filter. Publishing fails closed if no owner can be determined.
307
357
 
308
- There is no separate OSS `publish-all` command. To publish multiple components, pass a YAML/JSON config list, or `_defaults` + `configs`, to the same `published-components publish` command; the command aggregates results and exits nonzero if any component errors.
358
+ There is no separate OSS `publish-all` command. To publish multiple components, pass a YAML/JSON config list, or `_defaults` + `configs`, to the same `published-components publish` command; the command aggregates results and exits nonzero if any component errors. A top-level `_select` node can choose between such documents per environment (see [Environment-selected configs](#environment-selected-configs-_select)).
309
359
 
310
360
  ```yaml
311
361
  _defaults:
@@ -336,6 +386,7 @@ Pipeline run API/submit commands live under `sdk pipeline-runs`:
336
386
  ```bash
337
387
  uv run tangle sdk pipeline-runs submit pipeline.yaml --dry-run
338
388
  uv run tangle sdk pipeline-runs submit pipeline.yaml --arg key=value --annotation owner=team
389
+ uv run tangle sdk pipeline-runs submit-from-python pipeline.py --arg key=value
339
390
  uv run tangle sdk pipeline-runs wait RUN_ID --max-wait 600 --poll-interval 10
340
391
  uv run tangle sdk pipeline-runs logs EXECUTION_ID
341
392
  uv run tangle sdk pipeline-runs annotations set RUN_ID key value
@@ -351,6 +402,59 @@ uv run tangle sdk pipelines compile pipeline.py -o pipeline.yaml
351
402
  uv run tangle sdk pipelines compile pipeline.py -o pipeline.yaml --pipeline pipeline_fn_name
352
403
  ```
353
404
 
405
+ To compile and submit in one step, without keeping the compiled YAML around, use
406
+ `pipeline-runs submit-from-python`:
407
+
408
+ ```bash
409
+ uv run tangle sdk pipeline-runs submit-from-python pipeline.py \
410
+ --override batch_size=100 \
411
+ --image eval-slim=registry.example/eval-slim@sha256:... \
412
+ --arg shop=acme --annotation owner=team
413
+ ```
414
+
415
+ It compiles the script, hydrates the bundle, submits the run, and removes the
416
+ compiled artifacts again — including on dry runs and failures. Its run-tier
417
+ flags are the same as `pipeline-runs submit`; the extra compile-tier flags are
418
+ `--pipeline`, repeatable `--override KEY=VALUE`, and repeatable `--image ID=REF`.
419
+ Use `pipelines compile` instead when the compiled YAML itself is what you want.
420
+
421
+ The two value tiers are distinct and easy to confuse:
422
+
423
+ | Flag | Tier | Meaning |
424
+ | --- | --- | --- |
425
+ | `--override KEY=VALUE` | compile | `cfg` value used while the graph is built |
426
+ | `--image ID=REF` | compile | resolves `@task(image_id=ID)` to a registry ref |
427
+ | `--arg` / `--args-json` / `--arg-secret` | run | pipeline arguments for the created run |
428
+
429
+ Notes:
430
+
431
+ - Hydration is always on: the compiled bundle references local sidecars
432
+ (`resolve://./<stem>.components.yaml#…`, `file://<stem>.subgraphs/…`) that the
433
+ server cannot read. There is no `--no-hydrate` here.
434
+ - The bundle is compiled next to the script (never in `/tmp`) under a unique
435
+ hidden `.tangle-submit-*.yaml` name, because relative refs such as
436
+ `ref(url="file://./component.yaml")` resolve against the *output* directory.
437
+ The name is allocated with an exclusive create, so concurrent compiles of the
438
+ same script never collide or overwrite a sibling YAML. A symlinked script
439
+ compiles next to its resolved target, where its `config.yaml` and relative
440
+ refs actually live.
441
+ - The bundle stays readable until every run has been submitted, so
442
+ run-lifecycle hooks still see an existing `pipeline_path`; cleanup then
443
+ removes exactly that stem's files (`.yaml`, `.components.yaml`,
444
+ `.subgraphs/`) and nothing else.
445
+ - Submission never waits; use `pipeline-runs wait RUN_ID`.
446
+ - With a multi-entry `--config` file, entries may carry different `--override` /
447
+ `--image` values, and **every entry is fully prepared before any run is
448
+ created**: each is compiled exactly once, hydrated, merged with its run
449
+ arguments and secrets, validated, and frozen into a submit body; only then are
450
+ the frozen bodies submitted in order (no recompilation). An unsupported config
451
+ key (`hydrate:`, `pipeline_path:`), a malformed `override` / `image` /
452
+ `arg-secret` value, an input given as both `--arg` and `--arg-secret`, a
453
+ missing script, or a compile/hydrate error in *any* entry therefore creates no
454
+ runs and leaves no artifacts behind. A runtime failure while submitting entry
455
+ N can still follow the successful submits of entries 1..N-1 — that is inherent
456
+ to creating N runs.
457
+
354
458
  A minimal graph uses `@pipeline` for the graph and `@task` for local Python components. `@task` functions are not executed at compile time; the compiler records call sites, emits a sibling `<output>.components.yaml` with `local_from_python` entries, and rewrites task component refs to that sidecar. Hydrate later regenerates the same component YAML from the Python source.
355
459
 
356
460
  ```python
@@ -14,6 +14,6 @@ from tangle_cli.dynamic_discovery_client import TangleDynamicDiscoveryClient
14
14
  try:
15
15
  __version__ = metadata_version("tangle-cli")
16
16
  except PackageNotFoundError:
17
- __version__ = "0.1.10"
17
+ __version__ = "0.1.12"
18
18
 
19
19
  __all__ = ["TangleDynamicDiscoveryClient", "__version__"]