flock-core 0.3.41__py3-none-any.whl → 0.4.0b2__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.

Potentially problematic release.


This version of flock-core might be problematic. Click here for more details.

flock/core/flock_agent.py CHANGED
@@ -374,10 +374,12 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
374
374
  mode="json", # Use json mode for better handling of standard types by Pydantic
375
375
  exclude_none=True, # Exclude None values for cleaner output
376
376
  )
377
+ logger.debug(f"Base agent data for '{self.name}': {list(data.keys())}")
377
378
 
378
379
  # --- Serialize Components using Registry Type Names ---
379
380
  # Evaluator
380
381
  if self.evaluator:
382
+ logger.debug(f"Serializing evaluator for agent '{self.name}'")
381
383
  evaluator_type_name = FlockRegistry.get_component_type_name(
382
384
  type(self.evaluator)
383
385
  )
@@ -388,6 +390,9 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
388
390
  )
389
391
  evaluator_dict["type"] = evaluator_type_name # Add type marker
390
392
  data["evaluator"] = evaluator_dict
393
+ logger.debug(
394
+ f"Added evaluator of type '{evaluator_type_name}' to agent '{self.name}'"
395
+ )
391
396
  else:
392
397
  logger.warning(
393
398
  f"Could not get registered type name for evaluator {type(self.evaluator).__name__} in agent '{self.name}'. Skipping serialization."
@@ -395,6 +400,7 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
395
400
 
396
401
  # Router
397
402
  if self.handoff_router:
403
+ logger.debug(f"Serializing router for agent '{self.name}'")
398
404
  router_type_name = FlockRegistry.get_component_type_name(
399
405
  type(self.handoff_router)
400
406
  )
@@ -406,6 +412,9 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
406
412
  )
407
413
  router_dict["type"] = router_type_name
408
414
  data["handoff_router"] = router_dict
415
+ logger.debug(
416
+ f"Added router of type '{router_type_name}' to agent '{self.name}'"
417
+ )
409
418
  else:
410
419
  logger.warning(
411
420
  f"Could not get registered type name for router {type(self.handoff_router).__name__} in agent '{self.name}'. Skipping serialization."
@@ -413,6 +422,9 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
413
422
 
414
423
  # Modules
415
424
  if self.modules:
425
+ logger.debug(
426
+ f"Serializing {len(self.modules)} modules for agent '{self.name}'"
427
+ )
416
428
  serialized_modules = {}
417
429
  for name, module_instance in self.modules.items():
418
430
  module_type_name = FlockRegistry.get_component_type_name(
@@ -426,32 +438,55 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
426
438
  )
427
439
  module_dict["type"] = module_type_name
428
440
  serialized_modules[name] = module_dict
441
+ logger.debug(
442
+ f"Added module '{name}' of type '{module_type_name}' to agent '{self.name}'"
443
+ )
429
444
  else:
430
445
  logger.warning(
431
446
  f"Could not get registered type name for module {type(module_instance).__name__} ('{name}') in agent '{self.name}'. Skipping."
432
447
  )
433
448
  if serialized_modules:
434
449
  data["modules"] = serialized_modules
450
+ logger.debug(
451
+ f"Added {len(serialized_modules)} modules to agent '{self.name}'"
452
+ )
435
453
 
436
454
  # --- Serialize Tools (Callables) ---
437
455
  if self.tools:
456
+ logger.debug(
457
+ f"Serializing {len(self.tools)} tools for agent '{self.name}'"
458
+ )
438
459
  serialized_tools = []
439
460
  for tool in self.tools:
440
461
  if callable(tool) and not isinstance(tool, type):
441
462
  path_str = FlockRegistry.get_callable_path_string(tool)
442
463
  if path_str:
443
- serialized_tools.append({"__callable_ref__": path_str})
464
+ # Get just the function name from the path string
465
+ # If it's a namespaced path like module.submodule.function_name
466
+ # Just use the function_name part
467
+ func_name = path_str.split(".")[-1]
468
+ serialized_tools.append(func_name)
469
+ logger.debug(
470
+ f"Added tool '{func_name}' (from path '{path_str}') to agent '{self.name}'"
471
+ )
444
472
  else:
445
473
  logger.warning(
446
474
  f"Could not get path string for tool {tool} in agent '{self.name}'. Skipping."
447
475
  )
448
- # Silently skip non-callable items or log warning
449
- # else:
450
- # logger.warning(f"Non-callable item found in tools list for agent '{self.name}': {tool}. Skipping.")
476
+ else:
477
+ logger.warning(
478
+ f"Non-callable item found in tools list for agent '{self.name}': {tool}. Skipping."
479
+ )
451
480
  if serialized_tools:
452
481
  data["tools"] = serialized_tools
482
+ logger.debug(
483
+ f"Added {len(serialized_tools)} tools to agent '{self.name}'"
484
+ )
453
485
 
454
486
  # No need to call _filter_none_values here as model_dump(exclude_none=True) handles it
487
+ logger.info(
488
+ f"Serialization of agent '{self.name}' complete with {len(data)} fields"
489
+ )
455
490
  return data
456
491
 
457
492
  @classmethod
@@ -466,6 +501,7 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
466
501
  raise ValueError("Agent data must include a 'name' field.")
467
502
  FlockRegistry = get_registry()
468
503
  agent_name = data["name"] # For logging context
504
+ logger.info(f"Deserializing agent '{agent_name}'")
469
505
 
470
506
  # Pop complex components to handle them after basic agent instantiation
471
507
  evaluator_data = data.pop("evaluator", None)
@@ -473,6 +509,10 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
473
509
  modules_data = data.pop("modules", {})
474
510
  tools_data = data.pop("tools", [])
475
511
 
512
+ logger.debug(
513
+ f"Agent '{agent_name}' has {len(modules_data)} modules and {len(tools_data)} tools"
514
+ )
515
+
476
516
  # Deserialize remaining data recursively (handles nested basic types/callables)
477
517
  # Note: Pydantic v2 handles most basic deserialization well if types match.
478
518
  # Explicit deserialize_item might be needed if complex non-pydantic structures exist.
@@ -481,6 +521,9 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
481
521
 
482
522
  try:
483
523
  # Create the agent instance using Pydantic's constructor
524
+ logger.debug(
525
+ f"Creating agent instance with fields: {list(deserialized_basic_data.keys())}"
526
+ )
484
527
  agent = cls(**deserialized_basic_data)
485
528
  except Exception as e:
486
529
  logger.error(
@@ -495,13 +538,16 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
495
538
  # Evaluator
496
539
  if evaluator_data:
497
540
  try:
541
+ logger.debug(
542
+ f"Deserializing evaluator for agent '{agent_name}'"
543
+ )
498
544
  agent.evaluator = deserialize_component(
499
545
  evaluator_data, FlockEvaluator
500
546
  )
501
547
  if agent.evaluator is None:
502
548
  raise ValueError("deserialize_component returned None")
503
549
  logger.debug(
504
- f"Deserialized evaluator '{agent.evaluator.name}' for agent '{agent_name}'"
550
+ f"Deserialized evaluator '{agent.evaluator.name}' of type '{evaluator_data.get('type')}' for agent '{agent_name}'"
505
551
  )
506
552
  except Exception as e:
507
553
  logger.error(
@@ -514,13 +560,14 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
514
560
  # Router
515
561
  if router_data:
516
562
  try:
563
+ logger.debug(f"Deserializing router for agent '{agent_name}'")
517
564
  agent.handoff_router = deserialize_component(
518
565
  router_data, FlockRouter
519
566
  )
520
567
  if agent.handoff_router is None:
521
568
  raise ValueError("deserialize_component returned None")
522
569
  logger.debug(
523
- f"Deserialized router '{agent.handoff_router.name}' for agent '{agent_name}'"
570
+ f"Deserialized router '{agent.handoff_router.name}' of type '{router_data.get('type')}' for agent '{agent_name}'"
524
571
  )
525
572
  except Exception as e:
526
573
  logger.error(
@@ -532,8 +579,14 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
532
579
  # Modules
533
580
  if modules_data:
534
581
  agent.modules = {} # Ensure it's initialized
582
+ logger.debug(
583
+ f"Deserializing {len(modules_data)} modules for agent '{agent_name}'"
584
+ )
535
585
  for name, module_data in modules_data.items():
536
586
  try:
587
+ logger.debug(
588
+ f"Deserializing module '{name}' of type '{module_data.get('type')}' for agent '{agent_name}'"
589
+ )
537
590
  module_instance = deserialize_component(
538
591
  module_data, FlockModule
539
592
  )
@@ -543,6 +596,9 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
543
596
  agent.add_module(
544
597
  module_instance
545
598
  ) # Use add_module for consistency
599
+ logger.debug(
600
+ f"Successfully added module '{name}' to agent '{agent_name}'"
601
+ )
546
602
  else:
547
603
  raise ValueError("deserialize_component returned None")
548
604
  except Exception as e:
@@ -555,25 +611,61 @@ class FlockAgent(BaseModel, Serializable, DSPyIntegrationMixin, ABC):
555
611
  # --- Deserialize Tools ---
556
612
  agent.tools = [] # Initialize tools list
557
613
  if tools_data:
558
- for tool_ref in tools_data:
559
- if (
560
- isinstance(tool_ref, dict)
561
- and "__callable_ref__" in tool_ref
562
- ):
563
- path_str = tool_ref["__callable_ref__"]
564
- try:
565
- tool_func = FlockRegistry.get_callable(path_str)
566
- agent.tools.append(tool_func)
567
- except KeyError:
568
- logger.error(
569
- f"Tool callable '{path_str}' not found in registry for agent '{agent_name}'. Skipping."
614
+ # Get component registry to look up function imports
615
+ registry = get_registry()
616
+ components = getattr(registry, "_callables", {})
617
+ logger.debug(
618
+ f"Deserializing {len(tools_data)} tools for agent '{agent_name}'"
619
+ )
620
+ logger.debug(
621
+ f"Available callables in registry: {list(components.keys())}"
622
+ )
623
+
624
+ for tool_name in tools_data:
625
+ try:
626
+ logger.debug(f"Looking for tool '{tool_name}' in registry")
627
+ # First try to lookup by simple name in the registry's callables
628
+ found = False
629
+ for path_str, func in components.items():
630
+ if (
631
+ path_str.endswith("." + tool_name)
632
+ or path_str == tool_name
633
+ ):
634
+ agent.tools.append(func)
635
+ found = True
636
+ logger.info(
637
+ f"Found tool '{tool_name}' via path '{path_str}' for agent '{agent_name}'"
638
+ )
639
+ break
640
+
641
+ # If not found by simple name, try manual import
642
+ if not found:
643
+ logger.debug(
644
+ f"Attempting to import tool '{tool_name}' from modules"
570
645
  )
571
- else:
572
- logger.warning(
573
- f"Invalid tool format found during deserialization for agent '{agent_name}': {tool_ref}. Skipping."
646
+ # Check in relevant modules (could be customized based on project structure)
647
+ import __main__
648
+
649
+ if hasattr(__main__, tool_name):
650
+ agent.tools.append(getattr(__main__, tool_name))
651
+ found = True
652
+ logger.info(
653
+ f"Found tool '{tool_name}' in __main__ module for agent '{agent_name}'"
654
+ )
655
+
656
+ if not found:
657
+ logger.warning(
658
+ f"Could not find tool '{tool_name}' for agent '{agent_name}'"
659
+ )
660
+ except Exception as e:
661
+ logger.error(
662
+ f"Error adding tool '{tool_name}' to agent '{agent_name}': {e}",
663
+ exc_info=True,
574
664
  )
575
665
 
576
- logger.info(f"Successfully deserialized agent: {agent.name}")
666
+ logger.info(
667
+ f"Successfully deserialized agent '{agent_name}' with {len(agent.modules)} modules and {len(agent.tools)} tools"
668
+ )
577
669
  return agent
578
670
 
579
671
  # --- Pydantic v2 Configuration ---
@@ -162,16 +162,20 @@ class FlockRegistry:
162
162
  and self._callables[path_str] != func
163
163
  ):
164
164
  logger.warning(
165
- f"Callable '{path_str}' already registered. Overwriting."
165
+ f"Callable '{path_str}' already registered with a different function. Overwriting."
166
166
  )
167
167
  self._callables[path_str] = func
168
- logger.debug(f"Registered callable: {path_str}")
168
+ logger.debug(f"Registered callable: '{path_str}' ({func.__name__})")
169
169
  return path_str
170
+ logger.warning(
171
+ f"Could not register callable {func.__name__}: Unable to determine path string"
172
+ )
170
173
  return None
171
174
 
172
175
  def get_callable(self, path_str: str) -> Callable:
173
176
  """Retrieves a callable by its path string, attempting dynamic import if not found."""
174
177
  if path_str in self._callables:
178
+ logger.debug(f"Found callable '{path_str}' in registry")
175
179
  return self._callables[path_str]
176
180
 
177
181
  logger.debug(
@@ -179,14 +183,20 @@ class FlockRegistry:
179
183
  )
180
184
  try:
181
185
  if "." not in path_str: # Built-ins
186
+ logger.debug(f"Trying to import built-in callable '{path_str}'")
182
187
  builtins_module = importlib.import_module("builtins")
183
188
  if hasattr(builtins_module, path_str):
184
189
  func = getattr(builtins_module, path_str)
185
190
  if callable(func):
186
191
  self.register_callable(func, path_str) # Cache it
192
+ logger.info(
193
+ f"Successfully imported built-in callable '{path_str}'"
194
+ )
187
195
  return func
196
+ logger.error(f"Built-in callable '{path_str}' not found.")
188
197
  raise KeyError(f"Built-in callable '{path_str}' not found.")
189
198
 
199
+ logger.debug(f"Trying to import module callable '{path_str}'")
190
200
  module_name, func_name = path_str.rsplit(".", 1)
191
201
  module = importlib.import_module(module_name)
192
202
  func = getattr(module, func_name)
@@ -194,14 +204,21 @@ class FlockRegistry:
194
204
  self.register_callable(
195
205
  func, path_str
196
206
  ) # Cache dynamically imported
207
+ logger.info(
208
+ f"Successfully imported module callable '{path_str}'"
209
+ )
197
210
  return func
198
211
  else:
212
+ logger.error(
213
+ f"Dynamically imported object '{path_str}' is not callable."
214
+ )
199
215
  raise TypeError(
200
216
  f"Dynamically imported object '{path_str}' is not callable."
201
217
  )
202
218
  except (ImportError, AttributeError, KeyError, TypeError) as e:
203
219
  logger.error(
204
- f"Failed to dynamically load/find callable '{path_str}': {e}"
220
+ f"Failed to dynamically load/find callable '{path_str}': {e}",
221
+ exc_info=True,
205
222
  )
206
223
  raise KeyError(
207
224
  f"Callable '{path_str}' not found or failed to load: {e}"
@@ -209,11 +226,26 @@ class FlockRegistry:
209
226
 
210
227
  def get_callable_path_string(self, func: Callable) -> str | None:
211
228
  """Gets the path string for a callable, registering it if necessary."""
229
+ # First try to find by direct identity
212
230
  for path_str, registered_func in self._callables.items():
213
231
  if func == registered_func:
232
+ logger.debug(
233
+ f"Found existing path string for callable: '{path_str}'"
234
+ )
214
235
  return path_str
236
+
215
237
  # If not found by identity, generate path, register, and return
216
- return self.register_callable(func)
238
+ path_str = self.register_callable(func)
239
+ if path_str:
240
+ logger.debug(
241
+ f"Generated and registered new path string for callable: '{path_str}'"
242
+ )
243
+ else:
244
+ logger.warning(
245
+ f"Failed to generate path string for callable {func.__name__}"
246
+ )
247
+
248
+ return path_str
217
249
 
218
250
  # --- Type Registration ---
219
251
  def register_type(
@@ -227,7 +259,7 @@ class FlockRegistry:
227
259
  f"Type '{type_name}' already registered. Overwriting."
228
260
  )
229
261
  self._types[type_name] = type_obj
230
- # logger.debug(f"Registered type: {type_name}")
262
+ logger.debug(f"Registered type: {type_name}")
231
263
  return type_name
232
264
  return None
233
265
 
@@ -2,7 +2,7 @@
2
2
  import json
3
3
  from abc import ABC, abstractmethod
4
4
  from pathlib import Path
5
- from typing import Any, TypeVar
5
+ from typing import Any, Literal, TypeVar
6
6
 
7
7
  # Use yaml if available, otherwise skip yaml methods
8
8
  try:
@@ -85,16 +85,32 @@ class Serializable(ABC):
85
85
  ) from e
86
86
 
87
87
  # --- YAML Methods ---
88
- def to_yaml(self, sort_keys=False, default_flow_style=False) -> str:
89
- """Serialize to YAML string."""
88
+ def to_yaml(
89
+ self,
90
+ path_type: Literal["absolute", "relative"] = "relative",
91
+ sort_keys=False,
92
+ default_flow_style=False,
93
+ ) -> str:
94
+ """Serialize to YAML string.
95
+
96
+ Args:
97
+ path_type: How file paths should be formatted ('absolute' or 'relative')
98
+ sort_keys: Whether to sort dictionary keys
99
+ default_flow_style: YAML flow style setting
100
+ """
90
101
  if not YAML_AVAILABLE:
91
102
  raise NotImplementedError(
92
103
  "YAML support requires PyYAML: pip install pyyaml"
93
104
  )
94
105
  try:
95
- # to_dict should prepare a structure suitable for YAML dumping
106
+ # If to_dict supports path_type, pass it; otherwise use standard to_dict
107
+ if "path_type" in self.to_dict.__code__.co_varnames:
108
+ dict_data = self.to_dict(path_type=path_type)
109
+ else:
110
+ dict_data = self.to_dict()
111
+
96
112
  return yaml.dump(
97
- self.to_dict(),
113
+ dict_data,
98
114
  sort_keys=sort_keys,
99
115
  default_flow_style=default_flow_style,
100
116
  allow_unicode=True,
@@ -125,8 +141,19 @@ class Serializable(ABC):
125
141
  f"Failed to deserialize {cls.__name__} from YAML: {e}"
126
142
  ) from e
127
143
 
128
- def to_yaml_file(self, path: Path | str, **yaml_dump_kwargs) -> None:
129
- """Serialize to YAML file."""
144
+ def to_yaml_file(
145
+ self,
146
+ path: Path | str,
147
+ path_type: Literal["absolute", "relative"] = "relative",
148
+ **yaml_dump_kwargs,
149
+ ) -> None:
150
+ """Serialize to YAML file.
151
+
152
+ Args:
153
+ path: File path to write to
154
+ path_type: How file paths should be formatted ('absolute' or 'relative')
155
+ **yaml_dump_kwargs: Additional arguments to pass to yaml.dump
156
+ """
130
157
  if not YAML_AVAILABLE:
131
158
  raise NotImplementedError(
132
159
  "YAML support requires PyYAML: pip install pyyaml"
@@ -134,7 +161,7 @@ class Serializable(ABC):
134
161
  path = Path(path)
135
162
  try:
136
163
  path.parent.mkdir(parents=True, exist_ok=True)
137
- yaml_str = self.to_yaml(**yaml_dump_kwargs)
164
+ yaml_str = self.to_yaml(path_type=path_type, **yaml_dump_kwargs)
138
165
  path.write_text(yaml_str, encoding="utf-8")
139
166
  except Exception as e:
140
167
  raise RuntimeError(
@@ -1,9 +1,12 @@
1
1
  # src/flock/core/serialization/serialization_utils.py
2
2
  """Utilities for recursive serialization/deserialization with callable handling."""
3
3
 
4
+ import ast
5
+ import builtins
4
6
  import importlib
7
+ import sys
5
8
  from collections.abc import Mapping, Sequence
6
- from typing import TYPE_CHECKING, Any
9
+ from typing import TYPE_CHECKING, Any, get_args, get_origin
7
10
 
8
11
  from pydantic import BaseModel
9
12
 
@@ -21,6 +24,98 @@ logger = get_logger("serialization.utils")
21
24
  # --- Serialization Helper ---
22
25
 
23
26
 
27
+ def extract_identifiers_from_type_str(type_str: str) -> set[str]:
28
+ """Extract all identifiers from a type annotation string using the AST."""
29
+ tree = ast.parse(type_str, mode="eval")
30
+ identifiers = set()
31
+
32
+ class IdentifierVisitor(ast.NodeVisitor):
33
+ def visit_Name(self, node):
34
+ identifiers.add(node.id)
35
+
36
+ def visit_Attribute(self, node):
37
+ # Optionally support dotted names like mymodule.MyModel
38
+ full_name = []
39
+ while isinstance(node, ast.Attribute):
40
+ full_name.append(node.attr)
41
+ node = node.value
42
+ if isinstance(node, ast.Name):
43
+ full_name.append(node.id)
44
+ identifiers.add(".".join(reversed(full_name)))
45
+
46
+ IdentifierVisitor().visit(tree)
47
+ return identifiers
48
+
49
+
50
+ def resolve_name(name: str):
51
+ """Resolve a name to a Python object from loaded modules."""
52
+ # Try dotted names first
53
+ parts = name.split(".")
54
+ obj = None
55
+
56
+ if len(parts) == 1:
57
+ # Search globals and builtins
58
+ if parts[0] in globals():
59
+ return globals()[parts[0]]
60
+ if parts[0] in builtins.__dict__:
61
+ return builtins.__dict__[parts[0]]
62
+ else:
63
+ try:
64
+ obj = sys.modules[parts[0]]
65
+ for part in parts[1:]:
66
+ obj = getattr(obj, part)
67
+ return obj
68
+ except Exception:
69
+ return None
70
+
71
+ # Try all loaded modules' symbols
72
+ for module in list(sys.modules.values()):
73
+ if module is None or not hasattr(module, "__dict__"):
74
+ continue
75
+ if parts[0] in module.__dict__:
76
+ return module.__dict__[parts[0]]
77
+
78
+ return None
79
+
80
+
81
+ def extract_pydantic_models_from_type_string(
82
+ type_str: str,
83
+ ) -> list[type[BaseModel]]:
84
+ identifiers = extract_identifiers_from_type_str(type_str)
85
+ models = []
86
+ for name in identifiers:
87
+ resolved = resolve_name(name)
88
+ if (
89
+ isinstance(resolved, type)
90
+ and issubclass(resolved, BaseModel)
91
+ and resolved is not BaseModel
92
+ ):
93
+ models.append(resolved)
94
+ return models
95
+
96
+
97
+ def collect_pydantic_models(
98
+ type_hint, seen: set[type[BaseModel]] | None = None
99
+ ) -> set[type[BaseModel]]:
100
+ if seen is None:
101
+ seen = set()
102
+
103
+ origin = get_origin(type_hint)
104
+ args = get_args(type_hint)
105
+
106
+ # Direct BaseModel
107
+ if isinstance(type_hint, type) and issubclass(type_hint, BaseModel):
108
+ seen.add(type_hint)
109
+ return seen
110
+
111
+ # For Unions, Lists, Dicts, Tuples, etc.
112
+ if origin is not None:
113
+ for arg in args:
114
+ collect_pydantic_models(arg, seen)
115
+
116
+ return seen
117
+
118
+
24
119
  def serialize_item(item: Any) -> Any:
25
120
  """Recursively prepares an item for serialization (e.g., to dict for YAML/JSON).
26
121
  Converts known callables to their path strings using FlockRegistry.
@@ -52,7 +52,7 @@ def init_console(clear_screen: bool = True):
52
52
  │ ▒█▀▀▀ █░░ █▀▀█ █▀▀ █░█ │
53
53
  │ ▒█▀▀▀ █░░ █░░█ █░░ █▀▄ │
54
54
  │ ▒█░░░ ▀▀▀ ▀▀▀▀ ▀▀▀ ▀░▀ │
55
- ╰━━━━━━━━━v{__version__}━━━━━━━━╯
55
+ ╰━━━━━━━━v{__version__}━━━━━━━━╯
56
56
  🦆 🐤 🐧 🐓
57
57
  """,
58
58
  justify="center",
@@ -62,7 +62,7 @@ def init_console(clear_screen: bool = True):
62
62
  console.clear()
63
63
  console.print(banner_text)
64
64
  console.print(
65
- f"[italic]'Hummingbird'[/] milestone - [bold]white duck GmbH[/] - [cyan]https://whiteduck.de[/]\n"
65
+ f"[italic]'Magpie'[/] milestone - [bold]white duck GmbH[/] - [cyan]https://whiteduck.de[/]\n"
66
66
  )
67
67
 
68
68