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/main.py ADDED
@@ -0,0 +1,430 @@
1
+ """Main CLI entry point for bead package.
2
+
3
+ This module provides the main CLI command group and the init command for
4
+ project scaffolding.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import importlib
10
+ import re
11
+ from pathlib import Path
12
+
13
+ import click
14
+ from rich.console import Console
15
+
16
+ from bead import __version__
17
+ from bead.cli.utils import confirm, print_error, print_info, print_success
18
+
19
+ console = Console()
20
+
21
+
22
+ @click.group()
23
+ @click.version_option(version=__version__, prog_name="bead")
24
+ @click.option(
25
+ "--config-file",
26
+ "-c",
27
+ type=click.Path(exists=True, dir_okay=False, path_type=Path),
28
+ default=None,
29
+ help="Path to configuration file (default: use profile defaults)",
30
+ )
31
+ @click.option(
32
+ "--profile",
33
+ "-p",
34
+ type=click.Choice(["default", "dev", "prod", "test"], case_sensitive=False),
35
+ default="default",
36
+ help="Configuration profile to use",
37
+ )
38
+ @click.option(
39
+ "--verbose",
40
+ "-v",
41
+ is_flag=True,
42
+ default=False,
43
+ help="Enable verbose output",
44
+ )
45
+ @click.option(
46
+ "--quiet",
47
+ "-q",
48
+ is_flag=True,
49
+ default=False,
50
+ help="Suppress all output except errors",
51
+ )
52
+ @click.pass_context
53
+ def cli(
54
+ ctx: click.Context,
55
+ config_file: Path | None,
56
+ profile: str,
57
+ verbose: bool,
58
+ quiet: bool,
59
+ ) -> None:
60
+ r"""CLI for linguistic judgment experiments.
61
+
62
+ Provides commands for constructing, deploying, and analyzing large-scale
63
+ linguistic judgment experiments with active learning.
64
+
65
+ \b
66
+ Examples:
67
+ # Show version
68
+ $ bead --version
69
+
70
+ # Use custom config file
71
+ $ bead --config-file my-config.yaml config show
72
+
73
+ # Use development profile
74
+ $ bead --profile dev config show
75
+
76
+ # Initialize new project
77
+ $ bead init my-experiment
78
+
79
+ # Show current configuration
80
+ $ bead config show
81
+
82
+ # Validate configuration
83
+ $ bead config validate
84
+
85
+ For more information, visit: https://github.com/FACTSlab/bead
86
+ """
87
+ # Store options in context for subcommands
88
+ ctx.ensure_object(dict)
89
+ ctx.obj["config_file"] = config_file
90
+ ctx.obj["profile"] = profile
91
+ ctx.obj["verbose"] = verbose
92
+ ctx.obj["quiet"] = quiet
93
+
94
+
95
+ @cli.command()
96
+ @click.argument("project_name", required=False, default=None)
97
+ @click.option(
98
+ "--profile",
99
+ type=click.Choice(["default", "dev", "prod", "test"], case_sensitive=False),
100
+ default="default",
101
+ help="Initialize with specific profile",
102
+ )
103
+ @click.option(
104
+ "--force",
105
+ is_flag=True,
106
+ default=False,
107
+ help="Overwrite existing directory",
108
+ )
109
+ def init(project_name: str | None, profile: str, force: bool) -> None:
110
+ r"""Initialize a new bead project with scaffolding.
111
+
112
+ Creates a new project directory with the following structure:
113
+
114
+ \b
115
+ project-name/
116
+ ├── bead.yaml # Default configuration
117
+ ├── .gitignore # bead-specific ignores
118
+ ├── lexicons/ # Lexical resources
119
+ ├── templates/ # Template definitions
120
+ ├── filled_templates/ # Generated filled templates
121
+ ├── items/ # Generated items
122
+ ├── lists/ # Generated experiment lists
123
+ ├── experiments/ # Generated experiments
124
+ ├── data/ # Collected data
125
+ └── models/ # Trained models
126
+
127
+ \b
128
+ Examples:
129
+ $ bead init my-experiment
130
+ $ bead init my-experiment --profile dev
131
+ $ bead init --force # Overwrite current directory
132
+ """
133
+ # Use current directory if no project name provided
134
+ if project_name is None:
135
+ project_dir = Path.cwd()
136
+ project_name = project_dir.name
137
+ print_info(f"Using current directory: {project_dir}")
138
+ else:
139
+ # Validate project name
140
+ if not _is_valid_project_name(project_name):
141
+ print_error(
142
+ f"Invalid project name: '{project_name}'. "
143
+ "Use only letters, numbers, hyphens, and underscores."
144
+ )
145
+ return
146
+
147
+ project_dir = Path.cwd() / project_name
148
+
149
+ # Check if directory exists
150
+ if project_dir.exists() and not force:
151
+ if list(project_dir.iterdir()):
152
+ if not confirm(
153
+ f"Directory '{project_dir}' already exists and is not empty. "
154
+ "Continue anyway?",
155
+ default=False,
156
+ ):
157
+ print_info("Initialization cancelled.")
158
+ return
159
+ elif not project_dir.exists():
160
+ project_dir.mkdir(parents=True)
161
+
162
+ try:
163
+ _create_project_structure(project_dir, project_name, profile)
164
+ print_success(f"Project initialized: {project_dir}")
165
+ print_info("\nNext steps:")
166
+ print_info(
167
+ f" 1. cd {project_name if project_name != project_dir.name else '.'}"
168
+ )
169
+ print_info(" 2. Edit bead.yaml to configure your experiment")
170
+ print_info(" 3. Create lexicons in lexicons/")
171
+ print_info(" 4. Create templates in templates/")
172
+ print_info(" 5. Run: bead config validate")
173
+
174
+ except Exception as e:
175
+ print_error(f"Failed to initialize project: {e}")
176
+
177
+
178
+ def _is_valid_project_name(name: str) -> bool:
179
+ """Validate project name.
180
+
181
+ Parameters
182
+ ----------
183
+ name : str
184
+ Project name to validate.
185
+
186
+ Returns
187
+ -------
188
+ bool
189
+ True if valid, False otherwise.
190
+ """
191
+ # Allow letters, numbers, hyphens, underscores
192
+ return bool(re.match(r"^[a-zA-Z0-9_-]+$", name))
193
+
194
+
195
+ def _create_project_structure(
196
+ project_dir: Path,
197
+ project_name: str,
198
+ profile: str,
199
+ ) -> None:
200
+ """Create project directory structure.
201
+
202
+ Parameters
203
+ ----------
204
+ project_dir : Path
205
+ Project directory path.
206
+ project_name : str
207
+ Project name.
208
+ profile : str
209
+ Configuration profile.
210
+ """
211
+ # Create subdirectories
212
+ subdirs = [
213
+ "lexicons",
214
+ "templates",
215
+ "filled_templates",
216
+ "items",
217
+ "lists",
218
+ "experiments",
219
+ "data",
220
+ "models",
221
+ ]
222
+
223
+ for subdir in subdirs:
224
+ (project_dir / subdir).mkdir(parents=True, exist_ok=True)
225
+
226
+ # Create default bead.yaml
227
+ config_content = _generate_default_config(project_name, profile)
228
+ config_file = project_dir / "bead.yaml"
229
+ config_file.write_text(config_content)
230
+
231
+ # Create .gitignore
232
+ gitignore_content = _generate_gitignore()
233
+ gitignore_file = project_dir / ".gitignore"
234
+ gitignore_file.write_text(gitignore_content)
235
+
236
+
237
+ def _generate_default_config(project_name: str, profile: str) -> str:
238
+ """Generate default configuration file content.
239
+
240
+ Parameters
241
+ ----------
242
+ project_name : str
243
+ Project name.
244
+ profile : str
245
+ Configuration profile.
246
+
247
+ Returns
248
+ -------
249
+ str
250
+ YAML configuration content.
251
+ """
252
+ return f"""# bead Configuration
253
+ # Generated by: bead init
254
+
255
+ # Project metadata
256
+ profile: {profile}
257
+
258
+ # Logging configuration
259
+ logging:
260
+ level: INFO
261
+ format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
262
+ file: null # Set to path for file logging
263
+
264
+ # Path configuration
265
+ paths:
266
+ data_dir: .
267
+ lexicons_dir: lexicons
268
+ templates_dir: templates
269
+ filled_templates_dir: filled_templates
270
+ items_dir: items
271
+ lists_dir: lists
272
+ experiments_dir: experiments
273
+ results_dir: data
274
+ models_dir: models
275
+ cache_dir: .cache
276
+
277
+ # Resource configuration
278
+ resources:
279
+ auto_download: true
280
+ cache_resources: true
281
+ default_language: eng
282
+
283
+ # Template filling configuration
284
+ templates:
285
+ filling_strategy: exhaustive
286
+ max_combinations: null
287
+ random_seed: null
288
+ use_streaming: true
289
+ constraint_cache_size: 10000
290
+
291
+ # Model configuration
292
+ models:
293
+ default_language_model: gpt2
294
+ default_nli_model: facebook/bart-large-mnli
295
+ default_sentence_transformer: sentence-transformers/all-mpnet-base-v2
296
+ use_gpu: true
297
+ cache_model_outputs: true
298
+
299
+ # Item configuration
300
+ items:
301
+ validation_enabled: true
302
+ auto_save: true
303
+
304
+ # List configuration
305
+ lists:
306
+ partitioning_strategy: balanced
307
+ n_lists: 1
308
+ quantile_balancing: false
309
+
310
+ # Deployment configuration
311
+ deployment:
312
+ platform: jatos
313
+ jspsych_version: "8.0.0"
314
+ plugins: []
315
+ include_attention_checks: true
316
+
317
+ # Training configuration (Stage 6)
318
+ training:
319
+ framework: huggingface
320
+ epochs: 10
321
+ batch_size: 32
322
+ learning_rate: 2.0e-5
323
+ early_stopping: true
324
+ early_stopping_patience: 3
325
+ """
326
+
327
+
328
+ def _generate_gitignore() -> str:
329
+ """Generate .gitignore file content.
330
+
331
+ Returns
332
+ -------
333
+ str
334
+ .gitignore content.
335
+ """
336
+ return """# bead-specific ignores
337
+
338
+ # Cache
339
+ .cache/
340
+ __pycache__/
341
+ *.pyc
342
+
343
+ # Model outputs
344
+ models/*.pt
345
+ models/*.pth
346
+ models/*.bin
347
+
348
+ # Data (comment out if you want to track data)
349
+ data/
350
+ *.jsonl
351
+
352
+ # Experiments
353
+ experiments/
354
+
355
+ # Python
356
+ *.egg-info/
357
+ dist/
358
+ build/
359
+
360
+ # IDE
361
+ .vscode/
362
+ .idea/
363
+ *.swp
364
+ *.swo
365
+
366
+ # OS
367
+ .DS_Store
368
+ Thumbs.db
369
+ """
370
+
371
+
372
+ # Lazy command loading for fast startup
373
+ # Each command group is only imported when actually invoked
374
+
375
+
376
+ class LazyGroup(click.Group):
377
+ """Click group that lazily loads subcommands."""
378
+
379
+ def __init__(
380
+ self,
381
+ name: str | None = None,
382
+ lazy_subcommands: dict[str, tuple[str, str]] | None = None,
383
+ **kwargs: object,
384
+ ) -> None:
385
+ super().__init__(name=name, **kwargs)
386
+ self._lazy_subcommands: dict[str, tuple[str, str]] = lazy_subcommands or {}
387
+
388
+ def list_commands(self, ctx: click.Context) -> list[str]:
389
+ """List all available commands."""
390
+ base = super().list_commands(ctx)
391
+ lazy = list(self._lazy_subcommands.keys())
392
+ return sorted(base + lazy)
393
+
394
+ def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command | None:
395
+ """Get a command, loading it lazily if needed."""
396
+ if cmd_name in self._lazy_subcommands:
397
+ return self._lazy_load(cmd_name)
398
+ return super().get_command(ctx, cmd_name)
399
+
400
+ def _lazy_load(self, cmd_name: str) -> click.Command:
401
+ """Import and return a lazy command."""
402
+ module_path, attr_name = self._lazy_subcommands[cmd_name]
403
+ module = importlib.import_module(module_path)
404
+ return getattr(module, attr_name) # type: ignore[no-any-return]
405
+
406
+
407
+ # Replace cli group with lazy version
408
+ cli = LazyGroup(
409
+ name="bead",
410
+ help=cli.help,
411
+ params=cli.params,
412
+ callback=cli.callback,
413
+ lazy_subcommands={
414
+ "active-learning": ("bead.cli.active_learning", "active_learning"),
415
+ "completion": ("bead.cli.completion", "completion"),
416
+ "config": ("bead.cli.config", "config"),
417
+ "deployment": ("bead.cli.deployment", "deployment"),
418
+ "items": ("bead.cli.items", "items"),
419
+ "lists": ("bead.cli.lists", "lists"),
420
+ "models": ("bead.cli.models", "models"),
421
+ "resources": ("bead.cli.resources", "resources"),
422
+ "shell": ("bead.cli.shell", "shell"),
423
+ "simulate": ("bead.cli.simulate", "simulate"),
424
+ "templates": ("bead.cli.templates", "templates"),
425
+ "training": ("bead.cli.training", "training"),
426
+ "workflow": ("bead.cli.workflow", "workflow"),
427
+ },
428
+ )
429
+ # Re-add the init command which is defined above
430
+ cli.add_command(init)