pygeai 0.6.0b10__py3-none-any.whl → 0.6.0b12__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 (135) hide show
  1. pygeai/_docs/source/content/ai_lab/cli.rst +4 -4
  2. pygeai/_docs/source/content/ai_lab/models.rst +169 -35
  3. pygeai/_docs/source/content/ai_lab/runner.rst +2 -2
  4. pygeai/_docs/source/content/ai_lab/spec.rst +9 -9
  5. pygeai/_docs/source/content/ai_lab/usage.rst +34 -34
  6. pygeai/_docs/source/content/ai_lab.rst +1 -1
  7. pygeai/_docs/source/content/analytics.rst +598 -0
  8. pygeai/_docs/source/content/api_reference/chat.rst +428 -2
  9. pygeai/_docs/source/content/api_reference/embeddings.rst +1 -1
  10. pygeai/_docs/source/content/api_reference/project.rst +184 -0
  11. pygeai/_docs/source/content/api_reference/rag.rst +2 -2
  12. pygeai/_docs/source/content/authentication.rst +295 -0
  13. pygeai/_docs/source/content/cli.rst +79 -2
  14. pygeai/_docs/source/content/debugger.rst +1 -1
  15. pygeai/_docs/source/content/migration.rst +19 -2
  16. pygeai/_docs/source/index.rst +2 -0
  17. pygeai/_docs/source/pygeai.analytics.rst +53 -0
  18. pygeai/_docs/source/pygeai.cli.commands.rst +8 -0
  19. pygeai/_docs/source/pygeai.rst +1 -0
  20. pygeai/_docs/source/pygeai.tests.analytics.rst +45 -0
  21. pygeai/_docs/source/pygeai.tests.auth.rst +8 -0
  22. pygeai/_docs/source/pygeai.tests.rst +1 -1
  23. pygeai/analytics/__init__.py +0 -0
  24. pygeai/analytics/clients.py +505 -0
  25. pygeai/analytics/endpoints.py +35 -0
  26. pygeai/analytics/managers.py +606 -0
  27. pygeai/analytics/mappers.py +207 -0
  28. pygeai/analytics/responses.py +240 -0
  29. pygeai/chat/clients.py +46 -1
  30. pygeai/chat/endpoints.py +1 -0
  31. pygeai/cli/commands/analytics.py +525 -0
  32. pygeai/cli/commands/base.py +16 -0
  33. pygeai/cli/commands/chat.py +95 -0
  34. pygeai/cli/commands/common.py +28 -24
  35. pygeai/cli/commands/migrate.py +75 -6
  36. pygeai/cli/commands/organization.py +265 -0
  37. pygeai/cli/commands/validators.py +144 -1
  38. pygeai/cli/error_handler.py +41 -6
  39. pygeai/cli/geai.py +99 -16
  40. pygeai/cli/parsers.py +75 -31
  41. pygeai/cli/texts/help.py +75 -6
  42. pygeai/core/base/clients.py +18 -4
  43. pygeai/core/base/session.py +46 -7
  44. pygeai/core/common/config.py +25 -2
  45. pygeai/core/common/exceptions.py +64 -1
  46. pygeai/core/services/rest.py +20 -2
  47. pygeai/evaluation/clients.py +5 -3
  48. pygeai/lab/agents/clients.py +3 -3
  49. pygeai/lab/agents/endpoints.py +2 -2
  50. pygeai/lab/agents/mappers.py +50 -2
  51. pygeai/lab/clients.py +5 -2
  52. pygeai/lab/managers.py +7 -9
  53. pygeai/lab/models.py +70 -2
  54. pygeai/lab/tools/clients.py +1 -59
  55. pygeai/migration/__init__.py +3 -1
  56. pygeai/migration/strategies.py +72 -3
  57. pygeai/organization/clients.py +110 -1
  58. pygeai/organization/endpoints.py +11 -7
  59. pygeai/organization/managers.py +134 -2
  60. pygeai/organization/mappers.py +28 -2
  61. pygeai/organization/responses.py +11 -1
  62. pygeai/tests/analytics/__init__.py +0 -0
  63. pygeai/tests/analytics/test_clients.py +86 -0
  64. pygeai/tests/analytics/test_managers.py +94 -0
  65. pygeai/tests/analytics/test_mappers.py +84 -0
  66. pygeai/tests/analytics/test_responses.py +73 -0
  67. pygeai/tests/auth/test_oauth.py +172 -0
  68. pygeai/tests/cli/commands/test_migrate.py +14 -1
  69. pygeai/tests/cli/commands/test_organization.py +69 -1
  70. pygeai/tests/cli/test_error_handler.py +4 -4
  71. pygeai/tests/cli/test_geai_driver.py +1 -1
  72. pygeai/tests/lab/agents/test_mappers.py +128 -1
  73. pygeai/tests/lab/test_models.py +2 -0
  74. pygeai/tests/lab/tools/test_clients.py +2 -31
  75. pygeai/tests/organization/test_clients.py +180 -1
  76. pygeai/tests/organization/test_managers.py +40 -0
  77. pygeai/tests/snippets/analytics/__init__.py +0 -0
  78. pygeai/tests/snippets/analytics/get_agent_usage_per_user.py +16 -0
  79. pygeai/tests/snippets/analytics/get_agents_created_and_modified.py +11 -0
  80. pygeai/tests/snippets/analytics/get_average_cost_per_request.py +10 -0
  81. pygeai/tests/snippets/analytics/get_overall_error_rate.py +10 -0
  82. pygeai/tests/snippets/analytics/get_top_10_agents_by_requests.py +12 -0
  83. pygeai/tests/snippets/analytics/get_total_active_users.py +10 -0
  84. pygeai/tests/snippets/analytics/get_total_cost.py +10 -0
  85. pygeai/tests/snippets/analytics/get_total_requests_per_day.py +12 -0
  86. pygeai/tests/snippets/analytics/get_total_tokens.py +12 -0
  87. pygeai/tests/snippets/chat/get_response_complete_example.py +67 -0
  88. pygeai/tests/snippets/chat/get_response_with_instructions.py +19 -0
  89. pygeai/tests/snippets/chat/get_response_with_metadata.py +24 -0
  90. pygeai/tests/snippets/chat/get_response_with_parallel_tools.py +58 -0
  91. pygeai/tests/snippets/chat/get_response_with_reasoning.py +21 -0
  92. pygeai/tests/snippets/chat/get_response_with_store.py +38 -0
  93. pygeai/tests/snippets/chat/get_response_with_truncation.py +24 -0
  94. pygeai/tests/snippets/lab/agents/create_agent_with_permissions.py +39 -0
  95. pygeai/tests/snippets/lab/agents/create_agent_with_properties.py +46 -0
  96. pygeai/tests/snippets/lab/agents/get_agent_with_new_fields.py +62 -0
  97. pygeai/tests/snippets/lab/agents/update_agent_properties.py +50 -0
  98. pygeai/tests/snippets/organization/add_project_member.py +10 -0
  99. pygeai/tests/snippets/organization/add_project_member_batch.py +44 -0
  100. {pygeai-0.6.0b10.dist-info → pygeai-0.6.0b12.dist-info}/METADATA +1 -1
  101. {pygeai-0.6.0b10.dist-info → pygeai-0.6.0b12.dist-info}/RECORD +105 -95
  102. pygeai/_docs/source/pygeai.tests.snippets.assistants.data_analyst.rst +0 -37
  103. pygeai/_docs/source/pygeai.tests.snippets.assistants.rag.rst +0 -85
  104. pygeai/_docs/source/pygeai.tests.snippets.assistants.rst +0 -78
  105. pygeai/_docs/source/pygeai.tests.snippets.auth.rst +0 -10
  106. pygeai/_docs/source/pygeai.tests.snippets.chat.rst +0 -125
  107. pygeai/_docs/source/pygeai.tests.snippets.dbg.rst +0 -45
  108. pygeai/_docs/source/pygeai.tests.snippets.embeddings.rst +0 -61
  109. pygeai/_docs/source/pygeai.tests.snippets.evaluation.dataset.rst +0 -197
  110. pygeai/_docs/source/pygeai.tests.snippets.evaluation.plan.rst +0 -133
  111. pygeai/_docs/source/pygeai.tests.snippets.evaluation.result.rst +0 -37
  112. pygeai/_docs/source/pygeai.tests.snippets.evaluation.rst +0 -20
  113. pygeai/_docs/source/pygeai.tests.snippets.extras.rst +0 -37
  114. pygeai/_docs/source/pygeai.tests.snippets.files.rst +0 -53
  115. pygeai/_docs/source/pygeai.tests.snippets.gam.rst +0 -21
  116. pygeai/_docs/source/pygeai.tests.snippets.lab.agents.rst +0 -93
  117. pygeai/_docs/source/pygeai.tests.snippets.lab.processes.jobs.rst +0 -21
  118. pygeai/_docs/source/pygeai.tests.snippets.lab.processes.kbs.rst +0 -45
  119. pygeai/_docs/source/pygeai.tests.snippets.lab.processes.rst +0 -46
  120. pygeai/_docs/source/pygeai.tests.snippets.lab.rst +0 -82
  121. pygeai/_docs/source/pygeai.tests.snippets.lab.samples.rst +0 -21
  122. pygeai/_docs/source/pygeai.tests.snippets.lab.strategies.rst +0 -45
  123. pygeai/_docs/source/pygeai.tests.snippets.lab.tools.rst +0 -85
  124. pygeai/_docs/source/pygeai.tests.snippets.lab.use_cases.rst +0 -117
  125. pygeai/_docs/source/pygeai.tests.snippets.migrate.rst +0 -10
  126. pygeai/_docs/source/pygeai.tests.snippets.organization.rst +0 -109
  127. pygeai/_docs/source/pygeai.tests.snippets.rag.rst +0 -85
  128. pygeai/_docs/source/pygeai.tests.snippets.rerank.rst +0 -21
  129. pygeai/_docs/source/pygeai.tests.snippets.rst +0 -32
  130. pygeai/_docs/source/pygeai.tests.snippets.secrets.rst +0 -10
  131. pygeai/_docs/source/pygeai.tests.snippets.usage_limit.rst +0 -77
  132. {pygeai-0.6.0b10.dist-info → pygeai-0.6.0b12.dist-info}/WHEEL +0 -0
  133. {pygeai-0.6.0b10.dist-info → pygeai-0.6.0b12.dist-info}/entry_points.txt +0 -0
  134. {pygeai-0.6.0b10.dist-info → pygeai-0.6.0b12.dist-info}/licenses/LICENSE +0 -0
  135. {pygeai-0.6.0b10.dist-info → pygeai-0.6.0b12.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,16 @@
1
1
  import traceback
2
2
  from difflib import SequenceMatcher
3
- from typing import List, Optional
3
+ from typing import List, Optional, Tuple
4
4
 
5
5
  from pygeai import logger
6
6
  from pygeai.cli.commands import Command, Option
7
7
  from pygeai.core.utils.console import Console
8
8
 
9
9
 
10
+ FUZZY_MATCH_THRESHOLD = 0.6
11
+ MAX_FUZZY_SUGGESTIONS = 3
12
+
13
+
10
14
  class ExitCode:
11
15
  SUCCESS = 0
12
16
  USER_INPUT_ERROR = 1
@@ -19,27 +23,58 @@ class ExitCode:
19
23
  class ErrorHandler:
20
24
 
21
25
  @staticmethod
22
- def format_error(error_type: str, message: str, suggestion: Optional[str] = None, show_help: bool = True) -> str:
23
- output = f"ERROR: {message}"
26
+ def format_error(
27
+ error_type: str,
28
+ message: str,
29
+ suggestion: Optional[str] = None,
30
+ show_help: bool = True,
31
+ example: Optional[str] = None
32
+ ) -> str:
33
+ """
34
+ Formats an error message with optional suggestion and example.
35
+
36
+ :param error_type: str - Type of error (e.g., "Unknown Command").
37
+ :param message: str - The error message.
38
+ :param suggestion: Optional[str] - Suggested fix or next steps.
39
+ :param show_help: bool - Whether to show help command hint.
40
+ :param example: Optional[str] - Example of correct usage.
41
+ :return: str - Formatted error message.
42
+ """
43
+ output = f"ERROR [{error_type}]: {message}"
24
44
 
25
45
  if suggestion:
26
46
  output += f"\n → {suggestion}"
27
47
 
48
+ if example:
49
+ output += f"\n\n Example:\n {example}"
50
+
28
51
  if show_help:
29
52
  output += "\n\nRun 'geai help' for usage information."
30
53
 
31
54
  return output
32
55
 
33
56
  @staticmethod
34
- def find_similar_items(item: str, available_items: List[str], threshold: float = 0.6) -> List[str]:
35
- similarities = []
57
+ def find_similar_items(
58
+ item: str,
59
+ available_items: List[str],
60
+ threshold: float = FUZZY_MATCH_THRESHOLD
61
+ ) -> List[str]:
62
+ """
63
+ Finds similar items using fuzzy string matching.
64
+
65
+ :param item: str - The item to match against.
66
+ :param available_items: List[str] - List of available items.
67
+ :param threshold: float - Minimum similarity ratio (0.0 to 1.0).
68
+ :return: List[str] - List of similar items, up to MAX_FUZZY_SUGGESTIONS.
69
+ """
70
+ similarities: List[Tuple[str, float]] = []
36
71
  for available in available_items:
37
72
  ratio = SequenceMatcher(None, item.lower(), available.lower()).ratio()
38
73
  if ratio >= threshold:
39
74
  similarities.append((available, ratio))
40
75
 
41
76
  similarities.sort(key=lambda x: x[1], reverse=True)
42
- return [item[0] for item in similarities[:3]]
77
+ return [item[0] for item in similarities[:MAX_FUZZY_SUGGESTIONS]]
43
78
 
44
79
  @staticmethod
45
80
  def get_available_commands(commands: List[Command]) -> List[str]:
pygeai/cli/geai.py CHANGED
@@ -1,4 +1,6 @@
1
1
  import sys
2
+ import logging
3
+ from typing import List, Optional
2
4
 
3
5
  from pygeai import logger
4
6
  from pygeai.cli.commands.base import base_commands, base_options
@@ -12,18 +14,61 @@ from pygeai.core.common.exceptions import UnknownArgumentError, MissingRequireme
12
14
  from pygeai.core.utils.console import Console
13
15
 
14
16
 
15
- def main():
17
+ def setup_verbose_logging() -> None:
18
+ """
19
+ Configure verbose logging for the CLI.
20
+
21
+ Sets up a console handler with DEBUG level logging and a formatted output
22
+ that includes timestamp, logger name, level, and message.
23
+ """
24
+ if logger.handlers:
25
+ for handler in logger.handlers:
26
+ if not isinstance(handler, logging.NullHandler):
27
+ return
28
+
29
+ logger.setLevel(logging.DEBUG)
30
+ console_handler = logging.StreamHandler(sys.stderr)
31
+ console_handler.setLevel(logging.DEBUG)
32
+ formatter = logging.Formatter(
33
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
34
+ datefmt='%Y-%m-%d %H:%M:%S'
35
+ )
36
+ console_handler.setFormatter(formatter)
37
+ logger.addHandler(console_handler)
38
+ logger.propagate = False
39
+ logger.debug("Verbose mode enabled")
40
+
41
+
42
+ def main() -> int:
43
+ """
44
+ Main entry point for the GEAI CLI application.
45
+
46
+ :return: int - Exit code indicating success or error.
47
+ """
16
48
  driver = CLIDriver()
17
49
  return driver.main()
18
50
 
19
51
 
20
52
  class CLIDriver:
21
-
22
- def __init__(self, session=None):
53
+ """
54
+ Main CLI driver for the GEAI command-line interface.
55
+
56
+ The CLIDriver orchestrates command parsing, execution, and error handling
57
+ for all GEAI CLI operations. It supports multi-profile session management
58
+ via the --alias flag and provides comprehensive error handling with
59
+ user-friendly messages.
60
+ """
61
+
62
+ def __init__(self, session=None) -> None:
23
63
  """
24
- Sets session to be used while running the command, either with a specified alias,
25
- environment variables or function parameters.
26
- Once the session is defined, it won't change during the curse of the execution.
64
+ Initialize the CLI driver with optional session.
65
+
66
+ Sets up the session to be used while running commands, either with a
67
+ specified alias, environment variables, or function parameters.
68
+ Once the session is defined, it won't change during the execution.
69
+
70
+ :param session: Optional session object. If None, uses 'default' or
71
+ alias-specified session from command-line arguments.
27
72
  """
28
73
  arguments = sys.argv
29
74
  if "-a" in arguments or "--alias" in arguments:
@@ -32,9 +77,13 @@ class CLIDriver:
32
77
 
33
78
  self.session = get_session("default") if session is None else session
34
79
 
35
- def _get_alias(self, arguments: list):
80
+ def _get_alias(self, arguments: List[str]) -> str:
36
81
  """
37
- Retrieves and removes alias and alias flag from argument list
82
+ Retrieves and removes alias and alias flag from argument list.
83
+
84
+ :param arguments: List[str] - Command line arguments.
85
+ :return: str - The alias value.
86
+ :raises ValueError: If alias flag is present but no value provided.
38
87
  """
39
88
  alias_index = None
40
89
 
@@ -47,24 +96,46 @@ class CLIDriver:
47
96
  alias = arguments.pop(alias_index)
48
97
  return alias
49
98
 
50
- def main(self, args=None):
99
+ def main(self, args: Optional[List[str]] = None) -> int:
51
100
  """
52
- If not argument is received, it defaults to help (first command in base_command list).
101
+ Execute the CLI command based on provided arguments.
102
+
103
+ If no argument is received, it defaults to help (first command in base_command list).
53
104
  Otherwise, it parses the arguments received to identify the appropriate command and either
54
105
  execute it or parse it again to detect subcommands.
106
+
107
+ :param args: Optional[List[str]] - Command line arguments. If None, uses sys.argv.
108
+ :return: int - Exit code (0 for success, non-zero for errors).
55
109
  """
56
110
  try:
57
- logger.debug(f"Running geai with: {' '.join(a for a in sys.argv)}")
58
- if len(sys.argv) > 1:
59
- arg = sys.argv[1] if args is None else args[1]
60
- arguments = sys.argv[2:] if args is None else args[2:]
61
-
111
+ argv = sys.argv if args is None else args
112
+
113
+ if "--verbose" in argv or "-v" in argv:
114
+ setup_verbose_logging()
115
+ argv_copy = [a for a in argv if a not in ("--verbose", "-v")]
116
+ if args is None:
117
+ sys.argv = argv_copy
118
+ else:
119
+ args = argv_copy
120
+ argv = argv_copy
121
+
122
+ logger.debug(f"Running geai with: {' '.join(a for a in argv)}")
123
+ logger.debug(f"Session: {self.session.alias if hasattr(self.session, 'alias') else 'default'}")
124
+
125
+ if len(argv) > 1:
126
+ arg = argv[1] if args is None else args[1]
127
+ arguments = argv[2:] if args is None else args[2:]
128
+
129
+ logger.debug(f"Identifying command for argument: {arg}")
62
130
  command = CommandParser(base_commands, base_options).identify_command(arg)
131
+ logger.debug(f"Command identified: {command.name}")
63
132
  else:
133
+ logger.debug("No arguments provided, defaulting to help command")
64
134
  command = base_commands[0]
65
135
  arguments = []
66
136
 
67
137
  self.process_command(command, arguments)
138
+ logger.debug(f"Command completed successfully")
68
139
  return ExitCode.SUCCESS
69
140
  except UnknownArgumentError as e:
70
141
  if hasattr(e, 'available_commands') and e.available_commands:
@@ -102,27 +173,39 @@ class CLIDriver:
102
173
  If the command has no action associated with it, it means it has subcommands, so it must be parsed again
103
174
  to identify it.
104
175
  """
176
+ logger.debug(f"Processing command: {command.name}, arguments: {arguments}")
177
+
105
178
  if command.action:
106
179
  if command.additional_args == ArgumentsEnum.NOT_AVAILABLE:
180
+ logger.debug(f"Executing command {command.name} without arguments")
107
181
  command.action()
108
182
  else:
183
+ logger.debug(f"Extracting options for command {command.name}")
109
184
  option_list = CommandParser(base_commands, command.options).extract_option_list(arguments)
185
+ logger.debug(f"Options extracted: {len(option_list)} items")
110
186
  command.action(option_list)
111
187
  elif command.subcommands:
112
188
  subcommand_arg = arguments[0] if len(arguments) > 0 else None
113
189
  subcommand_arguments = arguments[1:] if len(arguments) > 1 else []
114
-
190
+
191
+ logger.debug(f"Command has subcommands, identifying: {subcommand_arg}")
192
+
115
193
  available_commands = command.subcommands
116
194
  available_options = command.options
117
195
  parser = CommandParser(available_commands, available_options)
118
196
 
119
197
  if not subcommand_arg:
198
+ logger.debug(f"No subcommand specified, using default: {command.subcommands[0].name}")
120
199
  subcommand = command.subcommands[0]
121
200
  else:
122
201
  subcommand = parser.identify_command(subcommand_arg)
202
+ logger.debug(f"Subcommand identified: {subcommand.name}")
123
203
 
124
204
  if subcommand.additional_args == ArgumentsEnum.NOT_AVAILABLE:
205
+ logger.debug(f"Executing subcommand {subcommand.name} without arguments")
125
206
  subcommand.action()
126
207
  else:
208
+ logger.debug(f"Extracting options for subcommand {subcommand.name}")
127
209
  option_list = CommandParser(None, subcommand.options).extract_option_list(subcommand_arguments)
210
+ logger.debug(f"Options extracted: {len(option_list)} items")
128
211
  subcommand.action(option_list)
pygeai/cli/parsers.py CHANGED
@@ -1,3 +1,6 @@
1
+ from typing import List, Tuple, Optional
2
+
3
+ from pygeai import logger
1
4
  from pygeai.cli.commands.base import base_options
2
5
  from pygeai.cli.commands import Command, Option
3
6
  from pygeai.core.common.exceptions import UnknownArgumentError, MissingRequirementException
@@ -5,7 +8,17 @@ from pygeai.core.common.exceptions import UnknownArgumentError, MissingRequireme
5
8
 
6
9
  class CommandParser:
7
10
 
8
- def __init__(self, available_commands, available_options):
11
+ def __init__(
12
+ self,
13
+ available_commands: Optional[List[Command]],
14
+ available_options: Optional[List[Option]]
15
+ ) -> None:
16
+ """
17
+ Initialize a CommandParser with available commands and options.
18
+
19
+ :param available_commands: Optional[List[Command]] - List of valid commands, or None.
20
+ :param available_options: Optional[List[Option]] - List of valid options, or None.
21
+ """
9
22
  self.available_commands = available_commands
10
23
  self.available_options = available_options
11
24
 
@@ -13,26 +26,34 @@ class CommandParser:
13
26
  """
14
27
  Analyzes the first argument and checks if it's a valid command.
15
28
 
16
- :param first_argument: The first argument to be analyzed.
17
- :return: The command to be run.
29
+ :param arg: str - The argument to be analyzed.
30
+ :return: Command - The identified command object.
31
+ :raises UnknownArgumentError: If the argument is not a valid command.
18
32
  """
33
+ logger.debug(f"Searching for command matching: {arg}")
19
34
  command = self._get_associated_command(arg)
20
35
  if not command:
21
- error = UnknownArgumentError(f"'{arg}' is not a valid command.")
22
- error.arg = arg
23
- error.available_commands = self.available_commands
24
- raise error
25
-
36
+ logger.debug(f"No command found for: {arg}")
37
+ raise UnknownArgumentError(
38
+ f"'{arg}' is not a valid command.",
39
+ arg=arg,
40
+ available_commands=self.available_commands
41
+ )
42
+
43
+ logger.debug(f"Command found: {command.name} (identifiers: {command.identifiers})")
26
44
  return command
27
45
 
28
- def extract_option_list(self, arguments: list) -> list[(Option, str)]:
46
+ def extract_option_list(self, arguments: List[str]) -> List[Tuple[Option, str]]:
29
47
  """
30
- Parses a list of arguments and returns the commands being invoked.
48
+ Parses a list of arguments and returns the options being invoked.
31
49
 
32
- :param arguments: list - The list of arguments received by the CLI utility.
33
- :return: list - A list of flags and their associated arguments.
50
+ :param arguments: List[str] - The list of arguments received by the CLI utility.
51
+ :return: List[Tuple[Option, str]] - A list of tuples containing Option objects and their values.
52
+ :raises UnknownArgumentError: If an unknown option is provided.
53
+ :raises MissingRequirementException: If a required option argument is missing.
34
54
  """
35
- flag_list = []
55
+ logger.debug(f"Extracting options from arguments: {arguments}")
56
+ flag_list: List[Tuple[Option, str]] = []
36
57
 
37
58
  complementary_arg = False
38
59
  for i, arg in enumerate(arguments):
@@ -42,36 +63,59 @@ class CommandParser:
42
63
 
43
64
  flag = self._get_associated_option(arg)
44
65
  if not flag:
45
- error = UnknownArgumentError(f"'{arg}' is not a valid option.")
46
- error.arg = arg
47
- error.available_options = self.available_options
48
- raise error
66
+ logger.debug(f"Unknown option: {arg}")
67
+ raise UnknownArgumentError(
68
+ f"'{arg}' is not a valid option.",
69
+ arg=arg,
70
+ available_options=self.available_options
71
+ )
72
+
73
+ logger.debug(f"Option found: {flag.name} (identifiers: {flag.identifiers})")
49
74
 
50
75
  if flag.requires_args:
51
76
  complementary_arg = True
52
77
  try:
53
- flag_list.append((flag, arguments[i + 1]))
54
- except IndexError as e:
78
+ value = arguments[i + 1]
79
+ logger.debug(f"Option {flag.name} has value: {value}")
80
+ flag_list.append((flag, value))
81
+ except IndexError:
82
+ logger.debug(f"Missing required argument for option: {flag.name}")
55
83
  raise MissingRequirementException(f"'{flag.name}' requires an argument.")
56
84
  else:
57
- flag_list.append([flag, []])
85
+ logger.debug(f"Option {flag.name} is a flag (no value required)")
86
+ flag_list.append((flag, ""))
58
87
 
88
+ logger.debug(f"Total options parsed: {len(flag_list)}")
59
89
  return flag_list
60
90
 
61
- def _get_associated_command(self, arg: str) -> Command:
62
- associated_command = None
91
+ def _get_associated_command(self, arg: str) -> Optional[Command]:
92
+ """
93
+ Finds the command associated with the given argument.
94
+
95
+ :param arg: str - The argument to search for.
96
+ :return: Optional[Command] - The associated command if found, None otherwise.
97
+ """
98
+ if not self.available_commands:
99
+ return None
100
+
63
101
  for command in self.available_commands:
64
102
  if arg in command.identifiers:
65
- associated_command = command
66
- break
103
+ return command
104
+
105
+ return None
67
106
 
68
- return associated_command
107
+ def _get_associated_option(self, arg: str) -> Optional[Option]:
108
+ """
109
+ Finds the option associated with the given argument.
69
110
 
70
- def _get_associated_option(self, arg: str) -> Option:
71
- associated_option = None
111
+ :param arg: str - The argument to search for.
112
+ :return: Optional[Option] - The associated option if found, None otherwise.
113
+ """
114
+ if not self.available_options:
115
+ return None
116
+
72
117
  for option in self.available_options:
73
118
  if arg in option.identifiers:
74
- associated_option = option
75
- break
76
-
77
- return associated_option
119
+ return option
120
+
121
+ return None
pygeai/cli/texts/help.py CHANGED
@@ -340,12 +340,30 @@ DESCRIPTION
340
340
  EXAMPLES
341
341
  The command:
342
342
 
343
- geai migrate agent --agent-id "UUID" --target-project "TARGET_UUID"
344
- migrates an agent to a different project.
345
-
346
- The command:
347
- geai migrate process --process-id "UUID" --target-project "TARGET_UUID"
348
- migrates an agentic process to another project
343
+ geai migrate clone-project \\
344
+ --from-api-key "source_api_key_123abc456def789ghi012jkl345mno678pqr901stu234" \\
345
+ --from-organization-api-key "org_key_abc123def456ghi789jkl012mno345pqr678" \\
346
+ --from-project-id "1a2b3c4d-5e6f-7g8h-9i0j-1k2l3m4n5o6p" \\
347
+ --from-instance "https://api.example.ai" \\
348
+ --to-project-name "Migrated Project - Complete Clone" \\
349
+ --admin-email "admin@example.com" \\
350
+ --all
351
+
352
+ will clone entire project within the same instance
353
+
354
+ The command:
355
+
356
+ geai migrate clone-project \\
357
+ --from-api-key "source_api_key_123abc456def789ghi012jkl345mno678pqr901stu234" \\
358
+ --from-organization-api-key "source_org_key_abc123def456ghi789jkl012mno345pqr678" \\
359
+ --from-project-id "1a2b3c4d-5e6f-7g8h-9i0j-1k2l3m4n5o6p" \\
360
+ --from-instance "https://api.test.example.ai" \\
361
+ --to-organization-api-key "dest_org_key_stu901vwx234yz567abc890def123ghi456jkl789" \\
362
+ --to-project-name "Migrated Project - Complete Clone" \\
363
+ --admin-email "admin@example.com" \\
364
+ --all
365
+
366
+ will clone entire project with ALL resources (cross-instance)
349
367
 
350
368
  {AUTHORS_SECTION}
351
369
  """
@@ -782,3 +800,54 @@ EXAMPLES
782
800
 
783
801
  {AUTHORS_SECTION}
784
802
  """
803
+
804
+ ANALYTICS_HELP_TEXT = f"""
805
+ GEAI CLI - ANALYTICS
806
+ --------------------
807
+
808
+ NAME
809
+ geai - Command Line Interface for Globant Enterprise AI
810
+
811
+ SYNOPSIS
812
+ geai analytics <subcommand> --[flag] [flag_arg]
813
+
814
+ DESCRIPTION
815
+ geai analytics is a command from geai cli utility, developed to retrieve analytics and metrics from GEAI.
816
+
817
+ Date Range Defaults:
818
+ If --start-date and --end-date are not specified, the commands will default to the previous month
819
+ (from the first day to the last day of the previous month).
820
+
821
+ The options are as follows:
822
+ {{available_commands}}
823
+
824
+ EXAMPLES
825
+ The command:
826
+
827
+ geai analytics agents-created --start-date "2024-01-01" --end-date "2024-01-31"
828
+ retrieves the total number of agents created and modified in January 2024.
829
+
830
+ The command:
831
+
832
+ geai analytics full-report
833
+ retrieves a comprehensive analytics report for the previous month.
834
+
835
+ The command:
836
+
837
+ geai analytics full-report --start-date "2024-01-01" --end-date "2024-01-31" --csv report.csv
838
+ retrieves a comprehensive analytics report for January 2024 and exports it to report.csv.
839
+
840
+ The command:
841
+ geai analytics total-cost -s "2024-01-01" -e "2024-01-31"
842
+ retrieves the total cost for January 2024.
843
+
844
+ The command:
845
+ geai analytics requests-per-day -s "2024-01-01" -e "2024-01-31"
846
+ retrieves the total requests per day for January 2024.
847
+
848
+ The command:
849
+ geai analytics top-agents -s "2024-01-01" -e "2024-01-31"
850
+ retrieves the top 10 agents by number of requests
851
+
852
+ {AUTHORS_SECTION}
853
+ """
@@ -8,20 +8,29 @@ from pygeai.core.utils.validators import validate_status_code
8
8
 
9
9
  class BaseClient(ABC):
10
10
 
11
- def __init__(self, api_key: str = None, base_url: str = None, alias: str = None):
11
+ def __init__(self, api_key: str = None, base_url: str = None, alias: str = None, *,
12
+ access_token: str = None, project_id: str = None):
12
13
  """
13
14
  If commont settings are not specified, they're retrieved from default Session, based on the
14
15
  credential files.
15
16
  :param api_key: GEAI API KEY to access services
16
17
  :param base_url: URL for GEAI instance to be used
18
+ :param alias: Alias to use from credentials file
19
+ :param access_token: OAuth access token (keyword-only)
20
+ :param project_id: Project ID for OAuth authentication (keyword-only)
17
21
  """
18
- if not (api_key and base_url) and alias:
22
+ if access_token and not project_id:
23
+ raise MissingRequirementException("project_id is required when using access_token")
24
+
25
+ if not (api_key and base_url) and not (access_token and base_url) and alias:
19
26
  self.__session = get_session(alias)
20
27
  if not self.__session:
21
28
  raise MissingRequirementException("API KEY and BASE URL must be defined in order to use this functionality")
22
- elif api_key and base_url:
29
+ elif (api_key or access_token) and base_url:
23
30
  self.__session = get_session()
24
31
  self.__session.api_key = api_key
32
+ self.__session.access_token = access_token
33
+ self.__session.project_id = project_id
25
34
  self.__session.base_url = base_url
26
35
  else:
27
36
  self.__session = get_session()
@@ -29,7 +38,12 @@ class BaseClient(ABC):
29
38
  if self.session is None:
30
39
  raise MissingRequirementException("Cannot access this functionality without setting API_KEY and BASE_URL")
31
40
 
32
- self.__api_service = ApiService(base_url=self.session.base_url, token=self.session.api_key)
41
+ token = self.session.access_token if self.session.access_token else self.session.api_key
42
+ self.__api_service = ApiService(
43
+ base_url=self.session.base_url,
44
+ token=token,
45
+ project_id=self.session.project_id
46
+ )
33
47
 
34
48
  @property
35
49
  def session(self):
@@ -19,6 +19,8 @@ class Session(metaclass=Singleton):
19
19
  :param api_key: str - API key to interact with GEAI
20
20
  :param base_url: str - Base URL of the GEAI instance
21
21
  :param eval_url: Optional[str] - Optional evaluation endpoint URL
22
+ :param access_token: Optional[str] - OAuth access token (keyword-only)
23
+ :param project_id: Optional[str] - Project ID for OAuth authentication (keyword-only)
22
24
  :return: Session - Instance of the Session class
23
25
  :raises: ValueError - If required parameters are missing or invalid
24
26
  """
@@ -28,13 +30,22 @@ class Session(metaclass=Singleton):
28
30
  api_key: str = None,
29
31
  base_url: str = None,
30
32
  eval_url: Optional[str] = None,
33
+ *,
34
+ access_token: Optional[str] = None,
35
+ project_id: Optional[str] = None,
36
+ alias: Optional[str] = None,
31
37
  ):
32
- if not api_key or not base_url:
33
- logger.warning("Cannot instantiate session without api_key and base_url")
38
+ if not api_key and not access_token:
39
+ logger.warning("Cannot instantiate session without api_key or access_token")
40
+ if not base_url:
41
+ logger.warning("Cannot instantiate session without base_url")
34
42
 
35
43
  self.__api_key = api_key
36
44
  self.__base_url = base_url
37
45
  self.__eval_url = eval_url
46
+ self.__access_token = access_token
47
+ self.__project_id = project_id
48
+ self.__alias = alias if alias else "default"
38
49
 
39
50
  global _session
40
51
  _session = self
@@ -63,6 +74,30 @@ class Session(metaclass=Singleton):
63
74
  def eval_url(self, eval_url: str):
64
75
  self.__eval_url = eval_url
65
76
 
77
+ @property
78
+ def access_token(self):
79
+ return self.__access_token
80
+
81
+ @access_token.setter
82
+ def access_token(self, access_token: str):
83
+ self.__access_token = access_token
84
+
85
+ @property
86
+ def project_id(self):
87
+ return self.__project_id
88
+
89
+ @project_id.setter
90
+ def project_id(self, project_id: str):
91
+ self.__project_id = project_id
92
+
93
+ @property
94
+ def alias(self):
95
+ return self.__alias
96
+
97
+ @alias.setter
98
+ def alias(self, alias: str):
99
+ self.__alias = alias
100
+
66
101
 
67
102
  def get_session(alias: str = None) -> Session:
68
103
  """
@@ -80,13 +115,17 @@ def get_session(alias: str = None) -> Session:
80
115
  api_key=settings.get_api_key(alias),
81
116
  base_url=settings.get_base_url(alias),
82
117
  eval_url=settings.get_eval_url(alias),
118
+ access_token=settings.get_access_token(alias),
119
+ project_id=settings.get_project_id(alias),
120
+ alias=alias,
83
121
  )
84
122
  elif _session is not None and alias:
85
- new_api_key = settings.get_api_key(alias)
86
- if new_api_key != _session.api_key:
87
- _session.api_key = new_api_key
88
- _session.base_url = settings.get_base_url(alias)
89
- _session.eval_url = settings.get_eval_url(alias)
123
+ _session.alias = alias
124
+ _session.api_key = settings.get_api_key(alias)
125
+ _session.base_url = settings.get_base_url(alias)
126
+ _session.eval_url = settings.get_eval_url(alias)
127
+ _session.access_token = settings.get_access_token(alias)
128
+ _session.project_id = settings.get_project_id(alias)
90
129
 
91
130
  if alias:
92
131
  logger.debug(f"Alias: {alias}")