ellements 0.2.0__py3-none-any.whl

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 (130) hide show
  1. ellements/__init__.py +57 -0
  2. ellements/agents/__init__.py +45 -0
  3. ellements/agents/backend.py +100 -0
  4. ellements/agents/builder.py +303 -0
  5. ellements/agents/claude_backend.py +200 -0
  6. ellements/agents/controller.py +237 -0
  7. ellements/agents/openai_backend.py +187 -0
  8. ellements/agents/py.typed +0 -0
  9. ellements/agents/runner.py +358 -0
  10. ellements/agents/tools.py +30 -0
  11. ellements/benchmarking/__init__.py +52 -0
  12. ellements/benchmarking/harness.py +342 -0
  13. ellements/benchmarking/py.typed +0 -0
  14. ellements/benchmarking/results.py +173 -0
  15. ellements/cli/__init__.py +80 -0
  16. ellements/cli/adapters.py +73 -0
  17. ellements/cli/agent_tui.py +1178 -0
  18. ellements/cli/components.py +411 -0
  19. ellements/cli/printer.py +229 -0
  20. ellements/cli/py.typed +0 -0
  21. ellements/core/__init__.py +112 -0
  22. ellements/core/async_utils.py +42 -0
  23. ellements/core/budgeting/__init__.py +43 -0
  24. ellements/core/budgeting/client.py +276 -0
  25. ellements/core/budgeting/protocol.py +51 -0
  26. ellements/core/budgeting/trackers.py +177 -0
  27. ellements/core/caching/__init__.py +51 -0
  28. ellements/core/caching/cache.py +73 -0
  29. ellements/core/caching/client.py +300 -0
  30. ellements/core/caching/disk.py +128 -0
  31. ellements/core/caching/keys.py +97 -0
  32. ellements/core/caching/memory.py +104 -0
  33. ellements/core/chunking.py +262 -0
  34. ellements/core/config.py +180 -0
  35. ellements/core/exceptions.py +145 -0
  36. ellements/core/llm/__init__.py +46 -0
  37. ellements/core/llm/client.py +1124 -0
  38. ellements/core/llm/images.py +226 -0
  39. ellements/core/llm/messages.py +202 -0
  40. ellements/core/llm/model_params.py +66 -0
  41. ellements/core/llm/protocol.py +146 -0
  42. ellements/core/llm/requests.py +100 -0
  43. ellements/core/llm/structured.py +91 -0
  44. ellements/core/llm/wrapper.py +79 -0
  45. ellements/core/observability/__init__.py +39 -0
  46. ellements/core/observability/events.py +169 -0
  47. ellements/core/observability/jsonl_logger.py +244 -0
  48. ellements/core/observability/markdown_formatter.py +197 -0
  49. ellements/core/observability/observer.py +56 -0
  50. ellements/core/prompting/__init__.py +14 -0
  51. ellements/core/prompting/context.py +185 -0
  52. ellements/core/prompting/guideline.py +133 -0
  53. ellements/core/prompting/persona.py +267 -0
  54. ellements/core/prompting/sources.py +92 -0
  55. ellements/core/py.typed +0 -0
  56. ellements/core/rate_limit/__init__.py +44 -0
  57. ellements/core/rate_limit/bucket.py +85 -0
  58. ellements/core/rate_limit/client.py +216 -0
  59. ellements/core/rate_limit/protocol.py +27 -0
  60. ellements/core/templating.py +126 -0
  61. ellements/core/tools/__init__.py +51 -0
  62. ellements/core/tools/dialects.py +136 -0
  63. ellements/core/tools/executor.py +48 -0
  64. ellements/core/tools/protocol.py +36 -0
  65. ellements/core/tools/records.py +28 -0
  66. ellements/core/tools/registry.py +205 -0
  67. ellements/core/tools/schemas.py +80 -0
  68. ellements/core/tools/simple.py +119 -0
  69. ellements/core/tools/spec.py +33 -0
  70. ellements/domain_specific/__init__.py +7 -0
  71. ellements/domain_specific/finance/__init__.py +188 -0
  72. ellements/domain_specific/finance/calculations.py +837 -0
  73. ellements/domain_specific/finance/charts.py +426 -0
  74. ellements/domain_specific/finance/portfolio.py +129 -0
  75. ellements/domain_specific/finance/quant_analysis.py +279 -0
  76. ellements/domain_specific/finance/risk.py +362 -0
  77. ellements/domain_specific/finance/technical_indicators.py +241 -0
  78. ellements/domain_specific/finance/tools.py +483 -0
  79. ellements/domain_specific/finance/valuation.py +312 -0
  80. ellements/domain_specific/finance/yahoo_finance.py +1523 -0
  81. ellements/domain_specific/finance/yahoo_finance_models.py +321 -0
  82. ellements/domain_specific/py.typed +0 -0
  83. ellements/execution/__init__.py +56 -0
  84. ellements/execution/callbacks.py +149 -0
  85. ellements/execution/catalog.py +70 -0
  86. ellements/execution/collaborative.py +191 -0
  87. ellements/execution/config.py +135 -0
  88. ellements/execution/py.typed +0 -0
  89. ellements/execution/reflection.py +156 -0
  90. ellements/execution/self_consistency.py +189 -0
  91. ellements/execution/single_call.py +48 -0
  92. ellements/execution/strategies.py +237 -0
  93. ellements/execution/tree_of_thought.py +541 -0
  94. ellements/fslm/__init__.py +108 -0
  95. ellements/fslm/builtins.py +103 -0
  96. ellements/fslm/cli.py +199 -0
  97. ellements/fslm/context.py +50 -0
  98. ellements/fslm/definition.py +163 -0
  99. ellements/fslm/det.py +173 -0
  100. ellements/fslm/dsl.py +458 -0
  101. ellements/fslm/errors.py +38 -0
  102. ellements/fslm/evaluators.py +141 -0
  103. ellements/fslm/kernel.py +603 -0
  104. ellements/fslm/loading.py +123 -0
  105. ellements/fslm/models.py +623 -0
  106. ellements/fslm/nl.py +87 -0
  107. ellements/fslm/observers.py +107 -0
  108. ellements/fslm/persistence.py +72 -0
  109. ellements/fslm/py.typed +0 -0
  110. ellements/fslm/rendering.py +551 -0
  111. ellements/fslm/visualization.py +25 -0
  112. ellements/reporting/__init__.py +12 -0
  113. ellements/reporting/charts.py +364 -0
  114. ellements/reporting/html_generation.py +132 -0
  115. ellements/reporting/py.typed +0 -0
  116. ellements/reporting/visualization.py +73 -0
  117. ellements/standard_tools/__init__.py +22 -0
  118. ellements/standard_tools/py.typed +0 -0
  119. ellements/standard_tools/terminal.py +205 -0
  120. ellements/standard_tools/web/__init__.py +37 -0
  121. ellements/standard_tools/web/browser_viewer.py +214 -0
  122. ellements/standard_tools/web/crawler.py +454 -0
  123. ellements/standard_tools/web/search.py +399 -0
  124. ellements/standard_tools/web/youtube.py +1297 -0
  125. ellements-0.2.0.dist-info/METADATA +368 -0
  126. ellements-0.2.0.dist-info/RECORD +130 -0
  127. ellements-0.2.0.dist-info/WHEEL +5 -0
  128. ellements-0.2.0.dist-info/entry_points.txt +2 -0
  129. ellements-0.2.0.dist-info/licenses/LICENSE +42 -0
  130. ellements-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,411 @@
1
+ """Low-level Rich building blocks for CLI applications.
2
+
3
+ Pure functions with no shared state. Each accepts an optional *console*
4
+ parameter so callers can direct output to stderr (default) or stdout.
5
+
6
+ Usage::
7
+
8
+ from ellements.cli import app_header, key_value_table
9
+
10
+ app_header("My App", {"Model": "gpt-4o"}, icon="🚀")
11
+ key_value_table({"topic": "cats"}, title="Variables")
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json as _json
17
+ from collections.abc import Sequence
18
+ from typing import Any
19
+
20
+ from rich import box
21
+ from rich.console import Console
22
+ from rich.markdown import Markdown
23
+ from rich.panel import Panel
24
+ from rich.rule import Rule
25
+ from rich.table import Table
26
+ from rich.text import Text
27
+
28
+ # Default console writes to stderr so stdout stays clean for data output
29
+ _default_console = Console(stderr=True)
30
+
31
+ # ── Issue / severity styling ──────────────────────────────────────────
32
+
33
+ ISSUE_STYLES: dict[str, tuple[str, str]] = {
34
+ "warning": ("⚠️ ", "yellow"),
35
+ "error": ("❌ ", "red"),
36
+ "suggestion": ("💡 ", "cyan"),
37
+ "info": ("ℹ️ ", "dim"),
38
+ }
39
+
40
+
41
+ # ── Building-block functions ──────────────────────────────────────────
42
+
43
+ def app_header(
44
+ title: str,
45
+ subtitle_items: dict[str, str] | None = None,
46
+ *,
47
+ icon: str = "",
48
+ border_style: str = "bright_blue",
49
+ console: Console | None = None,
50
+ ) -> None:
51
+ """Render a styled application header panel.
52
+
53
+ Args:
54
+ title: Application name.
55
+ subtitle_items: Key-value metadata pairs shown below the title
56
+ (e.g. ``{"Model": "gpt-4o", "Spec": "file.md"}``).
57
+ icon: Emoji or symbol prepended to the title.
58
+ border_style: Rich style for the panel border.
59
+ console: Console instance (defaults to stderr).
60
+ """
61
+ c = console or _default_console
62
+ display_title = f"{icon} {title}".strip() if icon else title
63
+ title_text = Text(display_title, style="bold bright_white")
64
+
65
+ if subtitle_items:
66
+ parts = [
67
+ f"[dim]{k}:[/dim] [bright_cyan]{v}[/]"
68
+ for k, v in subtitle_items.items()
69
+ ]
70
+ subtitle = " · ".join(parts)
71
+ else:
72
+ subtitle = ""
73
+
74
+ c.print(Panel(
75
+ subtitle,
76
+ title=title_text,
77
+ title_align="left",
78
+ border_style=border_style,
79
+ box=box.DOUBLE,
80
+ padding=(0, 2),
81
+ ))
82
+
83
+
84
+ def key_value_table(
85
+ data: dict[str, Any],
86
+ *,
87
+ title: str = "",
88
+ name_style: str = "bright_cyan",
89
+ value_style: str = "white",
90
+ console: Console | None = None,
91
+ ) -> None:
92
+ """Render a two-column key→value table.
93
+
94
+ Booleans are auto-formatted as colored ``true``/``false``.
95
+
96
+ Args:
97
+ data: Mapping of names to values.
98
+ title: Optional table title.
99
+ name_style: Rich style for the name column.
100
+ value_style: Rich style for the value column.
101
+ console: Console instance (defaults to stderr).
102
+ """
103
+ if not data:
104
+ return
105
+ c = console or _default_console
106
+
107
+ table = Table(
108
+ title=title or None,
109
+ title_style="bold",
110
+ box=box.ROUNDED,
111
+ border_style="dim",
112
+ show_lines=False,
113
+ padding=(0, 1),
114
+ )
115
+ table.add_column("Name", style=name_style, no_wrap=True)
116
+ table.add_column("Value", style=value_style)
117
+
118
+ for key, val in data.items():
119
+ if isinstance(val, bool):
120
+ val_str = "[green]true[/]" if val else "[red]false[/]"
121
+ else:
122
+ val_str = str(val)
123
+ table.add_row(f"[bold]{key}[/]", val_str)
124
+
125
+ c.print(table)
126
+ c.print()
127
+
128
+
129
+ def section_rule(
130
+ title: str,
131
+ *,
132
+ style: str = "bright_green",
133
+ console: Console | None = None,
134
+ ) -> None:
135
+ """Render a horizontal rule with a bold section title."""
136
+ c = console or _default_console
137
+ c.print(Rule(f"[bold]{title}", style=style))
138
+ c.print()
139
+
140
+
141
+ def stats_footer(
142
+ stats: dict[str, str | int | float],
143
+ *,
144
+ border_style: str = "dim",
145
+ console: Console | None = None,
146
+ ) -> None:
147
+ """Render a compact stats panel (key·value pairs on one line)."""
148
+ c = console or _default_console
149
+ parts = [
150
+ f"[dim]{k}:[/] [cyan]{v}[/]"
151
+ for k, v in stats.items()
152
+ ]
153
+ c.print(Panel(
154
+ " · ".join(parts),
155
+ border_style=border_style,
156
+ box=box.ROUNDED,
157
+ padding=(0, 1),
158
+ ))
159
+
160
+
161
+ def issue_line(
162
+ issue_type: str,
163
+ message: str,
164
+ *,
165
+ console: Console | None = None,
166
+ ) -> None:
167
+ """Print a single issue line with icon and color."""
168
+ c = console or _default_console
169
+ icon, style = ISSUE_STYLES.get(issue_type, ("ℹ️ ", "dim"))
170
+ c.print(f" {icon}[{style}]{issue_type.upper()}[/]: {message}")
171
+
172
+
173
+ def issues_block(
174
+ issues: Sequence[dict[str, str]],
175
+ *,
176
+ title: str = "Issues",
177
+ rule_style: str = "yellow",
178
+ console: Console | None = None,
179
+ ) -> None:
180
+ """Print a section rule followed by a list of issues."""
181
+ if not issues:
182
+ return
183
+ c = console or _default_console
184
+ section_rule(title, style=rule_style, console=c)
185
+ for issue in issues:
186
+ issue_line(issue.get("type", "info"), issue.get("message", ""), console=c)
187
+ c.print()
188
+
189
+
190
+ def success(message: str, *, console: Console | None = None) -> None:
191
+ """Print a green success line."""
192
+ (console or _default_console).print(f"[green]✓[/] {message}")
193
+
194
+
195
+ def error(message: str, *, console: Console | None = None) -> None:
196
+ """Print a red error line."""
197
+ (console or _default_console).print(f"[red]✗[/] {message}")
198
+
199
+
200
+ def warning(message: str, *, console: Console | None = None) -> None:
201
+ """Print a yellow warning line."""
202
+ (console or _default_console).print(f"[yellow]⚠[/] {message}")
203
+
204
+
205
+ def step_log(
206
+ message: str,
207
+ *,
208
+ position: str = "mid",
209
+ console: Console | None = None,
210
+ ) -> None:
211
+ """Print a tree-style step log line.
212
+
213
+ Args:
214
+ message: The message to display (may contain Rich markup).
215
+ position: ``"first"`` (├─), ``"mid"`` (├─), ``"last"`` (└─),
216
+ or ``"cont"`` (│ ) for continuation lines.
217
+ console: Console instance.
218
+ """
219
+ c = console or _default_console
220
+ prefixes = {
221
+ "first": "[dim]├─[/]",
222
+ "mid": "[dim]├─[/]",
223
+ "last": "[dim]└─[/]",
224
+ "cont": "[dim]│ [/]",
225
+ }
226
+ prefix = prefixes.get(position, prefixes["mid"])
227
+ c.print(f" {prefix} {message}")
228
+
229
+
230
+ def result_markdown(
231
+ content: str,
232
+ *,
233
+ title: str = "Result",
234
+ rule_style: str = "bright_green",
235
+ console: Console | None = None,
236
+ output_console: Console | None = None,
237
+ ) -> None:
238
+ """Print Markdown content preceded by a section rule.
239
+
240
+ The rule goes to *console* (stderr), the content to *output_console*
241
+ (stdout) so piping works correctly.
242
+ """
243
+ c = console or _default_console
244
+ out = output_console or Console()
245
+ section_rule(title, style=rule_style, console=c)
246
+ out.print(Markdown(content))
247
+ c.print()
248
+
249
+
250
+ def result_json(
251
+ data: Any,
252
+ *,
253
+ output_console: Console | None = None,
254
+ ) -> None:
255
+ """Print JSON data to stdout.
256
+
257
+ Emits the JSON verbatim — wrapping is disabled and Rich markup/emoji
258
+ interpretation is turned off — so downstream consumers (other CLIs,
259
+ GUIs, scripts piping the output) can always parse the result with a
260
+ strict JSON parser. Wrapping would otherwise inject literal newlines
261
+ inside string values when stdout's width is narrower than the longest
262
+ string, breaking JSON conformance per RFC 8259 §7.
263
+ """
264
+ out = output_console or Console()
265
+ out.print(
266
+ _json.dumps(data, indent=2, ensure_ascii=False),
267
+ highlight=False,
268
+ markup=False,
269
+ emoji=False,
270
+ soft_wrap=True,
271
+ )
272
+
273
+
274
+ # ── Chat components ───────────────────────────────────────────────────
275
+
276
+ # Role styles: (label, label_style, border_style, content_style)
277
+ ROLE_STYLES: dict[str, tuple[str, str, str, str]] = {
278
+ "user": ("You", "bold bright_white", "bright_blue", "white"),
279
+ "assistant": ("Assistant", "bold bright_green", "green", ""),
280
+ "system": ("System", "bold yellow", "yellow", "dim"),
281
+ }
282
+
283
+
284
+ def chat_message(
285
+ role: str,
286
+ content: str,
287
+ *,
288
+ label: str | None = None,
289
+ console: Console | None = None,
290
+ output_console: Console | None = None,
291
+ ) -> None:
292
+ """Render a styled chat message.
293
+
294
+ - **user** messages: compact label + text.
295
+ - **assistant** messages: bordered panel with Markdown rendering
296
+ sent to *output_console* (stdout).
297
+ - **system** messages: dimmed panel.
298
+
299
+ Args:
300
+ role: ``"user"``, ``"assistant"``, or ``"system"``.
301
+ content: The message text (Markdown is rendered for assistant).
302
+ label: Override the default role label.
303
+ console: Console for chrome (stderr).
304
+ output_console: Console for assistant content (stdout).
305
+ """
306
+ c = console or _default_console
307
+ out = output_console or Console()
308
+ style_info = ROLE_STYLES.get(role, ROLE_STYLES["assistant"])
309
+ role_label = label or style_info[0]
310
+ label_style = style_info[1]
311
+ border_style = style_info[2]
312
+
313
+ if role == "user":
314
+ c.print(f"\n[{label_style}]{role_label}[/] [dim]›[/] {content}")
315
+ elif role == "assistant":
316
+ c.print()
317
+ out.print(Panel(
318
+ Markdown(content),
319
+ title=f"[{label_style}]{role_label}[/]",
320
+ title_align="left",
321
+ border_style=border_style,
322
+ box=box.ROUNDED,
323
+ padding=(1, 2),
324
+ ))
325
+ else:
326
+ c.print(Panel(
327
+ content,
328
+ title=f"[{label_style}]{role_label}[/]",
329
+ title_align="left",
330
+ border_style=border_style,
331
+ box=box.SIMPLE,
332
+ padding=(0, 2),
333
+ ))
334
+
335
+
336
+ def chat_input_prompt(
337
+ label: str = "You",
338
+ *,
339
+ style: str = "bold bright_white",
340
+ ) -> str:
341
+ """Return a styled prompt string for ``console.input()``.
342
+
343
+ Usage::
344
+
345
+ user_input = console.input(chat_input_prompt())
346
+ """
347
+ return f"\n[{style}]{label}[/] [dim]›[/] "
348
+
349
+
350
+ def tool_use_badge(
351
+ tool_name: str,
352
+ *,
353
+ console: Console | None = None,
354
+ ) -> None:
355
+ """Print an inline tool-use notification."""
356
+ c = console or _default_console
357
+ c.print(f" [dim]🔧 Using[/] [bright_cyan]{tool_name}[/][dim]…[/]")
358
+
359
+
360
+ def welcome_banner(
361
+ title: str,
362
+ subtitle_items: dict[str, str] | None = None,
363
+ *,
364
+ commands: dict[str, str] | None = None,
365
+ icon: str = "",
366
+ border_style: str = "bright_blue",
367
+ console: Console | None = None,
368
+ ) -> None:
369
+ """Render a welcome banner for interactive CLI apps.
370
+
371
+ Shows the app title, metadata, and optional command hints.
372
+
373
+ Args:
374
+ title: Application name.
375
+ subtitle_items: Key-value pairs (e.g. model, mode).
376
+ commands: Command hints (e.g. ``{"quit": "Exit", "/help": "Help"}``).
377
+ icon: Emoji prepended to title.
378
+ border_style: Panel border style.
379
+ console: Console instance (stderr).
380
+ """
381
+ c = console or _default_console
382
+ display_title = f"{icon} {title}".strip() if icon else title
383
+ title_text = Text(display_title, style="bold bright_white")
384
+
385
+ lines: list[str] = []
386
+ if subtitle_items:
387
+ parts = [
388
+ f"[dim]{k}:[/] [bright_cyan]{v}[/]"
389
+ for k, v in subtitle_items.items()
390
+ ]
391
+ lines.append(" ".join(parts))
392
+
393
+ if commands:
394
+ if lines:
395
+ lines.append("")
396
+ cmd_parts = [
397
+ f" [dim bold]{k}[/] [dim]{v}[/]"
398
+ for k, v in commands.items()
399
+ ]
400
+ lines.append("[dim]Commands:[/]")
401
+ lines.extend(cmd_parts)
402
+
403
+ c.print(Panel(
404
+ "\n".join(lines) if lines else "",
405
+ title=title_text,
406
+ title_align="left",
407
+ border_style=border_style,
408
+ box=box.DOUBLE,
409
+ padding=(1, 2),
410
+ ))
411
+ c.print()
@@ -0,0 +1,229 @@
1
+ """High-level CLI printer with app branding.
2
+
3
+ Wraps the low-level building blocks in :mod:`components` into a stateful,
4
+ branded workflow API that any CLI app can instantiate.
5
+
6
+ All chrome (headers, spinners, events, stats) goes to *stderr*. Only
7
+ actual output (composed prompt, JSON data) goes to *stdout* so pipelines
8
+ stay clean.
9
+
10
+ Verbose progress events are dispatched through a pluggable
11
+ :class:`EventRenderer` Protocol. Apps with their own event vocabulary
12
+ register a renderer at construction time; the printer remains agnostic
13
+ to specific event names.
14
+
15
+ Usage::
16
+
17
+ from ellements.cli import CliPrinter
18
+
19
+ printer = CliPrinter("Prompt Composer", icon="🧩")
20
+ printer.header({"Spec": "file.md", "Model": "gpt-4o-mini"})
21
+ printer.variables({"topic": "cats"})
22
+ printer.status("Composing prompt…")
23
+ # pass printer.event as an on_event callback
24
+ result = await controller.compose(spec, vars, on_event=printer.event)
25
+ printer.result_markdown(result.composed_prompt)
26
+ printer.stats({"Tool calls": 3, "Issues": 1}, elapsed=2.1)
27
+ printer.done()
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ from collections.abc import Iterator, Mapping, Sequence
33
+ from contextlib import contextmanager
34
+ from pathlib import Path
35
+ from typing import Any, Protocol, runtime_checkable
36
+
37
+ from rich.console import Console
38
+
39
+ from . import components as _c
40
+
41
+
42
+ @runtime_checkable
43
+ class EventRenderer(Protocol):
44
+ """Renders verbose progress events to the supplied :class:`Console`."""
45
+
46
+ def render(
47
+ self,
48
+ event_type: str,
49
+ data: Mapping[str, Any],
50
+ console: Console,
51
+ ) -> None: ...
52
+
53
+
54
+ class CliPrinter:
55
+ """Branded CLI printer for terminal output.
56
+
57
+ Args:
58
+ app_name: Display name of the application.
59
+ icon: Emoji or symbol prepended to the title.
60
+ border_style: Default Rich border style for panels.
61
+ verbose: When False, :meth:`event` is a no-op.
62
+ event_renderer: Optional plug-in for rendering domain-specific
63
+ verbose events. When None, :meth:`event` accepts events but
64
+ does nothing with them in verbose mode.
65
+ """
66
+
67
+ def __init__(
68
+ self,
69
+ app_name: str,
70
+ *,
71
+ icon: str = "",
72
+ border_style: str = "bright_blue",
73
+ verbose: bool = False,
74
+ event_renderer: EventRenderer | None = None,
75
+ ) -> None:
76
+ self.app_name = app_name
77
+ self.icon = icon
78
+ self.border_style = border_style
79
+ self.verbose = verbose
80
+ self.event_renderer = event_renderer
81
+ self.console = Console(stderr=True)
82
+ self.output_console = Console()
83
+
84
+ # ── Header / preamble ──────────────────────────────────────────
85
+
86
+ def header(self, subtitle_items: Mapping[str, str] | None = None) -> None:
87
+ _c.app_header(
88
+ self.app_name,
89
+ dict(subtitle_items) if subtitle_items else None,
90
+ icon=self.icon,
91
+ border_style=self.border_style,
92
+ console=self.console,
93
+ )
94
+
95
+ def variables(
96
+ self, data: Mapping[str, Any], *, title: str = "📋 Variables"
97
+ ) -> None:
98
+ _c.key_value_table(dict(data), title=title, console=self.console)
99
+
100
+ # ── Status / progress ──────────────────────────────────────────
101
+
102
+ def status(self, message: str) -> None:
103
+ self.console.print()
104
+ self.console.print(
105
+ f"[bold bright_blue]⚙ {message}[/]", highlight=False
106
+ )
107
+
108
+ def event(self, event_type: str, data: Mapping[str, Any]) -> None:
109
+ """Forward an event to the configured renderer, if any.
110
+
111
+ Designed to be passed directly as an ``on_event`` callback. Does
112
+ nothing when *verbose* is False or no renderer is installed.
113
+ """
114
+ if not self.verbose or self.event_renderer is None:
115
+ return
116
+ self.event_renderer.render(event_type, data, self.console)
117
+
118
+ # ── Results ────────────────────────────────────────────────────
119
+
120
+ def result_markdown(
121
+ self, content: str, *, title: str = "Result"
122
+ ) -> None:
123
+ _c.result_markdown(
124
+ content,
125
+ title=title,
126
+ console=self.console,
127
+ output_console=self.output_console,
128
+ )
129
+
130
+ def result_json(self, data: Any) -> None:
131
+ _c.result_json(data, output_console=self.output_console)
132
+
133
+ def issues(
134
+ self,
135
+ issues_list: Sequence[Mapping[str, str]],
136
+ *,
137
+ title: str = "Issues",
138
+ ) -> None:
139
+ _c.issues_block(
140
+ [dict(i) for i in issues_list],
141
+ title=title,
142
+ console=self.console,
143
+ )
144
+
145
+ def stats(
146
+ self,
147
+ stats_dict: Mapping[str, str | int | float],
148
+ *,
149
+ elapsed: float | None = None,
150
+ ) -> None:
151
+ merged = dict(stats_dict)
152
+ if elapsed is not None:
153
+ merged["Time"] = f"{elapsed:.1f}s"
154
+ _c.stats_footer(merged, console=self.console)
155
+
156
+ def file_written(self, path: str | Path) -> None:
157
+ _c.success(
158
+ f"Output written to [bright_cyan]{path}[/]",
159
+ console=self.console,
160
+ )
161
+
162
+ def done(self, message: str = "Done. 👋") -> None:
163
+ self.console.print(f"[dim]{message}[/]")
164
+
165
+ # ── Chat ───────────────────────────────────────────────────────
166
+
167
+ def welcome(
168
+ self,
169
+ subtitle_items: Mapping[str, str] | None = None,
170
+ *,
171
+ commands: Mapping[str, str] | None = None,
172
+ ) -> None:
173
+ _c.welcome_banner(
174
+ self.app_name,
175
+ dict(subtitle_items) if subtitle_items else None,
176
+ commands=dict(commands) if commands else None,
177
+ icon=self.icon,
178
+ border_style=self.border_style,
179
+ console=self.console,
180
+ )
181
+
182
+ def user_message(self, content: str) -> None:
183
+ _c.chat_message(
184
+ "user",
185
+ content,
186
+ console=self.console,
187
+ output_console=self.output_console,
188
+ )
189
+
190
+ def assistant_message(self, content: str) -> None:
191
+ _c.chat_message(
192
+ "assistant",
193
+ content,
194
+ console=self.console,
195
+ output_console=self.output_console,
196
+ )
197
+
198
+ def tool_badge(self, tool_name: str) -> None:
199
+ _c.tool_use_badge(tool_name, console=self.console)
200
+
201
+ def input_prompt(self) -> str:
202
+ return _c.chat_input_prompt()
203
+
204
+ @contextmanager
205
+ def thinking(self, message: str = "Thinking…") -> Iterator[None]:
206
+ """Show a spinner while work is happening."""
207
+ with self.console.status(
208
+ f"[bold bright_blue]{message}[/]",
209
+ spinner="dots",
210
+ spinner_style="bright_blue",
211
+ ):
212
+ yield
213
+
214
+ def goodbye(self, message: str = "Goodbye! 👋") -> None:
215
+ self.console.print(f"\n[dim italic]{message}[/]\n")
216
+
217
+ # ── One-liners ─────────────────────────────────────────────────
218
+
219
+ def success(self, message: str) -> None:
220
+ _c.success(message, console=self.console)
221
+
222
+ def error(self, message: str) -> None:
223
+ _c.error(message, console=self.console)
224
+
225
+ def warning(self, message: str) -> None:
226
+ _c.warning(message, console=self.console)
227
+
228
+
229
+ __all__ = ["CliPrinter", "EventRenderer"]
ellements/cli/py.typed ADDED
File without changes