telize 0.2.2__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 (104) hide show
  1. {telize-0.2.2 → telize-0.3.0}/PKG-INFO +48 -39
  2. {telize-0.2.2 → telize-0.3.0}/README.md +47 -38
  3. telize-0.3.0/examples/chat_input.yaml +26 -0
  4. {telize-0.2.2 → telize-0.3.0}/examples/llm_loop.yaml +0 -1
  5. {telize-0.2.2 → telize-0.3.0}/examples/llm_save_output.yaml +1 -1
  6. {telize-0.2.2 → telize-0.3.0}/examples/read_directory.yaml +1 -0
  7. {telize-0.2.2 → telize-0.3.0}/examples/spec_reference.yaml +5 -2
  8. {telize-0.2.2 → telize-0.3.0}/pyproject.toml +1 -1
  9. telize-0.3.0/src/telize/__version__.py +1 -0
  10. {telize-0.2.2 → telize-0.3.0}/src/telize/cli.py +2 -0
  11. {telize-0.2.2 → telize-0.3.0}/src/telize/config/models/__init__.py +2 -0
  12. {telize-0.2.2 → telize-0.3.0}/src/telize/config/models/actions.py +31 -11
  13. {telize-0.2.2 → telize-0.3.0}/src/telize/config/models/config.py +10 -0
  14. {telize-0.2.2 → telize-0.3.0}/src/telize/console/__init__.py +1 -1
  15. {telize-0.2.2 → telize-0.3.0}/src/telize/console/display.py +3 -10
  16. {telize-0.2.2 → telize-0.3.0}/src/telize/console/observer.py +37 -2
  17. telize-0.3.0/src/telize/console/terminal.py +12 -0
  18. {telize-0.2.2 → telize-0.3.0}/src/telize/runtime/actions/base.py +17 -0
  19. telize-0.3.0/src/telize/runtime/actions/chat.py +54 -0
  20. {telize-0.2.2 → telize-0.3.0}/src/telize/runtime/actions/input.py +3 -3
  21. {telize-0.2.2 → telize-0.3.0}/src/telize/runtime/actions/llm.py +3 -33
  22. {telize-0.2.2 → telize-0.3.0}/src/telize/runtime/actions/registry.py +2 -0
  23. {telize-0.2.2 → telize-0.3.0}/src/telize/runtime/context.py +3 -2
  24. {telize-0.2.2 → telize-0.3.0}/src/telize/runtime/observer.py +12 -0
  25. {telize-0.2.2 → telize-0.3.0}/src/telize/runtime/runner.py +83 -50
  26. {telize-0.2.2 → telize-0.3.0}/src/telize/runtime/state.py +1 -0
  27. telize-0.3.0/tests/fixtures/loop_workflow.yaml +18 -0
  28. telize-0.3.0/tests/test_chat_action.py +119 -0
  29. {telize-0.2.2 → telize-0.3.0}/tests/test_cli.py +31 -0
  30. {telize-0.2.2 → telize-0.3.0}/tests/test_console_display.py +4 -4
  31. telize-0.3.0/tests/test_console_observer.py +63 -0
  32. telize-0.3.0/tests/test_input_action.py +61 -0
  33. {telize-0.2.2 → telize-0.3.0}/tests/test_models.py +7 -0
  34. {telize-0.2.2 → telize-0.3.0}/tests/test_observer.py +36 -0
  35. telize-0.3.0/tests/test_repeat.py +196 -0
  36. telize-0.3.0/tests/test_runner.py +40 -0
  37. {telize-0.2.2 → telize-0.3.0}/tests/test_version.py +8 -1
  38. {telize-0.2.2 → telize-0.3.0}/uv.lock +1 -1
  39. telize-0.2.2/src/telize/__version__.py +0 -1
  40. telize-0.2.2/tests/test_console_observer.py +0 -35
  41. telize-0.2.2/tests/test_runner.py +0 -16
  42. {telize-0.2.2 → telize-0.3.0}/.github/workflows/ci.yml +0 -0
  43. {telize-0.2.2 → telize-0.3.0}/.github/workflows/publish.yml +0 -0
  44. {telize-0.2.2 → telize-0.3.0}/.gitignore +0 -0
  45. {telize-0.2.2 → telize-0.3.0}/.python-version +0 -0
  46. {telize-0.2.2 → telize-0.3.0}/CHANGELOG.md +0 -0
  47. {telize-0.2.2 → telize-0.3.0}/CONTRIBUTING.md +0 -0
  48. {telize-0.2.2 → telize-0.3.0}/LICENSE +0 -0
  49. {telize-0.2.2 → telize-0.3.0}/examples/call_subflow.yaml +0 -0
  50. {telize-0.2.2 → telize-0.3.0}/examples/data/notes/alpha.md +0 -0
  51. {telize-0.2.2 → telize-0.3.0}/examples/data/notes/beta.md +0 -0
  52. {telize-0.2.2 → telize-0.3.0}/examples/data/sample.txt +0 -0
  53. {telize-0.2.2 → telize-0.3.0}/examples/env_config.yaml +0 -0
  54. {telize-0.2.2 → telize-0.3.0}/examples/minimal_llm.yaml +0 -0
  55. {telize-0.2.2 → telize-0.3.0}/examples/multi_model.yaml +0 -0
  56. {telize-0.2.2 → telize-0.3.0}/examples/nested_workflow.yaml +0 -0
  57. {telize-0.2.2 → telize-0.3.0}/examples/nested_workflow_child.yaml +0 -0
  58. {telize-0.2.2 → telize-0.3.0}/examples/python_step.yaml +0 -0
  59. {telize-0.2.2 → telize-0.3.0}/examples/read_file.yaml +0 -0
  60. {telize-0.2.2 → telize-0.3.0}/examples/shell_to_llm.yaml +0 -0
  61. {telize-0.2.2 → telize-0.3.0}/examples/shell_with_env.yaml +0 -0
  62. {telize-0.2.2 → telize-0.3.0}/examples/show.gif +0 -0
  63. {telize-0.2.2 → telize-0.3.0}/src/telize/__init__.py +0 -0
  64. {telize-0.2.2 → telize-0.3.0}/src/telize/__main__.py +0 -0
  65. {telize-0.2.2 → telize-0.3.0}/src/telize/config/__init__.py +0 -0
  66. {telize-0.2.2 → telize-0.3.0}/src/telize/config/loader.py +0 -0
  67. {telize-0.2.2 → telize-0.3.0}/src/telize/config/models/flow.py +0 -0
  68. {telize-0.2.2 → telize-0.3.0}/src/telize/config/models/spec.py +0 -0
  69. {telize-0.2.2 → telize-0.3.0}/src/telize/exceptions.py +0 -0
  70. {telize-0.2.2 → telize-0.3.0}/src/telize/providers/__init__.py +0 -0
  71. {telize-0.2.2 → telize-0.3.0}/src/telize/providers/base.py +0 -0
  72. {telize-0.2.2 → telize-0.3.0}/src/telize/providers/openai.py +0 -0
  73. {telize-0.2.2 → telize-0.3.0}/src/telize/providers/registry.py +0 -0
  74. {telize-0.2.2 → telize-0.3.0}/src/telize/py.typed +0 -0
  75. {telize-0.2.2 → telize-0.3.0}/src/telize/runtime/__init__.py +0 -0
  76. {telize-0.2.2 → telize-0.3.0}/src/telize/runtime/actions/__init__.py +0 -0
  77. {telize-0.2.2 → telize-0.3.0}/src/telize/runtime/actions/python.py +0 -0
  78. {telize-0.2.2 → telize-0.3.0}/src/telize/runtime/actions/shell.py +0 -0
  79. {telize-0.2.2 → telize-0.3.0}/src/telize/runtime/actions/yaml.py +0 -0
  80. {telize-0.2.2 → telize-0.3.0}/src/telize/runtime/paths.py +0 -0
  81. {telize-0.2.2 → telize-0.3.0}/src/telize/runtime/planning.py +0 -0
  82. {telize-0.2.2 → telize-0.3.0}/src/telize/runtime/workflow_input.py +0 -0
  83. {telize-0.2.2 → telize-0.3.0}/src/telize/templating/__init__.py +0 -0
  84. {telize-0.2.2 → telize-0.3.0}/src/telize/templating/context.py +0 -0
  85. {telize-0.2.2 → telize-0.3.0}/src/telize/templating/load.py +0 -0
  86. {telize-0.2.2 → telize-0.3.0}/src/telize/templating/renderer.py +0 -0
  87. {telize-0.2.2 → telize-0.3.0}/tests/conftest.py +0 -0
  88. {telize-0.2.2 → telize-0.3.0}/tests/fixtures/cli_input_workflow.yaml +0 -0
  89. {telize-0.2.2 → telize-0.3.0}/tests/fixtures/external_child_workflow.yaml +0 -0
  90. {telize-0.2.2 → telize-0.3.0}/tests/fixtures/hello_agent_workflow.yaml +0 -0
  91. {telize-0.2.2 → telize-0.3.0}/tests/fixtures/invalid_duplicate_step.yaml +0 -0
  92. {telize-0.2.2 → telize-0.3.0}/tests/fixtures/minimal_workflow.yaml +0 -0
  93. {telize-0.2.2 → telize-0.3.0}/tests/fixtures/shell_only.yaml +0 -0
  94. {telize-0.2.2 → telize-0.3.0}/tests/fixtures/yaml_child_config_workflow.yaml +0 -0
  95. {telize-0.2.2 → telize-0.3.0}/tests/fixtures/yaml_input_workflow.yaml +0 -0
  96. {telize-0.2.2 → telize-0.3.0}/tests/test_llm_system_prompt.py +0 -0
  97. {telize-0.2.2 → telize-0.3.0}/tests/test_loader.py +0 -0
  98. {telize-0.2.2 → telize-0.3.0}/tests/test_loader_env.py +0 -0
  99. {telize-0.2.2 → telize-0.3.0}/tests/test_openai_provider.py +0 -0
  100. {telize-0.2.2 → telize-0.3.0}/tests/test_planning.py +0 -0
  101. {telize-0.2.2 → telize-0.3.0}/tests/test_templating.py +0 -0
  102. {telize-0.2.2 → telize-0.3.0}/tests/test_templating_load.py +0 -0
  103. {telize-0.2.2 → telize-0.3.0}/tests/test_workflow_input.py +0 -0
  104. {telize-0.2.2 → telize-0.3.0}/tests/test_yaml_action.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: telize
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Automate with flows, not loose prompts. Chain LLM, shell, and code in one YAML pipeline.
5
5
  Project-URL: Homepage, https://github.com/telize-ai/telize
6
6
  Project-URL: Documentation, https://github.com/telize-ai/telize#readme
@@ -36,50 +36,50 @@ Requires-Dist: ruff>=0.9; extra == 'dev'
36
36
  Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
37
37
  Description-Content-Type: text/markdown
38
38
 
39
- # Telize
39
+ # 🚀 Telize
40
40
 
41
41
  **Build reproducible, structured AI workflows with YAML and run them from your terminal, combining LLMs, shell, Python, and more—fully under your control.**
42
42
 
43
43
  Telize is a low-code framework for building agent-style pipelines: chain shell commands, file I/O, LLM calls, Python functions, and nested flows in a single workflow file. Configuration is validated before execution, and the CLI shows live progress as each step completes.
44
44
 
45
- ![Telize CLI demo](examples/show.gif)
45
+ ![Telize CLI demo](https://raw.githubusercontent.com/telize-ai/telize/main/examples/show.gif)
46
46
 
47
47
  [CI](https://github.com/telize-ai/telize/actions/workflows/ci.yml) · [Python 3.12+](https://www.python.org/downloads/) · [License](LICENSE)
48
48
 
49
49
  ---
50
50
 
51
- ## Table of contents
51
+ ## 🧭 Table of contents
52
52
 
53
- - [Features](#features)
54
- - [Requirements](#requirements)
55
- - [Installation](#installation)
56
- - [Quick start](#quick-start)
57
- - [Motivation](#-motivation)
58
- - [How it works](#how-it-works)
59
- - [Workflow reference](#workflow-reference)
60
- - [Examples](#examples)
61
- - [CLI](#cli)
62
- - [Development](#development)
63
- - [Contributing](#contributing)
64
- - [License](#license)
53
+ - [Features](#-features)
54
+ - [🧩 Requirements](#-requirements)
55
+ - [📦 Installation](#-installation)
56
+ - [Quick start](#-quick-start)
57
+ - [🚀 Motivation](#-motivation)
58
+ - [⚙️ How it works](#-how-it-works)
59
+ - [📚 Workflow reference](#-workflow-reference)
60
+ - [🧪 Examples](#-examples)
61
+ - [💻 CLI](#-cli)
62
+ - [🛠️ Development](#-development)
63
+ - [🤝 Contributing](#-contributing)
64
+ - [📄 License](#-license)
65
65
 
66
- ## Features
66
+ ## Features
67
67
 
68
68
  - **YAML workflows** — one file defines `config`, named `models`, flows, and steps
69
69
  - **Composable steps** — `input`, `llm`, `shell`, `python`, `flow`, and `yaml` actions
70
70
  - **Jinja templating** — wire step outputs together with `{{ steps.name.output }}`
71
- - **Loops and sub-flows** — iterate LLM steps over split lists; call nested flows with `uses: flow`
71
+ - **Loops and sub-flows** — add `loop` to any step to iterate it over split lists; call nested flows with `uses: flow`
72
72
  - **Validated upfront** — Pydantic models catch schema errors before any step runs
73
73
  - **Rich CLI output** — progress, step panels, and errors in the terminal
74
74
  - **OpenAI-compatible LLMs** — official OpenAI API or local [Ollama](https://ollama.com/) via the same client
75
75
 
76
- ## Requirements
76
+ ## 🧩 Requirements
77
77
 
78
78
  - **Python 3.12+**
79
79
  - **LLM endpoint** for `uses: llm` steps — [OpenAI](https://platform.openai.com/) or [Ollama](https://ollama.com/); set `api_url` on each model profile (default `http://localhost:11434`)
80
80
  - Optional: [uv](https://docs.astral.sh/uv/) for fast local development
81
81
 
82
- ## Installation
82
+ ## 📦 Installation
83
83
 
84
84
  ```bash
85
85
  pip install telize
@@ -100,7 +100,7 @@ Check the install:
100
100
  telize --version
101
101
  ```
102
102
 
103
- ## Quick start
103
+ ## Quick start
104
104
 
105
105
  **1.** For local models, start [Ollama](https://ollama.com/) and pull a model:
106
106
 
@@ -163,7 +163,7 @@ By treating LLMs as just another step in a standard automation pipeline, it brin
163
163
 
164
164
  ---
165
165
 
166
- ### Why it works:
166
+ ### 🧠 Why it works
167
167
  - **Deterministic Structure + Non-Deterministic AI:**
168
168
  It keeps the overall architecture _rigid and predictable_ (`YAML`), while allowing the AI to handle the _fuzzy, creative tasks_ (**text generation, summarization**) within strict boundaries.
169
169
  - **Upfront Validation:**
@@ -185,7 +185,7 @@ By treating LLMs as just another step in a standard automation pipeline, it brin
185
185
 
186
186
  ---
187
187
 
188
- ### Where Telize Might Struggle (_The Limitations_)
188
+ ### ⚠️ Where Telize Might Struggle (_The Limitations_)
189
189
  While it is great for **structured automation**, it isn’t a silver bullet:
190
190
 
191
191
  - **Dynamic Decision Making:**
@@ -195,7 +195,7 @@ While it is great for **structured automation**, it isn’t a silver bullet:
195
195
 
196
196
  ---
197
197
 
198
- ## How it works
198
+ ## ⚙️ How it works
199
199
 
200
200
  1. Telize loads your YAML and validates it against typed Pydantic models.
201
201
  2. The flow named in `config.entrypoint` runs first.
@@ -203,9 +203,9 @@ While it is great for **structured automation**, it isn’t a silver bullet:
203
203
  4. Later steps can reference earlier outputs via Jinja templates.
204
204
  5. The CLI prints progress and results as the workflow runs.
205
205
 
206
- ## Workflow reference
206
+ ## 📚 Workflow reference
207
207
 
208
- ### Top-level structure
208
+ ### 🧱 Top-level structure
209
209
 
210
210
  | Key | Description |
211
211
  | -------- | ------------------------------------------------------------------- |
@@ -213,13 +213,14 @@ While it is great for **structured automation**, it isn’t a silver bullet:
213
213
  | `models` | Named LLM profiles; referenced by `model:` on each `uses: llm` step |
214
214
  | `flows` | Named flows; `config.entrypoint` must match one of these keys |
215
215
 
216
- ### `config`
216
+ ### ⚙️ `config`
217
217
 
218
218
  | Field | Description |
219
219
  | ------------ | ------------------------------------------------- |
220
220
  | `entrypoint` | Name of the flow to run when the file is executed |
221
+ | `repeat` | Optional repeat interval in seconds. Omitted, `null`, or `-1`: run once. `0`: restart immediately after each run finishes. `N > 0`: restart `N` seconds after each run started; if a run exceeds `N` seconds, restart immediately when it finishes |
221
222
 
222
- ### `models`
223
+ ### 🤖 `models`
223
224
 
224
225
  Each key under `models` is a profile name (for example `default`, `creative`). LLM steps pick a profile with `model: <name>`.
225
226
 
@@ -258,24 +259,32 @@ models:
258
259
  api_url: http://{{ env.OLLAMA_HOST }}:11434
259
260
  ```
260
261
 
261
- ### Flow
262
+ ### 🌊 Flow
262
263
 
263
264
  | Field | Description |
264
265
  | ------- | --------------------------------------------------------- |
265
266
  | `steps` | List of steps (unique `name` per flow), executed in order |
266
267
 
267
- ### Steps (`uses`)
268
+ Every step also supports:
269
+
270
+ | Field | Description |
271
+ | ------------ | --------------------------------------------------------------------------- |
272
+ | `name` | Unique id within the flow; referenced as `{{ steps.<name>.output }}` |
273
+ | `output_to` | Optional path (relative to the workflow file); raw step output is written when the step finishes |
274
+ | `loop` | Optional; run the step once per item (`items` split by `split_by`, default `\n<|separator|>\n`), exposing each as `{{ item }}` and joining outputs with `separator` (default `\n<|separator|>\n`) |
275
+
276
+ ### 🪜 Steps (`uses`)
268
277
 
269
278
  | `uses` | Description |
270
279
  | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
271
- | `input` | Read a `file` or a `directory` (with glob `include`) |
272
- | `llm` | Send a `prompt` using a named `model` from `models`; optional `output_to`, `loop` |
280
+ | `input` | Read a `file` or a `directory` (with glob `include`; optional `separator` when joining directory files, default `\n<|separator|>\n`) |
281
+ | `llm` | Send a `prompt` using a named `model` from `models` |
273
282
  | `shell` | Run `run` commands; optional `envs` (supports templates) |
274
283
  | `python` | Call `call` (`module.function`) with `args` |
275
284
  | `flow` | Run another flow via `run` |
276
285
  | `yaml` | Run an external workflow from `file` (own `config`, `models`, and `flows`); optional `input` map passed to the child as `{{ input.key }}` |
277
286
 
278
- ### Templating
287
+ ### 🧩 Templating
279
288
 
280
289
  Telize uses [Jinja2](https://jinja.palletsprojects.com/) in step fields.
281
290
 
@@ -303,7 +312,7 @@ Example — chain a shell step into an LLM step:
303
312
  {{ steps.fetch_data.output }}
304
313
  ```
305
314
 
306
- ## Examples
315
+ ## 🧪 Examples
307
316
 
308
317
  | File | What it demonstrates |
309
318
  | ---------------------------------------------------------------- | -------------------------------------------------------- |
@@ -312,7 +321,7 @@ Example — chain a shell step into an LLM step:
312
321
  | [`examples/shell_to_llm.yaml`](examples/shell_to_llm.yaml) | Shell → LLM with `{{ steps.*.output }}` |
313
322
  | [`examples/read_file.yaml`](examples/read_file.yaml) | `uses: input` — single file |
314
323
  | [`examples/read_directory.yaml`](examples/read_directory.yaml) | `uses: input` — directory glob |
315
- | [`examples/llm_save_output.yaml`](examples/llm_save_output.yaml) | `output_to` — persist LLM text to disk |
324
+ | [`examples/llm_save_output.yaml`](examples/llm_save_output.yaml) | `output_to` — persist step output to disk |
316
325
  | [`examples/llm_loop.yaml`](examples/llm_loop.yaml) | `loop` — split output and iterate |
317
326
  | [`examples/call_subflow.yaml`](examples/call_subflow.yaml) | `uses: flow` — sub-flow in the same file |
318
327
  | [`examples/nested_workflow.yaml`](examples/nested_workflow.yaml) | `uses: yaml` — external workflow + `input` |
@@ -321,7 +330,7 @@ Example — chain a shell step into an LLM step:
321
330
  | [`examples/shell_with_env.yaml`](examples/shell_with_env.yaml) | Shell `envs` and load-time `{{ env.* }}` |
322
331
  | [`examples/env_config.yaml`](examples/env_config.yaml) | `{{ env.VAR }}` in the `models` section at load time |
323
332
 
324
- ## CLI
333
+ ## 💻 CLI
325
334
 
326
335
  ```
327
336
  usage: telize [-h] [--version] [-f FILE] [--validate-only]
@@ -333,7 +342,7 @@ options:
333
342
  --validate-only parse and validate without running steps
334
343
  ```
335
344
 
336
- ## Development
345
+ ## 🛠️ Development
337
346
 
338
347
  ```bash
339
348
  uv sync
@@ -345,11 +354,11 @@ uv run mypy
345
354
 
346
355
  See [CONTRIBUTING.md](CONTRIBUTING.md) for pull request guidelines and [CHANGELOG.md](CHANGELOG.md) for release notes.
347
356
 
348
- ## Contributing
357
+ ## 🤝 Contributing
349
358
 
350
359
  Contributions are welcome — bug reports, docs, and pull requests. Please read [CONTRIBUTING.md](CONTRIBUTING.md) and open an [issue](https://github.com/telize-ai/telize/issues) before large changes.
351
360
 
352
- ## License
361
+ ## 📄 License
353
362
 
354
363
  Apache License 2.0 — see [LICENSE](LICENSE).
355
364
 
@@ -1,47 +1,47 @@
1
- # Telize
1
+ # 🚀 Telize
2
2
 
3
3
  **Build reproducible, structured AI workflows with YAML and run them from your terminal, combining LLMs, shell, Python, and more—fully under your control.**
4
4
 
5
5
  Telize is a low-code framework for building agent-style pipelines: chain shell commands, file I/O, LLM calls, Python functions, and nested flows in a single workflow file. Configuration is validated before execution, and the CLI shows live progress as each step completes.
6
6
 
7
- ![Telize CLI demo](examples/show.gif)
7
+ ![Telize CLI demo](https://raw.githubusercontent.com/telize-ai/telize/main/examples/show.gif)
8
8
 
9
9
  [CI](https://github.com/telize-ai/telize/actions/workflows/ci.yml) · [Python 3.12+](https://www.python.org/downloads/) · [License](LICENSE)
10
10
 
11
11
  ---
12
12
 
13
- ## Table of contents
13
+ ## 🧭 Table of contents
14
14
 
15
- - [Features](#features)
16
- - [Requirements](#requirements)
17
- - [Installation](#installation)
18
- - [Quick start](#quick-start)
19
- - [Motivation](#-motivation)
20
- - [How it works](#how-it-works)
21
- - [Workflow reference](#workflow-reference)
22
- - [Examples](#examples)
23
- - [CLI](#cli)
24
- - [Development](#development)
25
- - [Contributing](#contributing)
26
- - [License](#license)
15
+ - [Features](#-features)
16
+ - [🧩 Requirements](#-requirements)
17
+ - [📦 Installation](#-installation)
18
+ - [Quick start](#-quick-start)
19
+ - [🚀 Motivation](#-motivation)
20
+ - [⚙️ How it works](#-how-it-works)
21
+ - [📚 Workflow reference](#-workflow-reference)
22
+ - [🧪 Examples](#-examples)
23
+ - [💻 CLI](#-cli)
24
+ - [🛠️ Development](#-development)
25
+ - [🤝 Contributing](#-contributing)
26
+ - [📄 License](#-license)
27
27
 
28
- ## Features
28
+ ## Features
29
29
 
30
30
  - **YAML workflows** — one file defines `config`, named `models`, flows, and steps
31
31
  - **Composable steps** — `input`, `llm`, `shell`, `python`, `flow`, and `yaml` actions
32
32
  - **Jinja templating** — wire step outputs together with `{{ steps.name.output }}`
33
- - **Loops and sub-flows** — iterate LLM steps over split lists; call nested flows with `uses: flow`
33
+ - **Loops and sub-flows** — add `loop` to any step to iterate it over split lists; call nested flows with `uses: flow`
34
34
  - **Validated upfront** — Pydantic models catch schema errors before any step runs
35
35
  - **Rich CLI output** — progress, step panels, and errors in the terminal
36
36
  - **OpenAI-compatible LLMs** — official OpenAI API or local [Ollama](https://ollama.com/) via the same client
37
37
 
38
- ## Requirements
38
+ ## 🧩 Requirements
39
39
 
40
40
  - **Python 3.12+**
41
41
  - **LLM endpoint** for `uses: llm` steps — [OpenAI](https://platform.openai.com/) or [Ollama](https://ollama.com/); set `api_url` on each model profile (default `http://localhost:11434`)
42
42
  - Optional: [uv](https://docs.astral.sh/uv/) for fast local development
43
43
 
44
- ## Installation
44
+ ## 📦 Installation
45
45
 
46
46
  ```bash
47
47
  pip install telize
@@ -62,7 +62,7 @@ Check the install:
62
62
  telize --version
63
63
  ```
64
64
 
65
- ## Quick start
65
+ ## Quick start
66
66
 
67
67
  **1.** For local models, start [Ollama](https://ollama.com/) and pull a model:
68
68
 
@@ -125,7 +125,7 @@ By treating LLMs as just another step in a standard automation pipeline, it brin
125
125
 
126
126
  ---
127
127
 
128
- ### Why it works:
128
+ ### 🧠 Why it works
129
129
  - **Deterministic Structure + Non-Deterministic AI:**
130
130
  It keeps the overall architecture _rigid and predictable_ (`YAML`), while allowing the AI to handle the _fuzzy, creative tasks_ (**text generation, summarization**) within strict boundaries.
131
131
  - **Upfront Validation:**
@@ -147,7 +147,7 @@ By treating LLMs as just another step in a standard automation pipeline, it brin
147
147
 
148
148
  ---
149
149
 
150
- ### Where Telize Might Struggle (_The Limitations_)
150
+ ### ⚠️ Where Telize Might Struggle (_The Limitations_)
151
151
  While it is great for **structured automation**, it isn’t a silver bullet:
152
152
 
153
153
  - **Dynamic Decision Making:**
@@ -157,7 +157,7 @@ While it is great for **structured automation**, it isn’t a silver bullet:
157
157
 
158
158
  ---
159
159
 
160
- ## How it works
160
+ ## ⚙️ How it works
161
161
 
162
162
  1. Telize loads your YAML and validates it against typed Pydantic models.
163
163
  2. The flow named in `config.entrypoint` runs first.
@@ -165,9 +165,9 @@ While it is great for **structured automation**, it isn’t a silver bullet:
165
165
  4. Later steps can reference earlier outputs via Jinja templates.
166
166
  5. The CLI prints progress and results as the workflow runs.
167
167
 
168
- ## Workflow reference
168
+ ## 📚 Workflow reference
169
169
 
170
- ### Top-level structure
170
+ ### 🧱 Top-level structure
171
171
 
172
172
  | Key | Description |
173
173
  | -------- | ------------------------------------------------------------------- |
@@ -175,13 +175,14 @@ While it is great for **structured automation**, it isn’t a silver bullet:
175
175
  | `models` | Named LLM profiles; referenced by `model:` on each `uses: llm` step |
176
176
  | `flows` | Named flows; `config.entrypoint` must match one of these keys |
177
177
 
178
- ### `config`
178
+ ### ⚙️ `config`
179
179
 
180
180
  | Field | Description |
181
181
  | ------------ | ------------------------------------------------- |
182
182
  | `entrypoint` | Name of the flow to run when the file is executed |
183
+ | `repeat` | Optional repeat interval in seconds. Omitted, `null`, or `-1`: run once. `0`: restart immediately after each run finishes. `N > 0`: restart `N` seconds after each run started; if a run exceeds `N` seconds, restart immediately when it finishes |
183
184
 
184
- ### `models`
185
+ ### 🤖 `models`
185
186
 
186
187
  Each key under `models` is a profile name (for example `default`, `creative`). LLM steps pick a profile with `model: <name>`.
187
188
 
@@ -220,24 +221,32 @@ models:
220
221
  api_url: http://{{ env.OLLAMA_HOST }}:11434
221
222
  ```
222
223
 
223
- ### Flow
224
+ ### 🌊 Flow
224
225
 
225
226
  | Field | Description |
226
227
  | ------- | --------------------------------------------------------- |
227
228
  | `steps` | List of steps (unique `name` per flow), executed in order |
228
229
 
229
- ### Steps (`uses`)
230
+ Every step also supports:
231
+
232
+ | Field | Description |
233
+ | ------------ | --------------------------------------------------------------------------- |
234
+ | `name` | Unique id within the flow; referenced as `{{ steps.<name>.output }}` |
235
+ | `output_to` | Optional path (relative to the workflow file); raw step output is written when the step finishes |
236
+ | `loop` | Optional; run the step once per item (`items` split by `split_by`, default `\n<|separator|>\n`), exposing each as `{{ item }}` and joining outputs with `separator` (default `\n<|separator|>\n`) |
237
+
238
+ ### 🪜 Steps (`uses`)
230
239
 
231
240
  | `uses` | Description |
232
241
  | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
233
- | `input` | Read a `file` or a `directory` (with glob `include`) |
234
- | `llm` | Send a `prompt` using a named `model` from `models`; optional `output_to`, `loop` |
242
+ | `input` | Read a `file` or a `directory` (with glob `include`; optional `separator` when joining directory files, default `\n<|separator|>\n`) |
243
+ | `llm` | Send a `prompt` using a named `model` from `models` |
235
244
  | `shell` | Run `run` commands; optional `envs` (supports templates) |
236
245
  | `python` | Call `call` (`module.function`) with `args` |
237
246
  | `flow` | Run another flow via `run` |
238
247
  | `yaml` | Run an external workflow from `file` (own `config`, `models`, and `flows`); optional `input` map passed to the child as `{{ input.key }}` |
239
248
 
240
- ### Templating
249
+ ### 🧩 Templating
241
250
 
242
251
  Telize uses [Jinja2](https://jinja.palletsprojects.com/) in step fields.
243
252
 
@@ -265,7 +274,7 @@ Example — chain a shell step into an LLM step:
265
274
  {{ steps.fetch_data.output }}
266
275
  ```
267
276
 
268
- ## Examples
277
+ ## 🧪 Examples
269
278
 
270
279
  | File | What it demonstrates |
271
280
  | ---------------------------------------------------------------- | -------------------------------------------------------- |
@@ -274,7 +283,7 @@ Example — chain a shell step into an LLM step:
274
283
  | [`examples/shell_to_llm.yaml`](examples/shell_to_llm.yaml) | Shell → LLM with `{{ steps.*.output }}` |
275
284
  | [`examples/read_file.yaml`](examples/read_file.yaml) | `uses: input` — single file |
276
285
  | [`examples/read_directory.yaml`](examples/read_directory.yaml) | `uses: input` — directory glob |
277
- | [`examples/llm_save_output.yaml`](examples/llm_save_output.yaml) | `output_to` — persist LLM text to disk |
286
+ | [`examples/llm_save_output.yaml`](examples/llm_save_output.yaml) | `output_to` — persist step output to disk |
278
287
  | [`examples/llm_loop.yaml`](examples/llm_loop.yaml) | `loop` — split output and iterate |
279
288
  | [`examples/call_subflow.yaml`](examples/call_subflow.yaml) | `uses: flow` — sub-flow in the same file |
280
289
  | [`examples/nested_workflow.yaml`](examples/nested_workflow.yaml) | `uses: yaml` — external workflow + `input` |
@@ -283,7 +292,7 @@ Example — chain a shell step into an LLM step:
283
292
  | [`examples/shell_with_env.yaml`](examples/shell_with_env.yaml) | Shell `envs` and load-time `{{ env.* }}` |
284
293
  | [`examples/env_config.yaml`](examples/env_config.yaml) | `{{ env.VAR }}` in the `models` section at load time |
285
294
 
286
- ## CLI
295
+ ## 💻 CLI
287
296
 
288
297
  ```
289
298
  usage: telize [-h] [--version] [-f FILE] [--validate-only]
@@ -295,7 +304,7 @@ options:
295
304
  --validate-only parse and validate without running steps
296
305
  ```
297
306
 
298
- ## Development
307
+ ## 🛠️ Development
299
308
 
300
309
  ```bash
301
310
  uv sync
@@ -307,11 +316,11 @@ uv run mypy
307
316
 
308
317
  See [CONTRIBUTING.md](CONTRIBUTING.md) for pull request guidelines and [CHANGELOG.md](CHANGELOG.md) for release notes.
309
318
 
310
- ## Contributing
319
+ ## 🤝 Contributing
311
320
 
312
321
  Contributions are welcome — bug reports, docs, and pull requests. Please read [CONTRIBUTING.md](CONTRIBUTING.md) and open an [issue](https://github.com/telize-ai/telize/issues) before large changes.
313
322
 
314
- ## License
323
+ ## 📄 License
315
324
 
316
325
  Apache License 2.0 — see [LICENSE](LICENSE).
317
326
 
@@ -0,0 +1,26 @@
1
+ # Pause the workflow and ask the user for input with uses: chat.
2
+ config:
3
+ entrypoint: main
4
+
5
+ models:
6
+ default:
7
+ provider: openai
8
+ model: gpt-4o-mini
9
+ api_url: https://api.openai.com/v1
10
+
11
+ flows:
12
+ main:
13
+ steps:
14
+ - name: user_chat
15
+ uses: chat
16
+ message: |
17
+ What would you like help with today?
18
+
19
+ - name: respond
20
+ uses: llm
21
+ model: default
22
+ prompt: |
23
+ The user said:
24
+ {{ steps.user_chat.output }}
25
+
26
+ Reply briefly and helpfully.
@@ -28,6 +28,5 @@ flows:
28
28
  loop:
29
29
  items: "{{ steps.topics.output }}"
30
30
  split_by: ","
31
- execution: sequential
32
31
  prompt: |
33
32
  Reply with one short hashtag for the topic "{{ item }}" (no explanation).
@@ -1,4 +1,4 @@
1
- # Write LLM output to disk with output_to (path relative to this file).
1
+ # Write step output to disk with output_to (path relative to this file).
2
2
  #
3
3
  # Run:
4
4
  # telize -f examples/llm_save_output.yaml
@@ -22,6 +22,7 @@ flows:
22
22
  directory:
23
23
  path: ./data/notes
24
24
  include: "*.md"
25
+ separator: "\n\n===\n\n"
25
26
 
26
27
  - name: merge_summary
27
28
  uses: llm
@@ -15,6 +15,10 @@
15
15
  config:
16
16
  # entrypoint — name of the flow that runs when you execute this file
17
17
  entrypoint: release_pipeline
18
+ # repeat — optional; omit, null, or -1 to run once. 0 restarts immediately after
19
+ # each run finishes. N>0 restarts N seconds after each run started (or immediately
20
+ # if the run took longer than N seconds).
21
+ # repeat: 0
18
22
 
19
23
  # models — named LLM profiles referenced by `model:` on llm steps
20
24
  models:
@@ -63,7 +67,7 @@ flows:
63
67
 
64
68
  Draft Documents:
65
69
  {{ steps.fetch_context_docs.output }}
66
- # output_to — optional path; raw LLM text is written when the step finishes
70
+ # output_to — optional path; raw step output is written when the step finishes
67
71
  output_to: ./reports/documentation_gap_analysis.md
68
72
 
69
73
  - name: generate_marketing_copy
@@ -89,7 +93,6 @@ flows:
89
93
  loop:
90
94
  items: "{{ steps.get_keywords.output }}"
91
95
  split_by: ","
92
- execution: sequential # or parallel
93
96
  prompt: |
94
97
  Create 2 social hashtags for this topic: {{ item }}
95
98
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "telize"
3
- version = "0.2.2"
3
+ version = "0.3.0"
4
4
  description = "Automate with flows, not loose prompts. Chain LLM, shell, and code in one YAML pipeline."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -0,0 +1 @@
1
+ __version__ = "0.3.0"
@@ -109,6 +109,8 @@ def main(argv: list[str] | None = None) -> None:
109
109
  observer=observer,
110
110
  workflow_input=workflow_input,
111
111
  ).run()
112
+ except KeyboardInterrupt:
113
+ sys.exit(130)
112
114
  except (ConfigError, ExecutionError) as exc:
113
115
  _print_error(str(exc))
114
116
  sys.exit(1)
@@ -1,4 +1,5 @@
1
1
  from telize.config.models.actions import (
2
+ ChatStep,
2
3
  FlowRefStep,
3
4
  InputStep,
4
5
  LlmStep,
@@ -13,6 +14,7 @@ from telize.config.models.flow import Flow
13
14
  from telize.config.models.spec import WorkflowSpec
14
15
 
15
16
  __all__ = [
17
+ "ChatStep",
16
18
  "Flow",
17
19
  "FlowRefStep",
18
20
  "GlobalConfig",
@@ -6,7 +6,7 @@ from pydantic import BaseModel, ConfigDict, Field, model_validator
6
6
 
7
7
 
8
8
  class LoopConfig(BaseModel):
9
- """Iterate an LLM step over items produced from a prior step's output."""
9
+ """Iterate a step over items produced from a prior step's output."""
10
10
 
11
11
  model_config = ConfigDict(extra="forbid")
12
12
 
@@ -14,12 +14,12 @@ class LoopConfig(BaseModel):
14
14
  description="Jinja template resolving to a delimited list (e.g. `{{ steps.foo.output }}`).",
15
15
  )
16
16
  split_by: str = Field(
17
- default=",",
17
+ default="\n<|separator|>\n",
18
18
  description="Delimiter used to split `items` into separate loop iterations.",
19
19
  )
20
- execution: Literal["sequential", "parallel"] = Field(
21
- default="sequential",
22
- description="How loop iterations are scheduled.",
20
+ separator: str = Field(
21
+ default="\n<|separator|>\n",
22
+ description="String inserted between each iteration's output when joining results.",
23
23
  )
24
24
 
25
25
 
@@ -30,6 +30,10 @@ class DirectoryInput(BaseModel):
30
30
 
31
31
  path: str
32
32
  include: str = Field(default="*", description="Glob pattern for files to include.")
33
+ separator: str = Field(
34
+ default="\n<|separator|>\n",
35
+ description="String inserted between each file section when joining.",
36
+ )
33
37
 
34
38
 
35
39
  class _StepBase(BaseModel):
@@ -41,6 +45,17 @@ class _StepBase(BaseModel):
41
45
  min_length=1,
42
46
  description="Unique step id within the flow; referenced as `steps.<name>.output`.",
43
47
  )
48
+ output_to: str | None = Field(
49
+ default=None,
50
+ description="Optional path to write raw step output when the step completes.",
51
+ )
52
+ loop: LoopConfig | None = Field(
53
+ default=None,
54
+ description=(
55
+ "Optional loop config; runs the step once per item, exposing the "
56
+ "current value as `{{ item }}` and joining outputs with `separator`."
57
+ ),
58
+ )
44
59
 
45
60
 
46
61
  class InputStep(_StepBase):
@@ -68,11 +83,6 @@ class LlmStep(_StepBase):
68
83
  description="Name of a model defined in the top-level `models` mapping.",
69
84
  )
70
85
  prompt: str
71
- output_to: str | None = Field(
72
- default=None,
73
- description="Optional path to write raw output after the step completes.",
74
- )
75
- loop: LoopConfig | None = None
76
86
 
77
87
 
78
88
  class ShellStep(_StepBase):
@@ -106,6 +116,16 @@ class FlowRefStep(_StepBase):
106
116
  run: str = Field(description="Name of the flow to execute.")
107
117
 
108
118
 
119
+ class ChatStep(_StepBase):
120
+ """Prompt the user for input interactively in the terminal."""
121
+
122
+ uses: Literal["chat"] = "chat"
123
+ message: str = Field(
124
+ default="",
125
+ description="Optional message shown before collecting user input (supports Jinja).",
126
+ )
127
+
128
+
109
129
  class YamlStep(_StepBase):
110
130
  """Run a workflow defined in an external YAML file."""
111
131
 
@@ -123,6 +143,6 @@ class YamlStep(_StepBase):
123
143
 
124
144
 
125
145
  Step = Annotated[
126
- InputStep | LlmStep | ShellStep | PythonStep | FlowRefStep | YamlStep,
146
+ InputStep | LlmStep | ShellStep | PythonStep | FlowRefStep | ChatStep | YamlStep,
127
147
  Field(discriminator="uses"),
128
148
  ]