bead 0.1.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 (231) hide show
  1. bead/__init__.py +11 -0
  2. bead/__main__.py +11 -0
  3. bead/active_learning/__init__.py +15 -0
  4. bead/active_learning/config.py +231 -0
  5. bead/active_learning/loop.py +566 -0
  6. bead/active_learning/models/__init__.py +24 -0
  7. bead/active_learning/models/base.py +852 -0
  8. bead/active_learning/models/binary.py +910 -0
  9. bead/active_learning/models/categorical.py +943 -0
  10. bead/active_learning/models/cloze.py +862 -0
  11. bead/active_learning/models/forced_choice.py +956 -0
  12. bead/active_learning/models/free_text.py +773 -0
  13. bead/active_learning/models/lora.py +365 -0
  14. bead/active_learning/models/magnitude.py +835 -0
  15. bead/active_learning/models/multi_select.py +795 -0
  16. bead/active_learning/models/ordinal_scale.py +811 -0
  17. bead/active_learning/models/peft_adapter.py +155 -0
  18. bead/active_learning/models/random_effects.py +639 -0
  19. bead/active_learning/selection.py +354 -0
  20. bead/active_learning/strategies.py +391 -0
  21. bead/active_learning/trainers/__init__.py +26 -0
  22. bead/active_learning/trainers/base.py +210 -0
  23. bead/active_learning/trainers/data_collator.py +172 -0
  24. bead/active_learning/trainers/dataset_utils.py +261 -0
  25. bead/active_learning/trainers/huggingface.py +304 -0
  26. bead/active_learning/trainers/lightning.py +324 -0
  27. bead/active_learning/trainers/metrics.py +424 -0
  28. bead/active_learning/trainers/mixed_effects.py +551 -0
  29. bead/active_learning/trainers/model_wrapper.py +509 -0
  30. bead/active_learning/trainers/registry.py +104 -0
  31. bead/adapters/__init__.py +11 -0
  32. bead/adapters/huggingface.py +61 -0
  33. bead/behavioral/__init__.py +116 -0
  34. bead/behavioral/analytics.py +646 -0
  35. bead/behavioral/extraction.py +343 -0
  36. bead/behavioral/merging.py +343 -0
  37. bead/cli/__init__.py +11 -0
  38. bead/cli/active_learning.py +513 -0
  39. bead/cli/active_learning_commands.py +779 -0
  40. bead/cli/completion.py +359 -0
  41. bead/cli/config.py +624 -0
  42. bead/cli/constraint_builders.py +286 -0
  43. bead/cli/deployment.py +859 -0
  44. bead/cli/deployment_trials.py +493 -0
  45. bead/cli/deployment_ui.py +332 -0
  46. bead/cli/display.py +378 -0
  47. bead/cli/items.py +960 -0
  48. bead/cli/items_factories.py +776 -0
  49. bead/cli/list_constraints.py +714 -0
  50. bead/cli/lists.py +490 -0
  51. bead/cli/main.py +430 -0
  52. bead/cli/models.py +877 -0
  53. bead/cli/resource_loaders.py +621 -0
  54. bead/cli/resources.py +1036 -0
  55. bead/cli/shell.py +356 -0
  56. bead/cli/simulate.py +840 -0
  57. bead/cli/templates.py +1158 -0
  58. bead/cli/training.py +1080 -0
  59. bead/cli/utils.py +614 -0
  60. bead/cli/workflow.py +1273 -0
  61. bead/config/__init__.py +68 -0
  62. bead/config/active_learning.py +1009 -0
  63. bead/config/config.py +192 -0
  64. bead/config/defaults.py +118 -0
  65. bead/config/deployment.py +217 -0
  66. bead/config/env.py +147 -0
  67. bead/config/item.py +45 -0
  68. bead/config/list.py +193 -0
  69. bead/config/loader.py +149 -0
  70. bead/config/logging.py +42 -0
  71. bead/config/model.py +49 -0
  72. bead/config/paths.py +46 -0
  73. bead/config/profiles.py +320 -0
  74. bead/config/resources.py +47 -0
  75. bead/config/serialization.py +210 -0
  76. bead/config/simulation.py +206 -0
  77. bead/config/template.py +238 -0
  78. bead/config/validation.py +267 -0
  79. bead/data/__init__.py +65 -0
  80. bead/data/base.py +87 -0
  81. bead/data/identifiers.py +97 -0
  82. bead/data/language_codes.py +61 -0
  83. bead/data/metadata.py +270 -0
  84. bead/data/range.py +123 -0
  85. bead/data/repository.py +358 -0
  86. bead/data/serialization.py +249 -0
  87. bead/data/timestamps.py +89 -0
  88. bead/data/validation.py +349 -0
  89. bead/data_collection/__init__.py +11 -0
  90. bead/data_collection/jatos.py +223 -0
  91. bead/data_collection/merger.py +154 -0
  92. bead/data_collection/prolific.py +198 -0
  93. bead/deployment/__init__.py +5 -0
  94. bead/deployment/distribution.py +402 -0
  95. bead/deployment/jatos/__init__.py +1 -0
  96. bead/deployment/jatos/api.py +200 -0
  97. bead/deployment/jatos/exporter.py +210 -0
  98. bead/deployment/jspsych/__init__.py +9 -0
  99. bead/deployment/jspsych/biome.json +44 -0
  100. bead/deployment/jspsych/config.py +411 -0
  101. bead/deployment/jspsych/generator.py +598 -0
  102. bead/deployment/jspsych/package.json +51 -0
  103. bead/deployment/jspsych/pnpm-lock.yaml +2141 -0
  104. bead/deployment/jspsych/randomizer.py +299 -0
  105. bead/deployment/jspsych/src/lib/list-distributor.test.ts +327 -0
  106. bead/deployment/jspsych/src/lib/list-distributor.ts +1282 -0
  107. bead/deployment/jspsych/src/lib/randomizer.test.ts +232 -0
  108. bead/deployment/jspsych/src/lib/randomizer.ts +367 -0
  109. bead/deployment/jspsych/src/plugins/cloze-dropdown.ts +252 -0
  110. bead/deployment/jspsych/src/plugins/forced-choice.ts +265 -0
  111. bead/deployment/jspsych/src/plugins/plugins.test.ts +141 -0
  112. bead/deployment/jspsych/src/plugins/rating.ts +248 -0
  113. bead/deployment/jspsych/src/slopit/index.ts +9 -0
  114. bead/deployment/jspsych/src/types/jatos.d.ts +256 -0
  115. bead/deployment/jspsych/src/types/jspsych.d.ts +228 -0
  116. bead/deployment/jspsych/templates/experiment.css +1 -0
  117. bead/deployment/jspsych/templates/experiment.js.template +289 -0
  118. bead/deployment/jspsych/templates/index.html +51 -0
  119. bead/deployment/jspsych/templates/randomizer.js +241 -0
  120. bead/deployment/jspsych/templates/randomizer.js.template +313 -0
  121. bead/deployment/jspsych/trials.py +723 -0
  122. bead/deployment/jspsych/tsconfig.json +23 -0
  123. bead/deployment/jspsych/tsup.config.ts +30 -0
  124. bead/deployment/jspsych/ui/__init__.py +1 -0
  125. bead/deployment/jspsych/ui/components.py +383 -0
  126. bead/deployment/jspsych/ui/styles.py +411 -0
  127. bead/dsl/__init__.py +80 -0
  128. bead/dsl/ast.py +168 -0
  129. bead/dsl/context.py +178 -0
  130. bead/dsl/errors.py +71 -0
  131. bead/dsl/evaluator.py +570 -0
  132. bead/dsl/grammar.lark +81 -0
  133. bead/dsl/parser.py +231 -0
  134. bead/dsl/stdlib.py +929 -0
  135. bead/evaluation/__init__.py +13 -0
  136. bead/evaluation/convergence.py +485 -0
  137. bead/evaluation/interannotator.py +398 -0
  138. bead/items/__init__.py +40 -0
  139. bead/items/adapters/__init__.py +70 -0
  140. bead/items/adapters/anthropic.py +224 -0
  141. bead/items/adapters/api_utils.py +167 -0
  142. bead/items/adapters/base.py +216 -0
  143. bead/items/adapters/google.py +259 -0
  144. bead/items/adapters/huggingface.py +1074 -0
  145. bead/items/adapters/openai.py +323 -0
  146. bead/items/adapters/registry.py +202 -0
  147. bead/items/adapters/sentence_transformers.py +224 -0
  148. bead/items/adapters/togetherai.py +309 -0
  149. bead/items/binary.py +515 -0
  150. bead/items/cache.py +558 -0
  151. bead/items/categorical.py +593 -0
  152. bead/items/cloze.py +757 -0
  153. bead/items/constructor.py +784 -0
  154. bead/items/forced_choice.py +413 -0
  155. bead/items/free_text.py +681 -0
  156. bead/items/generation.py +432 -0
  157. bead/items/item.py +396 -0
  158. bead/items/item_template.py +787 -0
  159. bead/items/magnitude.py +573 -0
  160. bead/items/multi_select.py +621 -0
  161. bead/items/ordinal_scale.py +569 -0
  162. bead/items/scoring.py +448 -0
  163. bead/items/validation.py +723 -0
  164. bead/lists/__init__.py +30 -0
  165. bead/lists/balancer.py +263 -0
  166. bead/lists/constraints.py +1067 -0
  167. bead/lists/experiment_list.py +286 -0
  168. bead/lists/list_collection.py +378 -0
  169. bead/lists/partitioner.py +1141 -0
  170. bead/lists/stratification.py +254 -0
  171. bead/participants/__init__.py +73 -0
  172. bead/participants/collection.py +699 -0
  173. bead/participants/merging.py +312 -0
  174. bead/participants/metadata_spec.py +491 -0
  175. bead/participants/models.py +276 -0
  176. bead/resources/__init__.py +29 -0
  177. bead/resources/adapters/__init__.py +19 -0
  178. bead/resources/adapters/base.py +104 -0
  179. bead/resources/adapters/cache.py +128 -0
  180. bead/resources/adapters/glazing.py +508 -0
  181. bead/resources/adapters/registry.py +117 -0
  182. bead/resources/adapters/unimorph.py +796 -0
  183. bead/resources/classification.py +856 -0
  184. bead/resources/constraint_builders.py +329 -0
  185. bead/resources/constraints.py +165 -0
  186. bead/resources/lexical_item.py +223 -0
  187. bead/resources/lexicon.py +744 -0
  188. bead/resources/loaders.py +209 -0
  189. bead/resources/template.py +441 -0
  190. bead/resources/template_collection.py +707 -0
  191. bead/resources/template_generation.py +349 -0
  192. bead/simulation/__init__.py +29 -0
  193. bead/simulation/annotators/__init__.py +15 -0
  194. bead/simulation/annotators/base.py +175 -0
  195. bead/simulation/annotators/distance_based.py +135 -0
  196. bead/simulation/annotators/lm_based.py +114 -0
  197. bead/simulation/annotators/oracle.py +182 -0
  198. bead/simulation/annotators/random.py +181 -0
  199. bead/simulation/dsl_extension/__init__.py +3 -0
  200. bead/simulation/noise_models/__init__.py +13 -0
  201. bead/simulation/noise_models/base.py +42 -0
  202. bead/simulation/noise_models/random_noise.py +82 -0
  203. bead/simulation/noise_models/systematic.py +132 -0
  204. bead/simulation/noise_models/temperature.py +86 -0
  205. bead/simulation/runner.py +144 -0
  206. bead/simulation/strategies/__init__.py +23 -0
  207. bead/simulation/strategies/base.py +123 -0
  208. bead/simulation/strategies/binary.py +103 -0
  209. bead/simulation/strategies/categorical.py +123 -0
  210. bead/simulation/strategies/cloze.py +224 -0
  211. bead/simulation/strategies/forced_choice.py +127 -0
  212. bead/simulation/strategies/free_text.py +105 -0
  213. bead/simulation/strategies/magnitude.py +116 -0
  214. bead/simulation/strategies/multi_select.py +129 -0
  215. bead/simulation/strategies/ordinal_scale.py +131 -0
  216. bead/templates/__init__.py +27 -0
  217. bead/templates/adapters/__init__.py +17 -0
  218. bead/templates/adapters/base.py +128 -0
  219. bead/templates/adapters/cache.py +178 -0
  220. bead/templates/adapters/huggingface.py +312 -0
  221. bead/templates/combinatorics.py +103 -0
  222. bead/templates/filler.py +605 -0
  223. bead/templates/renderers.py +177 -0
  224. bead/templates/resolver.py +178 -0
  225. bead/templates/strategies.py +1806 -0
  226. bead/templates/streaming.py +195 -0
  227. bead-0.1.0.dist-info/METADATA +212 -0
  228. bead-0.1.0.dist-info/RECORD +231 -0
  229. bead-0.1.0.dist-info/WHEEL +4 -0
  230. bead-0.1.0.dist-info/entry_points.txt +2 -0
  231. bead-0.1.0.dist-info/licenses/LICENSE +21 -0
bead/cli/shell.py ADDED
@@ -0,0 +1,356 @@
1
+ """Interactive CLI shell using Click and Prompt Toolkit.
2
+
3
+ This module provides an interactive REPL shell for the bead CLI with
4
+ autocomplete, command history, and rich formatting. Commands are executed
5
+ through Click's CLI system, providing full integration with all bead commands.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import shlex
11
+ from pathlib import Path
12
+ from typing import TYPE_CHECKING
13
+
14
+ import click
15
+ from prompt_toolkit import PromptSession
16
+ from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
17
+ from prompt_toolkit.completion import Completer, Completion
18
+ from prompt_toolkit.history import FileHistory
19
+ from prompt_toolkit.lexers import PygmentsLexer
20
+ from prompt_toolkit.styles import Style
21
+ from pygments.lexers.shell import BashLexer
22
+ from rich.console import Console
23
+ from rich.markdown import Markdown
24
+
25
+ if TYPE_CHECKING:
26
+ from collections.abc import Iterable
27
+
28
+ console = Console()
29
+
30
+
31
+ # Create shell command group
32
+ @click.group()
33
+ def shell() -> None:
34
+ r"""Interactive shell for bead CLI.
35
+
36
+ Provides an interactive REPL with autocomplete, history, and rich formatting.
37
+ """
38
+ pass
39
+
40
+
41
+ class BeadCompleter(Completer):
42
+ """Command completer for bead shell."""
43
+
44
+ def __init__(self, commands: list[str]) -> None:
45
+ """Initialize completer with available commands.
46
+
47
+ Parameters
48
+ ----------
49
+ commands : list[str]
50
+ List of available commands.
51
+ """
52
+ self.commands = commands
53
+ # Initialize subcommands dict - will be populated lazily
54
+ self.subcommands: dict[str, list[str]] = {}
55
+ self._subcommands_loaded = False
56
+
57
+ def get_completions(
58
+ self, document: object, complete_event: object
59
+ ) -> Iterable[Completion]:
60
+ """Get completions for current input.
61
+
62
+ Parameters
63
+ ----------
64
+ document : object
65
+ Current document/input.
66
+ complete_event : object
67
+ Completion event.
68
+
69
+ Yields
70
+ ------
71
+ Completion
72
+ Completion suggestions.
73
+ """
74
+ # Access text_before_cursor attribute safely
75
+ text = getattr(document, "text_before_cursor", "")
76
+ words = text.split()
77
+
78
+ # Lazy load subcommands to avoid circular import
79
+ if not self._subcommands_loaded:
80
+ try:
81
+ from bead.cli.main import cli # noqa: PLC0415
82
+
83
+ for cmd_name, cmd_obj in cli.commands.items():
84
+ if hasattr(cmd_obj, "commands"):
85
+ # It's a group, get its subcommands
86
+ self.subcommands[cmd_name] = list(cmd_obj.commands.keys())
87
+ self._subcommands_loaded = True
88
+ except Exception:
89
+ # Fallback to hardcoded list if import fails
90
+ self.subcommands = {
91
+ "resources": ["create", "list", "validate", "import"],
92
+ "templates": ["fill", "list", "validate"],
93
+ "items": ["create", "list", "validate", "stats"],
94
+ "lists": ["partition", "list", "validate"],
95
+ "deployment": ["generate", "validate", "export"],
96
+ "training": ["train", "evaluate", "predict"],
97
+ "active-learning": ["select-items", "run", "monitor-convergence"],
98
+ "config": ["show", "validate", "edit"],
99
+ "workflow": ["run", "status", "resume", "rollback"],
100
+ }
101
+ self._subcommands_loaded = True
102
+
103
+ if len(words) == 0:
104
+ # Complete main commands
105
+ for cmd in self.commands:
106
+ if cmd.startswith(text):
107
+ yield Completion(cmd, start_position=-len(text))
108
+ elif len(words) == 1:
109
+ # Complete subcommands
110
+ cmd = words[0]
111
+ if cmd in self.subcommands:
112
+ for subcmd in self.subcommands[cmd]:
113
+ yield Completion(subcmd, start_position=0)
114
+ else:
115
+ # File path completion
116
+ last_word = words[-1]
117
+ if "/" in last_word or last_word.startswith("."):
118
+ try:
119
+ path = Path(last_word).parent if "/" in last_word else Path(".")
120
+ prefix = Path(last_word).name
121
+ if path.exists():
122
+ for item in path.iterdir():
123
+ if item.name.startswith(prefix):
124
+ suffix = "/" if item.is_dir() else ""
125
+ yield Completion(
126
+ str(item.name) + suffix,
127
+ start_position=-len(prefix),
128
+ )
129
+ except Exception:
130
+ pass
131
+
132
+
133
+ # Shell style
134
+ shell_style = Style.from_dict(
135
+ {
136
+ "prompt": "bold cyan",
137
+ "command": "bold green",
138
+ "error": "bold red",
139
+ }
140
+ )
141
+
142
+
143
+ _DEFAULT_HISTORY_FILE = Path.home() / ".bead_history"
144
+
145
+
146
+ @shell.command()
147
+ @click.option(
148
+ "--history",
149
+ type=click.Path(path_type=Path),
150
+ default=_DEFAULT_HISTORY_FILE,
151
+ help="Path to history file",
152
+ )
153
+ def repl(history_file: Path) -> None:
154
+ """Start interactive REPL shell.
155
+
156
+ Provides an interactive command-line interface with:
157
+ - Command autocomplete
158
+ - Command history
159
+ - Rich formatting
160
+ - Multi-line input support
161
+
162
+ Parameters
163
+ ----------
164
+ history_file : Path
165
+ Path to history file for command persistence.
166
+
167
+ Examples
168
+ --------
169
+ Start shell:
170
+ $ bead shell
171
+
172
+ Start shell with custom history:
173
+ $ bead shell --history ~/.my_bead_history
174
+ """
175
+ console.print("[bold cyan]bead Interactive Shell[/bold cyan]")
176
+ console.print("[dim]Type 'help' for available commands, 'exit' to quit[/dim]\n")
177
+
178
+ # Available commands - lazy import to avoid circular dependency
179
+ commands: list[str]
180
+ try:
181
+ # Import here to avoid circular import at module level
182
+ from bead.cli.main import cli as main_cli # noqa: PLC0415
183
+
184
+ commands = list(main_cli.commands.keys()) + ["help", "exit", "quit", "clear"]
185
+ except Exception:
186
+ # Fallback to hardcoded list if import fails
187
+ commands = [
188
+ "resources",
189
+ "templates",
190
+ "items",
191
+ "lists",
192
+ "deployment",
193
+ "training",
194
+ "active-learning",
195
+ "config",
196
+ "workflow",
197
+ "help",
198
+ "exit",
199
+ "quit",
200
+ ]
201
+
202
+ # Create completer
203
+ completer = BeadCompleter(commands)
204
+
205
+ # Create history
206
+ history = FileHistory(str(history_file)) if history_file else None
207
+
208
+ # Create session
209
+ session = PromptSession(
210
+ history=history,
211
+ completer=completer,
212
+ auto_suggest=AutoSuggestFromHistory(),
213
+ lexer=PygmentsLexer(BashLexer),
214
+ style=shell_style,
215
+ complete_while_typing=True,
216
+ )
217
+
218
+ # Main loop
219
+ while True:
220
+ try:
221
+ # Get user input
222
+ text = session.prompt("bead> ")
223
+
224
+ if not text.strip():
225
+ continue
226
+
227
+ # Handle built-in commands
228
+ if text.strip() in ("exit", "quit"):
229
+ console.print("[green]Goodbye![/green]")
230
+ break
231
+ elif text.strip() == "help":
232
+ _show_help()
233
+ continue
234
+ elif text.strip().startswith("clear"):
235
+ console.clear()
236
+ continue
237
+
238
+ # Execute command
239
+ _execute_command(text)
240
+
241
+ except KeyboardInterrupt:
242
+ console.print("\n[yellow]Interrupted. Type 'exit' to quit.[/yellow]")
243
+ except EOFError:
244
+ console.print("\n[green]Goodbye![/green]")
245
+ break
246
+ except Exception as e:
247
+ console.print(f"[red]Error: {e}[/red]")
248
+
249
+
250
+ def _show_help() -> None:
251
+ """Show help message."""
252
+ help_text = """
253
+ # bead Interactive Shell
254
+
255
+ ## Available Commands
256
+
257
+ - **resources** - Manage lexicons and templates
258
+ - **templates** - Fill and manage templates
259
+ - **items** - Create and manage experimental items
260
+ - **lists** - Partition items into experiment lists
261
+ - **deployment** - Generate and deploy experiments
262
+ - **training** - Train models with active learning
263
+ - **active-learning** - Active learning commands
264
+ - **config** - Configuration management
265
+ - **workflow** - End-to-end pipeline workflows
266
+
267
+ ## Built-in Commands
268
+
269
+ - **help** - Show this help message
270
+ - **exit** / **quit** - Exit the shell
271
+ - **clear** - Clear the screen
272
+
273
+ ## Usage
274
+
275
+ Type commands as you would in the regular CLI, but without the 'bead' prefix:
276
+
277
+ bead> resources list
278
+ bead> templates fill --strategy exhaustive
279
+ bead> items create --template template.jsonl
280
+
281
+ ## Features
282
+
283
+ - **Autocomplete**: Press TAB to autocomplete commands
284
+ - **History**: Use arrow keys to navigate command history
285
+ - **Multi-line**: Use backslash for line continuation
286
+ """
287
+ console.print(Markdown(help_text))
288
+
289
+
290
+ def _execute_command(text: str) -> None:
291
+ """Execute a command using Click's CLI system.
292
+
293
+ Parameters
294
+ ----------
295
+ text : str
296
+ Command text to execute.
297
+ """
298
+ if not text.strip():
299
+ return
300
+
301
+ # Parse command using shlex to handle quoted arguments properly
302
+ try:
303
+ parts = shlex.split(text)
304
+ except ValueError as e:
305
+ console.print(f"[red]Error parsing command: {e}[/red]")
306
+ return
307
+
308
+ if not parts:
309
+ return
310
+
311
+ # Import main CLI group
312
+ from bead.cli.main import cli # noqa: PLC0415
313
+
314
+ # Create a context with default options
315
+ ctx_obj: dict[str, object] = {
316
+ "config_file": None,
317
+ "profile": "default",
318
+ "verbose": False,
319
+ "quiet": False,
320
+ }
321
+
322
+ # Build command line arguments
323
+ # Click expects sys.argv format, so we need to prepend the program name
324
+ # and handle the command parts
325
+ try:
326
+ # Create a context for the main CLI group
327
+ with cli.make_context("bead", list(parts), obj=ctx_obj) as ctx:
328
+ # Invoke the command
329
+ cli.invoke(ctx)
330
+ except click.exceptions.Exit as e:
331
+ # Click commands use ctx.exit() which raises Exit
332
+ # Exit code 0 is success, non-zero is error
333
+ if e.exit_code != 0:
334
+ console.print(f"[red]Command failed with exit code {e.exit_code}[/red]")
335
+ except click.exceptions.ClickException as e:
336
+ # Click-specific exceptions (e.g., BadParameter, UsageError)
337
+ console.print(f"[red]{e.format_message()}[/red]")
338
+ except SystemExit as e:
339
+ # Some commands may call sys.exit()
340
+ if e.code and e.code != 0:
341
+ console.print(f"[red]Command exited with code {e.code}[/red]")
342
+ except Exception as e:
343
+ # Catch-all for other exceptions
344
+ console.print(f"[red]Error executing command: {e}[/red]")
345
+ if ctx_obj.get("verbose", False):
346
+ import traceback # noqa: PLC0415
347
+
348
+ console.print(f"[dim]{traceback.format_exc()}[/dim]")
349
+
350
+
351
+ # Register repl command
352
+ shell.add_command(repl)
353
+
354
+ # Entry point for direct execution
355
+ if __name__ == "__main__":
356
+ shell()