pygeai 0.6.0b7__py3-none-any.whl → 0.6.0b10__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 (178) hide show
  1. pygeai/_docs/source/conf.py +78 -6
  2. pygeai/_docs/source/content/api_reference/embeddings.rst +31 -1
  3. pygeai/_docs/source/content/api_reference/evaluation.rst +590 -0
  4. pygeai/_docs/source/content/api_reference/feedback.rst +237 -0
  5. pygeai/_docs/source/content/api_reference/files.rst +592 -0
  6. pygeai/_docs/source/content/api_reference/gam.rst +401 -0
  7. pygeai/_docs/source/content/api_reference/proxy.rst +318 -0
  8. pygeai/_docs/source/content/api_reference/secrets.rst +495 -0
  9. pygeai/_docs/source/content/api_reference/usage_limits.rst +390 -0
  10. pygeai/_docs/source/content/api_reference.rst +7 -0
  11. pygeai/_docs/source/content/debugger.rst +376 -83
  12. pygeai/_docs/source/content/migration.rst +528 -0
  13. pygeai/_docs/source/content/modules.rst +1 -1
  14. pygeai/_docs/source/pygeai.cli.rst +8 -0
  15. pygeai/_docs/source/pygeai.tests.cli.rst +16 -0
  16. pygeai/_docs/source/pygeai.tests.core.embeddings.rst +16 -0
  17. pygeai/_docs/source/pygeai.tests.snippets.chat.rst +40 -0
  18. pygeai/_docs/source/pygeai.tests.snippets.dbg.rst +45 -0
  19. pygeai/_docs/source/pygeai.tests.snippets.embeddings.rst +40 -0
  20. pygeai/_docs/source/pygeai.tests.snippets.evaluation.dataset.rst +197 -0
  21. pygeai/_docs/source/pygeai.tests.snippets.evaluation.plan.rst +133 -0
  22. pygeai/_docs/source/pygeai.tests.snippets.evaluation.result.rst +37 -0
  23. pygeai/_docs/source/pygeai.tests.snippets.evaluation.rst +10 -0
  24. pygeai/_docs/source/pygeai.tests.snippets.rst +1 -0
  25. pygeai/admin/clients.py +5 -0
  26. pygeai/assistant/clients.py +7 -0
  27. pygeai/assistant/data_analyst/clients.py +2 -0
  28. pygeai/assistant/rag/clients.py +11 -0
  29. pygeai/chat/clients.py +191 -25
  30. pygeai/chat/endpoints.py +2 -1
  31. pygeai/cli/commands/chat.py +227 -1
  32. pygeai/cli/commands/embeddings.py +56 -8
  33. pygeai/cli/commands/migrate.py +994 -434
  34. pygeai/cli/error_handler.py +116 -0
  35. pygeai/cli/geai.py +28 -10
  36. pygeai/cli/parsers.py +8 -2
  37. pygeai/core/base/clients.py +3 -1
  38. pygeai/core/common/exceptions.py +11 -10
  39. pygeai/core/embeddings/__init__.py +19 -0
  40. pygeai/core/embeddings/clients.py +17 -2
  41. pygeai/core/embeddings/mappers.py +16 -2
  42. pygeai/core/embeddings/responses.py +9 -2
  43. pygeai/core/feedback/clients.py +1 -0
  44. pygeai/core/files/clients.py +5 -7
  45. pygeai/core/files/managers.py +42 -0
  46. pygeai/core/llm/clients.py +4 -0
  47. pygeai/core/plugins/clients.py +1 -0
  48. pygeai/core/rerank/clients.py +1 -0
  49. pygeai/core/secrets/clients.py +6 -0
  50. pygeai/core/services/rest.py +1 -1
  51. pygeai/dbg/__init__.py +3 -0
  52. pygeai/dbg/debugger.py +565 -70
  53. pygeai/evaluation/clients.py +1 -1
  54. pygeai/evaluation/dataset/clients.py +45 -44
  55. pygeai/evaluation/plan/clients.py +27 -26
  56. pygeai/evaluation/result/clients.py +37 -5
  57. pygeai/gam/clients.py +4 -0
  58. pygeai/health/clients.py +1 -0
  59. pygeai/lab/agents/clients.py +8 -1
  60. pygeai/lab/models.py +3 -3
  61. pygeai/lab/processes/clients.py +21 -0
  62. pygeai/lab/strategies/clients.py +4 -0
  63. pygeai/lab/tools/clients.py +1 -0
  64. pygeai/migration/__init__.py +31 -0
  65. pygeai/migration/strategies.py +404 -155
  66. pygeai/migration/tools.py +170 -3
  67. pygeai/organization/clients.py +13 -0
  68. pygeai/organization/limits/clients.py +15 -0
  69. pygeai/proxy/clients.py +3 -1
  70. pygeai/tests/admin/test_clients.py +16 -11
  71. pygeai/tests/assistants/rag/test_clients.py +35 -23
  72. pygeai/tests/assistants/test_clients.py +22 -15
  73. pygeai/tests/auth/test_clients.py +14 -6
  74. pygeai/tests/chat/test_clients.py +211 -1
  75. pygeai/tests/cli/commands/test_embeddings.py +32 -9
  76. pygeai/tests/cli/commands/test_evaluation.py +7 -0
  77. pygeai/tests/cli/commands/test_migrate.py +112 -243
  78. pygeai/tests/cli/test_error_handler.py +225 -0
  79. pygeai/tests/cli/test_geai_driver.py +154 -0
  80. pygeai/tests/cli/test_parsers.py +5 -5
  81. pygeai/tests/core/embeddings/test_clients.py +144 -0
  82. pygeai/tests/core/embeddings/test_managers.py +171 -0
  83. pygeai/tests/core/embeddings/test_mappers.py +142 -0
  84. pygeai/tests/core/feedback/test_clients.py +2 -0
  85. pygeai/tests/core/files/test_clients.py +1 -0
  86. pygeai/tests/core/llm/test_clients.py +14 -9
  87. pygeai/tests/core/plugins/test_clients.py +5 -3
  88. pygeai/tests/core/rerank/test_clients.py +1 -0
  89. pygeai/tests/core/secrets/test_clients.py +19 -13
  90. pygeai/tests/dbg/test_debugger.py +453 -75
  91. pygeai/tests/evaluation/dataset/test_clients.py +3 -1
  92. pygeai/tests/evaluation/plan/test_clients.py +4 -2
  93. pygeai/tests/evaluation/result/test_clients.py +7 -5
  94. pygeai/tests/gam/test_clients.py +1 -1
  95. pygeai/tests/health/test_clients.py +1 -0
  96. pygeai/tests/lab/agents/test_clients.py +9 -0
  97. pygeai/tests/lab/processes/test_clients.py +36 -0
  98. pygeai/tests/lab/processes/test_mappers.py +3 -0
  99. pygeai/tests/lab/strategies/test_clients.py +14 -9
  100. pygeai/tests/migration/test_strategies.py +45 -218
  101. pygeai/tests/migration/test_tools.py +133 -9
  102. pygeai/tests/organization/limits/test_clients.py +17 -0
  103. pygeai/tests/organization/test_clients.py +22 -0
  104. pygeai/tests/proxy/test_clients.py +2 -0
  105. pygeai/tests/proxy/test_integration.py +1 -0
  106. pygeai/tests/snippets/chat/chat_completion_with_reasoning_effort.py +18 -0
  107. pygeai/tests/snippets/chat/get_response.py +15 -0
  108. pygeai/tests/snippets/chat/get_response_streaming.py +20 -0
  109. pygeai/tests/snippets/chat/get_response_with_files.py +16 -0
  110. pygeai/tests/snippets/chat/get_response_with_tools.py +36 -0
  111. pygeai/tests/snippets/dbg/__init__.py +0 -0
  112. pygeai/tests/snippets/dbg/basic_debugging.py +32 -0
  113. pygeai/tests/snippets/dbg/breakpoint_management.py +48 -0
  114. pygeai/tests/snippets/dbg/stack_navigation.py +45 -0
  115. pygeai/tests/snippets/dbg/stepping_example.py +40 -0
  116. pygeai/tests/snippets/embeddings/cache_example.py +31 -0
  117. pygeai/tests/snippets/embeddings/cohere_example.py +41 -0
  118. pygeai/tests/snippets/embeddings/openai_base64_example.py +27 -0
  119. pygeai/tests/snippets/embeddings/openai_example.py +30 -0
  120. pygeai/tests/snippets/embeddings/similarity_example.py +42 -0
  121. pygeai/tests/snippets/evaluation/dataset/__init__.py +0 -0
  122. pygeai/tests/snippets/evaluation/dataset/complete_workflow_example.py +195 -0
  123. pygeai/tests/snippets/evaluation/dataset/create_dataset.py +26 -0
  124. pygeai/tests/snippets/evaluation/dataset/create_dataset_from_file.py +11 -0
  125. pygeai/tests/snippets/evaluation/dataset/create_dataset_row.py +17 -0
  126. pygeai/tests/snippets/evaluation/dataset/create_expected_source.py +18 -0
  127. pygeai/tests/snippets/evaluation/dataset/create_filter_variable.py +19 -0
  128. pygeai/tests/snippets/evaluation/dataset/delete_dataset.py +9 -0
  129. pygeai/tests/snippets/evaluation/dataset/delete_dataset_row.py +10 -0
  130. pygeai/tests/snippets/evaluation/dataset/delete_expected_source.py +15 -0
  131. pygeai/tests/snippets/evaluation/dataset/delete_filter_variable.py +15 -0
  132. pygeai/tests/snippets/evaluation/dataset/get_dataset.py +9 -0
  133. pygeai/tests/snippets/evaluation/dataset/get_dataset_row.py +10 -0
  134. pygeai/tests/snippets/evaluation/dataset/get_expected_source.py +15 -0
  135. pygeai/tests/snippets/evaluation/dataset/get_filter_variable.py +15 -0
  136. pygeai/tests/snippets/evaluation/dataset/list_dataset_rows.py +9 -0
  137. pygeai/tests/snippets/evaluation/dataset/list_datasets.py +6 -0
  138. pygeai/tests/snippets/evaluation/dataset/list_expected_sources.py +10 -0
  139. pygeai/tests/snippets/evaluation/dataset/list_filter_variables.py +10 -0
  140. pygeai/tests/snippets/evaluation/dataset/update_dataset.py +15 -0
  141. pygeai/tests/snippets/evaluation/dataset/update_dataset_row.py +20 -0
  142. pygeai/tests/snippets/evaluation/dataset/update_expected_source.py +18 -0
  143. pygeai/tests/snippets/evaluation/dataset/update_filter_variable.py +19 -0
  144. pygeai/tests/snippets/evaluation/dataset/upload_dataset_rows_file.py +10 -0
  145. pygeai/tests/snippets/evaluation/plan/__init__.py +0 -0
  146. pygeai/tests/snippets/evaluation/plan/add_plan_system_metric.py +13 -0
  147. pygeai/tests/snippets/evaluation/plan/complete_workflow_example.py +136 -0
  148. pygeai/tests/snippets/evaluation/plan/create_evaluation_plan.py +24 -0
  149. pygeai/tests/snippets/evaluation/plan/create_rag_evaluation_plan.py +22 -0
  150. pygeai/tests/snippets/evaluation/plan/delete_evaluation_plan.py +9 -0
  151. pygeai/tests/snippets/evaluation/plan/delete_plan_system_metric.py +13 -0
  152. pygeai/tests/snippets/evaluation/plan/execute_evaluation_plan.py +11 -0
  153. pygeai/tests/snippets/evaluation/plan/get_evaluation_plan.py +9 -0
  154. pygeai/tests/snippets/evaluation/plan/get_plan_system_metric.py +13 -0
  155. pygeai/tests/snippets/evaluation/plan/get_system_metric.py +9 -0
  156. pygeai/tests/snippets/evaluation/plan/list_evaluation_plans.py +7 -0
  157. pygeai/tests/snippets/evaluation/plan/list_plan_system_metrics.py +9 -0
  158. pygeai/tests/snippets/evaluation/plan/list_system_metrics.py +7 -0
  159. pygeai/tests/snippets/evaluation/plan/update_evaluation_plan.py +22 -0
  160. pygeai/tests/snippets/evaluation/plan/update_plan_system_metric.py +14 -0
  161. pygeai/tests/snippets/evaluation/result/__init__.py +0 -0
  162. pygeai/tests/snippets/evaluation/result/complete_workflow_example.py +150 -0
  163. pygeai/tests/snippets/evaluation/result/get_evaluation_result.py +26 -0
  164. pygeai/tests/snippets/evaluation/result/list_evaluation_results.py +17 -0
  165. pygeai/tests/snippets/migrate/__init__.py +45 -0
  166. pygeai/tests/snippets/migrate/agent_migration.py +110 -0
  167. pygeai/tests/snippets/migrate/assistant_migration.py +64 -0
  168. pygeai/tests/snippets/migrate/orchestrator_examples.py +179 -0
  169. pygeai/tests/snippets/migrate/process_migration.py +64 -0
  170. pygeai/tests/snippets/migrate/project_migration.py +42 -0
  171. pygeai/tests/snippets/migrate/tool_migration.py +64 -0
  172. pygeai/tests/snippets/organization/create_project.py +2 -2
  173. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b10.dist-info}/METADATA +1 -1
  174. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b10.dist-info}/RECORD +178 -96
  175. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b10.dist-info}/WHEEL +0 -0
  176. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b10.dist-info}/entry_points.txt +0 -0
  177. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b10.dist-info}/licenses/LICENSE +0 -0
  178. {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b10.dist-info}/top_level.txt +0 -0
@@ -1,515 +1,1112 @@
1
+ from typing import Tuple, Dict, Any, Optional, List
2
+ from pygeai import logger
1
3
  from pygeai.cli.commands import Command, Option, ArgumentsEnum
2
4
  from pygeai.cli.commands.builders import build_help_text
3
5
  from pygeai.cli.texts.help import MIGRATE_HELP_TEXT
4
6
  from pygeai.core.common.exceptions import MissingRequirementException
5
7
  from pygeai.core.utils.console import Console
6
- from pygeai.migration.strategies import ProjectMigrationStrategy, AgentMigrationStrategy, ToolMigrationStrategy, \
7
- AgenticProcessMigrationStrategy, TaskMigrationStrategy
8
- from pygeai.migration.tools import MigrationTool
8
+ from pygeai.lab.managers import AILabManager
9
+ from pygeai.lab.models import FilterSettings
10
+ from pygeai.assistant.managers import AssistantManager
11
+ from pygeai.assistant.rag.clients import RAGAssistantClient
12
+ from pygeai.assistant.rag.mappers import RAGAssistantMapper
13
+ from pygeai.core.files.managers import FileManager
14
+ from pygeai.migration.strategies import (
15
+ ProjectMigrationStrategy,
16
+ AgentMigrationStrategy,
17
+ ToolMigrationStrategy,
18
+ AgenticProcessMigrationStrategy,
19
+ TaskMigrationStrategy,
20
+ UsageLimitMigrationStrategy,
21
+ RAGAssistantMigrationStrategy,
22
+ FileMigrationStrategy
23
+ )
24
+ from pygeai.migration.tools import MigrationTool, MigrationPlan, MigrationOrchestrator
25
+ from pygeai.admin.clients import AdminClient
9
26
 
10
27
 
11
- def show_help():
28
+ def show_help() -> None:
12
29
  """
13
- Displays help text in stdout
30
+ Displays help text in stdout.
14
31
  """
15
32
  help_text = build_help_text(migrate_commands, MIGRATE_HELP_TEXT)
16
33
  Console.write_stdout(help_text)
17
34
 
18
35
 
19
- def migrate_base(option_list: list):
20
- from_api_key = None
21
- from_instance = None
22
- to_api_key = None
23
- to_instance = None
36
+ def prompt_with_retry(
37
+ prompt_message: str,
38
+ valid_choices: Optional[list] = None,
39
+ allow_empty: bool = False
40
+ ) -> str:
41
+ """
42
+ Prompt user for input with validation and retry logic.
43
+
44
+ :param prompt_message: Message to display when prompting
45
+ :param valid_choices: Optional list of valid input choices
46
+ :param allow_empty: Whether to allow empty input
47
+ :return: User's validated input string
48
+ """
49
+ while True:
50
+ user_input = input(prompt_message).strip()
51
+
52
+ if not user_input and not allow_empty:
53
+ Console.write_stdout("Error: Input cannot be empty. Please try again.")
54
+ continue
55
+
56
+ if valid_choices and user_input not in valid_choices:
57
+ Console.write_stdout(f"Error: Invalid choice '{user_input}'. Valid options: {', '.join(valid_choices)}")
58
+ continue
59
+
60
+ return user_input
24
61
 
25
- for option_flag, option_arg in option_list:
26
- if option_flag.name == "from_api_key":
27
- from_api_key = option_arg
28
- if option_flag.name == "from_instance":
29
- from_instance = option_arg
30
- if option_flag.name == "to_api_key":
31
- to_api_key = option_arg
32
- if option_flag.name == "to_instance":
33
- to_instance = option_arg
34
62
 
35
- if not (from_api_key and from_instance):
36
- raise MissingRequirementException("Cannot migrate resources without indicating source: API key and instance")
63
+ def prompt_resource_selection(
64
+ resource_type: str,
65
+ items: list,
66
+ id_field: str = "id",
67
+ name_field: str = "name"
68
+ ) -> Optional[str]:
69
+ """
70
+ Display a list of resources and let the user select which ones to migrate.
71
+
72
+ :param resource_type: Type of resource being selected (for display purposes)
73
+ :param items: List of resource items to display
74
+ :param id_field: Name of the attribute containing the resource ID
75
+ :param name_field: Name of the attribute containing the resource name
76
+ :return: Comma-separated string of selected IDs, 'all' for all resources, or None to cancel
77
+ """
78
+ if not items:
79
+ Console.write_stdout(f"No {resource_type} found.")
80
+ return None
81
+
82
+ Console.write_stdout(f"\nAvailable {resource_type}:")
83
+ Console.write_stdout(" 0. Cancel (don't migrate this resource type)")
84
+
85
+ for idx, item in enumerate(items, 1):
86
+ item_id = getattr(item, id_field, None)
87
+ item_name = getattr(item, name_field, None) if hasattr(item, name_field) else None
88
+ if item_name:
89
+ Console.write_stdout(f" {idx}. {item_name} (ID: {item_id})")
90
+ else:
91
+ Console.write_stdout(f" {idx}. {item_id}")
92
+
93
+ while True:
94
+ selection = input(f"\nSelect {resource_type} (comma-separated numbers, or empty for all): ").strip()
95
+
96
+ if not selection:
97
+ return "all"
98
+
99
+ if selection == "0":
100
+ return None
101
+
102
+ try:
103
+ indices = [int(x.strip()) for x in selection.split(",")]
104
+ if any(i < 0 or i > len(items) for i in indices):
105
+ Console.write_stdout(f"Error: Invalid selection. Numbers must be between 0 and {len(items)}.")
106
+ continue
107
+
108
+ if 0 in indices:
109
+ return None
110
+
111
+ selected_ids = [getattr(items[i-1], id_field) for i in indices]
112
+ return ",".join(str(sid) for sid in selected_ids)
113
+ except (ValueError, IndexError) as e:
114
+ Console.write_stdout(f"Error: Invalid input format. Please enter comma-separated numbers.")
115
+ continue
37
116
 
38
- return from_api_key, from_instance, to_api_key, to_instance
39
117
 
118
+ def get_source_configuration() -> Tuple[str, str, str, Optional[str]]:
119
+ """
120
+ Prompt user for source configuration and retrieve organization ID.
121
+
122
+ :return: Tuple of (api_key, instance_url, project_id, organization_id)
123
+ """
124
+ Console.write_stdout("\n--- Source Configuration ---")
125
+ from_api_key = prompt_with_retry("Source API key: ")
126
+ from_instance = prompt_with_retry("Source instance URL: ")
127
+ from_project_id = prompt_with_retry("Source project ID: ")
128
+
129
+ admin_client = AdminClient(api_key=from_api_key, base_url=from_instance)
130
+ source_token_info = admin_client.validate_api_token()
131
+ from_organization_id = source_token_info.get("organizationId")
132
+
133
+ return from_api_key, from_instance, from_project_id, from_organization_id
40
134
 
41
- migrate_base_options = [
42
- Option(
43
- "from_api_key",
44
- ["--from-api-key", "--fak"],
45
- "API key for the source instance",
46
- True
47
- ),
48
- Option(
49
- "from_instance",
50
- ["--from-instance", "--fi"],
51
- "Source instance to migrate from",
52
- True
53
- ),
54
- Option(
55
- "to_api_key",
56
- ["--to-api-key", "--tak"],
57
- "API key for the destination instance",
58
- True
59
- ),
60
- Option(
61
- "to_instance",
62
- ["--to-instance", "--ti"],
63
- "Destination instance to migrate to",
64
- True
65
- ),
66
- ]
135
+
136
+ def get_destination_configuration(
137
+ same_instance: bool,
138
+ from_instance: str,
139
+ from_api_key: str,
140
+ from_organization_id: Optional[str],
141
+ creating_project: bool
142
+ ) -> Tuple[str, Optional[str], Optional[str]]:
143
+ """
144
+ Configure destination instance settings based on migration type.
145
+
146
+ :param same_instance: Whether migration is within the same instance
147
+ :param from_instance: Source instance URL
148
+ :param from_api_key: Source API key
149
+ :param from_organization_id: Source organization ID
150
+ :param creating_project: Whether a new project will be created
151
+ :return: Tuple of (instance_url, api_key, organization_id)
152
+ """
153
+ if same_instance:
154
+ to_instance = from_instance
155
+ to_organization_id = from_organization_id
156
+ Console.write_stdout(f"Destination instance: {to_instance} (same as source)")
157
+ Console.write_stdout(f"Destination organization ID: {to_organization_id} (same as source)")
158
+
159
+ if creating_project:
160
+ to_api_key = None
161
+ Console.write_stdout("Destination API key: (will be created after project creation)")
162
+ else:
163
+ to_api_key = prompt_with_retry("Destination API key: ")
164
+ else:
165
+ Console.write_stdout("\n--- Destination Configuration ---")
166
+ to_instance = prompt_with_retry("Destination instance URL: ")
167
+
168
+ if creating_project:
169
+ to_api_key = None
170
+ to_organization_id = None
171
+ Console.write_stdout("Destination API key: (will be created after project creation)")
172
+ Console.write_stdout("Destination organization ID: (will be retrieved after project creation)")
173
+ else:
174
+ to_api_key = prompt_with_retry("Destination API key: ")
175
+ dest_admin_client = AdminClient(api_key=to_api_key, base_url=to_instance)
176
+ dest_token_info = dest_admin_client.validate_api_token()
177
+ to_organization_id = dest_token_info.get("organizationId")
178
+
179
+ return to_instance, to_api_key, to_organization_id
67
180
 
68
181
 
69
- def clone_project(option_list: list):
70
- from_api_key = None
71
- from_project_id = None
72
- from_instance = None
73
- to_api_key = None
182
+ def get_project_creation_info(same_instance: bool) -> Tuple[Optional[str], Optional[str], Optional[str], Optional[str], Optional[str]]:
183
+ """
184
+ Prompt user for project creation information.
185
+
186
+ :param same_instance: Whether migration is within the same instance
187
+ :return: Tuple of (from_org_api_key, to_org_api_key, project_name, admin_email, project_id)
188
+ """
189
+ create_project = prompt_with_retry(
190
+ "Create new destination project? (y/n): ",
191
+ valid_choices=["y", "n"]
192
+ )
193
+
194
+ from_organization_api_key = None
195
+ to_organization_api_key = None
74
196
  to_project_name = None
75
- to_instance = None
76
197
  admin_email = None
198
+ to_project_id = None
199
+
200
+ if create_project == "y":
201
+ from_organization_api_key = prompt_with_retry("Source organization API key: ")
202
+ if same_instance:
203
+ to_organization_api_key = from_organization_api_key
204
+ Console.write_stdout("Destination organization API key: (same as source)")
205
+ else:
206
+ to_organization_api_key = prompt_with_retry("Destination organization API key: ")
207
+ to_project_name = prompt_with_retry("New project name: ")
208
+ admin_email = prompt_with_retry("Admin email: ")
209
+ else:
210
+ to_project_id = prompt_with_retry("Destination project ID: ")
211
+
212
+ return from_organization_api_key, to_organization_api_key, to_project_name, admin_email, to_project_id
77
213
 
78
- for option_flag, option_arg in option_list:
79
- if option_flag.name == "from_api_key":
80
- from_api_key = option_arg
81
- if option_flag.name == "from_project_id":
82
- from_project_id = option_arg
83
- if option_flag.name == "from_instance":
84
- from_instance = option_arg
85
- if option_flag.name == "to_api_key":
86
- to_api_key = option_arg
87
- if option_flag.name == "to_project_name":
88
- to_project_name = option_arg
89
- if option_flag.name == "to_instance":
90
- to_instance = option_arg
91
- if option_flag.name == "admin_email":
92
- admin_email = option_arg
93
214
 
94
- if not (from_project_id and from_instance):
95
- raise MissingRequirementException("Cannot migrate resources without indicating source: project and instance")
215
+ def select_resource_types() -> List[int]:
216
+ """
217
+ Prompt user to select which resource types to migrate.
218
+
219
+ :return: List of integers representing selected resource types (1-7)
220
+ """
221
+ Console.write_stdout("\n--- Resource Type Selection ---")
222
+ Console.write_stdout("Which resource types do you want to migrate?")
223
+ Console.write_stdout(" 1. Agents")
224
+ Console.write_stdout(" 2. Tools")
225
+ Console.write_stdout(" 3. Agentic Processes")
226
+ Console.write_stdout(" 4. Tasks")
227
+ Console.write_stdout(" 5. RAG Assistants")
228
+ Console.write_stdout(" 6. Files")
229
+ Console.write_stdout(" 7. Usage Limits")
230
+
231
+ while True:
232
+ resource_choice = input("\nSelect resource types (comma-separated numbers, or empty for all): ").strip()
233
+ if not resource_choice:
234
+ return [1, 2, 3, 4, 5, 6, 7]
235
+ try:
236
+ resource_types = [int(x.strip()) for x in resource_choice.split(",")]
237
+ if any(i < 1 or i > 7 for i in resource_types):
238
+ Console.write_stdout("Error: Invalid selection. Numbers must be between 1 and 7.")
239
+ continue
240
+ return resource_types
241
+ except ValueError:
242
+ Console.write_stdout("Error: Invalid input format. Please enter comma-separated numbers.")
243
+ continue
96
244
 
97
- if not admin_email:
98
- raise MissingRequirementException("Admin email for new project must be defined.")
99
245
 
100
- migration_strategy = ProjectMigrationStrategy(
101
- from_api_key=from_api_key,
102
- from_project_id=from_project_id,
103
- from_instance=from_instance,
104
- to_api_key=to_api_key,
105
- to_project_name=to_project_name,
106
- to_instance=to_instance,
107
- admin_email=admin_email
108
- )
109
- tool = MigrationTool(migration_strategy)
110
- response = tool.run_migration()
246
+ def fetch_and_select_agents(lab_manager: AILabManager) -> Optional[str]:
247
+ """
248
+ Fetch and prompt user to select agents for migration.
249
+
250
+ :param lab_manager: AI Lab manager instance
251
+ :return: Comma-separated IDs, 'all', or None
252
+ """
253
+ try:
254
+ agent_list = lab_manager.get_agent_list(FilterSettings(count=1000))
255
+ agents = [a for a in agent_list.agents if a.id]
256
+ if agents:
257
+ selection = prompt_resource_selection("agents", agents, id_field="id", name_field="name")
258
+ return selection
259
+ except Exception as e:
260
+ Console.write_stdout(f"Warning: Could not retrieve agents: {e}")
261
+ return None
111
262
 
112
- Console.write_stdout(f"Migration result: \n{response}")
113
263
 
264
+ def fetch_and_select_tools(lab_manager: AILabManager) -> Optional[str]:
265
+ """
266
+ Fetch and prompt user to select tools for migration.
267
+
268
+ :param lab_manager: AI Lab manager instance
269
+ :return: Comma-separated IDs, 'all', or None
270
+ """
271
+ try:
272
+ tool_list = lab_manager.list_tools(FilterSettings(count=1000))
273
+ tools = [t for t in tool_list.tools if t.id]
274
+ if tools:
275
+ selection = prompt_resource_selection("tools", tools, id_field="id", name_field="name")
276
+ return selection
277
+ except Exception as e:
278
+ Console.write_stdout(f"Warning: Could not retrieve tools: {e}")
279
+ return None
114
280
 
115
- clone_project_options = [
116
- Option(
117
- "from_api_key",
118
- ["--from-api-key", "--fak"],
119
- "API key for the source instance",
120
- True
121
- ),
122
- Option(
123
- "from_project_id",
124
- ["--from-project-id", "--fpid"],
125
- "ID of the source project to migrate from",
126
- True
127
- ),
128
- Option(
129
- "from_instance",
130
- ["--from-instance", "--fi"],
131
- "URL from the source instance to migrate from",
132
- True
133
- ),
134
- Option(
135
- "to_api_key",
136
- ["--to-api-key", "--tak"],
137
- "API key for the destination instance. If not specified, the same instance's API key will be used",
138
- True
139
- ),
140
- Option(
141
- "to_project_name",
142
- ["--to-project-name", "--tpn"],
143
- "Name of the destination project to migrate to",
144
- True
145
- ),
146
- Option(
147
- "to_instance",
148
- ["--to-instance", "--ti"],
149
- "URL from the destination instance to migrate to. If not specified, the same instance's URL will be used",
150
- True
151
- ),
152
- Option(
153
- "admin_email",
154
- ["--admin-email", "--ae"],
155
- "Email from destination project's administrator",
156
- True
157
- ),
158
- ]
159
281
 
282
+ def fetch_and_select_processes(lab_manager: AILabManager) -> Optional[str]:
283
+ """
284
+ Fetch and prompt user to select processes for migration.
285
+
286
+ :param lab_manager: AI Lab manager instance
287
+ :return: Comma-separated IDs, 'all', or None
288
+ """
289
+ try:
290
+ process_list = lab_manager.list_processes(FilterSettings(count=1000))
291
+ processes = [p for p in process_list.processes if p.id]
292
+ if processes:
293
+ selection = prompt_resource_selection("agentic processes", processes, id_field="id", name_field="name")
294
+ return selection
295
+ except Exception as e:
296
+ Console.write_stdout(f"Warning: Could not retrieve agentic processes: {e}")
297
+ return None
160
298
 
161
- def clone_agent(option_list: list):
162
- from_api_key = None
163
- from_project_id = None
164
- from_instance = None
165
- to_api_key = None
166
- to_project_id = None
167
- to_instance = None
168
- agent_id = None
169
299
 
170
- for option_flag, option_arg in option_list:
171
- if option_flag.name == "from_api_key":
172
- from_api_key = option_arg
173
- if option_flag.name == "from_project_id":
174
- from_project_id = option_arg
175
- if option_flag.name == "from_instance":
176
- from_instance = option_arg
177
- if option_flag.name == "to_api_key":
178
- to_api_key = option_arg
179
- if option_flag.name == "to_project_id":
180
- to_project_id = option_arg
181
- if option_flag.name == "to_instance":
182
- to_instance = option_arg
183
- if option_flag.name == "agent_id":
184
- agent_id = option_arg
185
-
186
- if not (from_project_id and from_instance and agent_id):
187
- raise MissingRequirementException("Cannot migrate resources without indicating source: project, instance and agent id")
188
-
189
- migration_strategy = AgentMigrationStrategy(
190
- from_api_key=from_api_key,
191
- from_project_id=from_project_id,
192
- from_instance=from_instance,
193
- to_api_key=to_api_key,
194
- to_project_id=to_project_id,
195
- to_instance=to_instance,
196
- agent_id=agent_id
197
- )
198
- tool = MigrationTool(migration_strategy)
199
- response = tool.run_migration()
300
+ def fetch_and_select_tasks(lab_manager: AILabManager) -> Optional[str]:
301
+ """
302
+ Fetch and prompt user to select tasks for migration.
303
+
304
+ :param lab_manager: AI Lab manager instance
305
+ :return: Comma-separated IDs, 'all', or None
306
+ """
307
+ try:
308
+ task_list = lab_manager.list_tasks(FilterSettings(count=1000))
309
+ tasks = [t for t in task_list.tasks if t.id]
310
+ if tasks:
311
+ selection = prompt_resource_selection("tasks", tasks, id_field="id", name_field="name")
312
+ return selection
313
+ except Exception as e:
314
+ Console.write_stdout(f"Warning: Could not retrieve tasks: {e}")
315
+ return None
200
316
 
201
317
 
202
- clone_agent_options = [
203
- Option(
204
- "from_api_key",
205
- ["--from-api-key", "--fak"],
206
- "API key for the source instance",
207
- True
208
- ),
209
- Option(
210
- "from_project_id",
211
- ["--from-project-id", "--fpid"],
212
- "ID of the source project to migrate from",
213
- True
214
- ),
215
- Option(
216
- "from_instance",
217
- ["--from-instance", "--fi"],
218
- "URL from the source instance to migrate from",
219
- True
220
- ),
221
- Option(
222
- "to_api_key",
223
- ["--to-api-key", "--tak"],
224
- "API key for the destination instance. If not specified, the same instance's API key will be used",
225
- True
226
- ),
227
- Option(
228
- "to_project_id",
229
- ["--to-project-id", "--tpid"],
230
- "ID of the destination project to migrate to",
231
- True
232
- ),
233
- Option(
234
- "to_instance",
235
- ["--to-instance", "--ti"],
236
- "URL from the destination instance to migrate to. If not specified, the same instance's URL will be used",
237
- True
238
- ),
239
- Option(
240
- "agent_id",
241
- ["--agent-id", "--aid"],
242
- "Unique identifier from the agent to be migrated",
243
- True
244
- ),
318
+ def fetch_and_select_rag_assistants(from_api_key: str, from_instance: str) -> Optional[str]:
319
+ """
320
+ Fetch and prompt user to select RAG assistants for migration.
321
+
322
+ :param from_api_key: Source API key
323
+ :param from_instance: Source instance URL
324
+ :return: Comma-separated names, 'all', or None
325
+ """
326
+ try:
327
+ rag_client = RAGAssistantClient(api_key=from_api_key, base_url=from_instance)
328
+ assistant_data = rag_client.get_assistants_from_project()
329
+ assistants_raw = assistant_data.get("assistants", [])
330
+ assistant_list = [RAGAssistantMapper.map_to_rag_assistant(a) for a in assistants_raw] if assistants_raw else []
331
+ if assistant_list:
332
+ selection = prompt_resource_selection("RAG assistants", assistant_list, id_field="name", name_field="name")
333
+ return selection
334
+ except Exception as e:
335
+ Console.write_stdout(f"Warning: Could not retrieve RAG assistants: {e}")
336
+ return None
337
+
338
+
339
+ def fetch_and_select_files(
340
+ from_api_key: str,
341
+ from_instance: str,
342
+ from_organization_id: str,
343
+ from_project_id: str
344
+ ) -> Optional[str]:
345
+ """
346
+ Fetch and prompt user to select files for migration.
347
+
348
+ :param from_api_key: Source API key
349
+ :param from_instance: Source instance URL
350
+ :param from_organization_id: Source organization ID
351
+ :param from_project_id: Source project ID
352
+ :return: Comma-separated file IDs, 'all', or None
353
+ """
354
+ try:
355
+ file_manager = FileManager(
356
+ api_key=from_api_key,
357
+ base_url=from_instance,
358
+ organization_id=from_organization_id,
359
+ project_id=from_project_id
360
+ )
361
+ file_list_response = file_manager.get_file_list()
362
+ files = [f for f in file_list_response.files if f.id]
363
+ if files:
364
+ selection = prompt_resource_selection("files", files, id_field="id", name_field="filename")
365
+ return selection
366
+ except Exception as e:
367
+ Console.write_stdout(f"Warning: Could not retrieve files: {e}")
368
+ return None
369
+
370
+
371
+ def handle_usage_limits_keys(
372
+ same_instance: bool,
373
+ from_organization_api_key: Optional[str],
374
+ to_organization_api_key: Optional[str]
375
+ ) -> Tuple[str, str]:
376
+ """
377
+ Ensure organization API keys are available for usage limits migration.
378
+
379
+ :param same_instance: Whether migration is within the same instance
380
+ :param from_organization_api_key: Source organization API key (may be None)
381
+ :param to_organization_api_key: Destination organization API key (may be None)
382
+ :return: Tuple of (from_org_api_key, to_org_api_key)
383
+ """
384
+ if not from_organization_api_key:
385
+ from_organization_api_key = prompt_with_retry("Source organization API key (required for usage limits): ")
386
+ if same_instance:
387
+ to_organization_api_key = from_organization_api_key
388
+ elif not to_organization_api_key:
389
+ to_organization_api_key = prompt_with_retry("Destination organization API key (required for usage limits): ")
390
+
391
+ return from_organization_api_key, to_organization_api_key
392
+
393
+
394
+ def show_summary_and_confirm(
395
+ from_instance: str,
396
+ from_project_id: str,
397
+ to_instance: str,
398
+ to_project_name: Optional[str],
399
+ to_project_id: Optional[str],
400
+ selected_resources: Dict[str, Any]
401
+ ) -> Tuple[bool, bool]:
402
+ """
403
+ Display migration summary and prompt user for confirmation and error handling preference.
404
+
405
+ :param from_instance: Source instance URL
406
+ :param from_project_id: Source project ID
407
+ :param to_instance: Destination instance URL
408
+ :param to_project_name: Destination project name (if creating new project)
409
+ :param to_project_id: Destination project ID (if using existing project)
410
+ :param selected_resources: Dictionary of selected resources to migrate
411
+ :return: Tuple of (confirmation, stop_on_error) - whether user confirmed migration and whether to stop on errors
412
+ """
413
+ Console.write_stdout("\n--- Migration Summary ---")
414
+ Console.write_stdout(f"Source: {from_instance} / Project: {from_project_id}")
415
+ Console.write_stdout(f"Destination: {to_instance} / Project: {to_project_name or to_project_id}")
416
+ Console.write_stdout(f"Resources: {', '.join(selected_resources.keys()) if selected_resources else 'None'}")
417
+ Console.write_stdout("")
418
+
419
+ stop_on_error_response = prompt_with_retry("Stop migration on first error? (Y/n): ", valid_choices=["y", "n", "Y", "N", ""])
420
+ stop_on_error = stop_on_error_response.lower() != "n"
421
+
422
+ confirm = prompt_with_retry("Proceed with migration? (y/n): ", valid_choices=["y", "n"])
423
+ return confirm == "y", stop_on_error
245
424
 
246
- ]
247
425
 
426
+ def build_option_list_and_execute(
427
+ from_api_key: str,
428
+ from_instance: str,
429
+ from_project_id: str,
430
+ from_organization_id: Optional[str],
431
+ from_organization_api_key: Optional[str],
432
+ to_api_key: Optional[str],
433
+ to_instance: str,
434
+ to_project_id: Optional[str],
435
+ to_organization_id: Optional[str],
436
+ to_organization_api_key: Optional[str],
437
+ to_project_name: Optional[str],
438
+ admin_email: Optional[str],
439
+ selected_resources: Dict[str, Any],
440
+ stop_on_error: bool
441
+ ) -> None:
442
+ """
443
+ Build option list from interactive mode selections and execute migration.
444
+
445
+ :param from_api_key: Source API key
446
+ :param from_instance: Source instance URL
447
+ :param from_project_id: Source project ID
448
+ :param from_organization_id: Source organization ID
449
+ :param from_organization_api_key: Source organization API key
450
+ :param to_api_key: Destination API key
451
+ :param to_instance: Destination instance URL
452
+ :param to_project_id: Destination project ID
453
+ :param to_organization_id: Destination organization ID
454
+ :param to_organization_api_key: Destination organization API key
455
+ :param to_project_name: New project name (if creating)
456
+ :param admin_email: Admin email (if creating project)
457
+ :param selected_resources: Dictionary of selected resources to migrate
458
+ :param stop_on_error: Whether to stop migration on first error
459
+ """
460
+ option_list = []
461
+ option_list.append((type('obj', (object,), {'name': 'from_api_key'})(), from_api_key))
462
+ option_list.append((type('obj', (object,), {'name': 'from_instance'})(), from_instance))
463
+ option_list.append((type('obj', (object,), {'name': 'from_project_id'})(), from_project_id))
464
+ option_list.append((type('obj', (object,), {'name': 'from_organization_api_key'})(), from_organization_api_key))
465
+
466
+ if from_organization_id:
467
+ option_list.append((type('obj', (object,), {'name': 'from_organization_id'})(), from_organization_id))
468
+
469
+ option_list.append((type('obj', (object,), {'name': 'to_api_key'})(), to_api_key))
470
+ option_list.append((type('obj', (object,), {'name': 'to_instance'})(), to_instance))
471
+ option_list.append((type('obj', (object,), {'name': 'to_organization_api_key'})(), to_organization_api_key))
472
+
473
+ if to_project_id:
474
+ option_list.append((type('obj', (object,), {'name': 'to_project_id'})(), to_project_id))
475
+ if to_project_name:
476
+ option_list.append((type('obj', (object,), {'name': 'to_project_name'})(), to_project_name))
477
+ if admin_email:
478
+ option_list.append((type('obj', (object,), {'name': 'admin_email'})(), admin_email))
479
+ if to_organization_id:
480
+ option_list.append((type('obj', (object,), {'name': 'to_organization_id'})(), to_organization_id))
481
+
482
+ for resource_type, value in selected_resources.items():
483
+ option_list.append((type('obj', (object,), {'name': resource_type})(), value))
484
+
485
+ option_list.append((type('obj', (object,), {'name': 'stop_on_error'})(), "1" if stop_on_error else "0"))
486
+
487
+ clone_project(option_list)
488
+
489
+
490
+ def clone_project_interactively() -> None:
491
+ """
492
+ Run interactive migration wizard with step-by-step prompts for all configuration.
493
+ """
494
+ Console.write_stdout("")
495
+ Console.write_stdout("=" * 80)
496
+ Console.write_stdout("PROJECT MIGRATION ASSISTANT")
497
+ Console.write_stdout("=" * 80)
498
+ Console.write_stdout("")
499
+
500
+ migration_type = prompt_with_retry(
501
+ "Migration type (1=same instance, 2=cross instance): ",
502
+ valid_choices=["1", "2"]
503
+ )
504
+ same_instance = migration_type == "1"
505
+
506
+ from_api_key, from_instance, from_project_id, from_organization_id = get_source_configuration()
507
+ from_organization_api_key, to_organization_api_key, to_project_name, admin_email, to_project_id = get_project_creation_info(same_instance)
508
+
509
+ creating_project = bool(to_project_name and admin_email)
510
+ to_instance, to_api_key, to_organization_id = get_destination_configuration(
511
+ same_instance, from_instance, from_api_key, from_organization_id, creating_project
512
+ )
513
+
514
+ resource_types_to_migrate = select_resource_types()
515
+
516
+ Console.write_stdout("\n--- Retrieving Available Resources ---")
517
+ lab_manager = AILabManager(api_key=from_api_key, base_url=from_instance)
518
+
519
+ selected_resources = {}
520
+
521
+ if 1 in resource_types_to_migrate:
522
+ selection = fetch_and_select_agents(lab_manager)
523
+ if selection:
524
+ selected_resources["agents"] = selection
525
+
526
+ if 2 in resource_types_to_migrate:
527
+ selection = fetch_and_select_tools(lab_manager)
528
+ if selection:
529
+ selected_resources["tools"] = selection
530
+
531
+ if 3 in resource_types_to_migrate:
532
+ selection = fetch_and_select_processes(lab_manager)
533
+ if selection:
534
+ selected_resources["agentic_processes"] = selection
535
+
536
+ if 4 in resource_types_to_migrate:
537
+ selection = fetch_and_select_tasks(lab_manager)
538
+ if selection:
539
+ selected_resources["tasks"] = selection
540
+
541
+ if 5 in resource_types_to_migrate:
542
+ selection = fetch_and_select_rag_assistants(from_api_key, from_instance)
543
+ if selection:
544
+ selected_resources["rag_assistants"] = selection
545
+
546
+ if 6 in resource_types_to_migrate:
547
+ selection = fetch_and_select_files(from_api_key, from_instance, from_organization_id, from_project_id)
548
+ if selection:
549
+ selected_resources["files"] = selection
550
+
551
+ if 7 in resource_types_to_migrate:
552
+ from_organization_api_key, to_organization_api_key = handle_usage_limits_keys(
553
+ same_instance, from_organization_api_key, to_organization_api_key
554
+ )
555
+ selected_resources["usage_limits"] = True
556
+
557
+ confirmed, stop_on_error = show_summary_and_confirm(from_instance, from_project_id, to_instance, to_project_name, to_project_id, selected_resources)
558
+ if not confirmed:
559
+ Console.write_stdout("Migration cancelled.")
560
+ return
561
+
562
+ build_option_list_and_execute(
563
+ from_api_key, from_instance, from_project_id, from_organization_id, from_organization_api_key,
564
+ to_api_key, to_instance, to_project_id, to_organization_id, to_organization_api_key,
565
+ to_project_name, admin_email, selected_resources, stop_on_error
566
+ )
248
567
 
249
- def clone_tool(option_list: list):
568
+
569
+ def clone_project(option_list: list) -> None:
570
+ """
571
+ Clone a project with selected components from source to destination instance.
572
+
573
+ Supports migration of agents, tools, agentic processes, tasks, usage limits,
574
+ RAG assistants, and files between GenExus AI instances.
575
+
576
+ :param option_list: List of (option_flag, option_value) tuples from CLI parsing
577
+ """
578
+ interactive_mode = False
579
+ for option_flag, option_arg in option_list:
580
+ if option_flag.name == "interactive":
581
+ interactive_mode = True
582
+ break
583
+
584
+ if interactive_mode:
585
+ clone_project_interactively()
586
+ return
587
+
250
588
  from_api_key = None
251
- from_project_id = None
589
+ from_organization_api_key = None
252
590
  from_instance = None
591
+ from_project_id = None
592
+ from_organization_id = None
253
593
  to_api_key = None
254
- to_project_id = None
594
+ to_organization_api_key = None
255
595
  to_instance = None
256
- tool_id = None
596
+ to_project_id = None
597
+ to_organization_id = None
598
+ to_project_name = None
599
+ admin_email = None
600
+
601
+ migrate_all = False
602
+ migrate_agents = False
603
+ migrate_tools = False
604
+ migrate_processes = False
605
+ migrate_tasks = False
606
+ migrate_usage_limits = False
607
+ migrate_rag_assistants = False
608
+ migrate_files = False
609
+
610
+ agent_ids = None
611
+ tool_ids = None
612
+ process_ids = None
613
+ task_ids = None
614
+ assistant_names = None
615
+ file_ids = None
616
+
617
+ stop_on_error = True
257
618
 
258
619
  for option_flag, option_arg in option_list:
259
620
  if option_flag.name == "from_api_key":
260
621
  from_api_key = option_arg
261
- if option_flag.name == "from_project_id":
262
- from_project_id = option_arg
263
- if option_flag.name == "from_instance":
622
+ elif option_flag.name == "from_organization_api_key":
623
+ from_organization_api_key = option_arg
624
+ elif option_flag.name == "from_instance":
264
625
  from_instance = option_arg
265
- if option_flag.name == "to_api_key":
626
+ elif option_flag.name == "from_project_id":
627
+ from_project_id = option_arg
628
+ elif option_flag.name == "from_organization_id":
629
+ from_organization_id = option_arg
630
+ elif option_flag.name == "to_api_key":
266
631
  to_api_key = option_arg
267
- if option_flag.name == "to_project_id":
268
- to_project_id = option_arg
269
- if option_flag.name == "to_instance":
632
+ elif option_flag.name == "to_organization_api_key":
633
+ to_organization_api_key = option_arg
634
+ elif option_flag.name == "to_instance":
270
635
  to_instance = option_arg
271
- if option_flag.name == "tool_id":
272
- tool_id = option_arg
273
-
274
- if not (from_project_id and from_instance and tool_id):
275
- raise MissingRequirementException("Cannot migrate resources without indicating source: project, instance, and tool ID")
276
-
277
- migration_strategy = ToolMigrationStrategy(
278
- from_api_key=from_api_key,
279
- from_project_id=from_project_id,
280
- from_instance=from_instance,
281
- to_api_key=to_api_key,
282
- to_project_id=to_project_id,
283
- to_instance=to_instance,
284
- tool_id=tool_id
285
- )
286
- tool = MigrationTool(migration_strategy)
287
- tool.run_migration()
636
+ elif option_flag.name == "to_project_id":
637
+ to_project_id = option_arg
638
+ elif option_flag.name == "to_organization_id":
639
+ to_organization_id = option_arg
640
+ elif option_flag.name == "to_project_name":
641
+ to_project_name = option_arg
642
+ elif option_flag.name == "admin_email":
643
+ admin_email = option_arg
644
+ elif option_flag.name == "all":
645
+ migrate_all = True
646
+ elif option_flag.name == "agents":
647
+ migrate_agents = True
648
+ agent_ids = option_arg if option_arg else "all"
649
+ elif option_flag.name == "tools":
650
+ migrate_tools = True
651
+ tool_ids = option_arg if option_arg else "all"
652
+ elif option_flag.name == "agentic_processes":
653
+ migrate_processes = True
654
+ process_ids = option_arg if option_arg else "all"
655
+ elif option_flag.name == "tasks":
656
+ migrate_tasks = True
657
+ task_ids = option_arg if option_arg else "all"
658
+ elif option_flag.name == "usage_limits":
659
+ migrate_usage_limits = True
660
+ elif option_flag.name == "rag_assistants":
661
+ migrate_rag_assistants = True
662
+ assistant_names = option_arg if option_arg else "all"
663
+ elif option_flag.name == "files":
664
+ migrate_files = True
665
+ file_ids = option_arg if option_arg else "all"
666
+ elif option_flag.name == "stop_on_error":
667
+ from pygeai.cli.commands.common import get_boolean_value
668
+ stop_on_error = get_boolean_value(option_arg)
669
+
670
+ if not all([from_api_key, from_instance, from_project_id]):
671
+ raise MissingRequirementException("Source API key, instance, and project ID are required")
672
+
673
+ if (to_project_name or admin_email) and not (to_project_name and admin_email):
674
+ raise MissingRequirementException(
675
+ "Both --to-project-name and --admin-email are required when creating a new project"
676
+ )
677
+
678
+ if to_project_id and (to_project_name or admin_email):
679
+ raise MissingRequirementException(
680
+ "Cannot specify both --to-project-id and project creation parameters (--to-project-name, --admin-email)"
681
+ )
682
+
683
+ if not to_project_id and not (to_project_name and admin_email):
684
+ raise MissingRequirementException(
685
+ "Must specify either --to-project-id (for existing project) or both --to-project-name and --admin-email (to create new project)"
686
+ )
687
+
688
+ if to_project_id and not to_api_key:
689
+ raise MissingRequirementException(
690
+ "Destination project API key (--to-api-key) is required when migrating to an existing project (--to-project-id)"
691
+ )
692
+
693
+ if to_project_name and admin_email:
694
+ if not from_organization_api_key:
695
+ raise MissingRequirementException(
696
+ "Source organization scope API key (--from-org-key) is required for project creation"
697
+ )
698
+ if not (to_organization_api_key or from_organization_api_key):
699
+ raise MissingRequirementException(
700
+ "Destination organization scope API key (--to-org-key) is required for project creation in a different "
701
+ "instance. Alternatively source organization scope (--from-org-key) can be used if project needs to be "
702
+ "created in the same instance."
703
+ )
704
+
705
+ # Validate organization scope keys for usage limits migration
706
+ if migrate_usage_limits:
707
+ if not from_organization_api_key:
708
+ raise MissingRequirementException(
709
+ "Source organization scope API key (--from-org-key) is required for usage limits migration"
710
+ )
711
+ if not (to_organization_api_key or from_organization_api_key):
712
+ raise MissingRequirementException(
713
+ "Destination organization scope API key (--to-org-key) is required for usage limits migration in a "
714
+ "different instance. Alternatively source organization scope (--from-org-key) can be used if limits "
715
+ "need to be migrated in the same instance."
716
+ )
717
+
718
+ if to_project_name and admin_email:
719
+ Console.write_stdout(f"Creating new project '{to_project_name}'...")
720
+
721
+ org_key_to_use = to_organization_api_key or from_organization_api_key
722
+ logger.debug(f"DEBUG: Preparing to create project with organization API key")
723
+ logger.debug(f" - to_organization_api_key exists: {to_organization_api_key is not None}")
724
+ logger.debug(f" - from_organization_api_key exists: {from_organization_api_key is not None}")
725
+ logger.debug(f" - Using key (first 20 chars): {org_key_to_use[:20] if org_key_to_use else 'None'}...")
726
+ Console.write_stderr(f"DEBUG: Using org key for project creation (first 20 chars): {org_key_to_use[:20] if org_key_to_use else 'None'}...")
727
+
728
+ project_strategy = ProjectMigrationStrategy(
729
+ from_api_key=from_organization_api_key,
730
+ from_instance=from_instance,
731
+ from_project_id=from_project_id,
732
+ to_project_name=to_project_name,
733
+ admin_email=admin_email,
734
+ to_api_key=org_key_to_use,
735
+ to_instance=to_instance
736
+ )
737
+ project_tool = MigrationTool(project_strategy)
738
+ to_project_id = project_tool.run_migration()
739
+
740
+ if not to_project_id:
741
+ raise ValueError("Project creation did not return a project ID")
742
+
743
+ Console.write_stdout(f"Project '{to_project_name}' created successfully with ID: {to_project_id}")
744
+
745
+ from pygeai.auth.clients import AuthClient
746
+ Console.write_stdout(f"Creating project API key for new project...")
747
+
748
+ org_key_for_token_creation = to_organization_api_key or from_organization_api_key
749
+ auth_client = AuthClient(
750
+ api_key=org_key_for_token_creation,
751
+ base_url=to_instance or from_instance
752
+ )
753
+
754
+ token_response = auth_client.create_project_api_token(
755
+ project_id=to_project_id,
756
+ name=f"Migration API Key for {to_project_name}",
757
+ description=f"Auto-generated API key for project migration to {to_project_name}"
758
+ )
759
+
760
+ if not token_response or 'id' not in token_response:
761
+ raise ValueError("Failed to create project API key")
762
+
763
+ to_api_key = token_response['id']
764
+ Console.write_stdout(f"Project API key created successfully")
765
+
766
+ if not to_organization_id:
767
+ Console.write_stdout(f"Retrieving destination organization ID...")
768
+ dest_admin_client = AdminClient(api_key=to_api_key, base_url=to_instance or from_instance)
769
+ dest_token_info = dest_admin_client.validate_api_token()
770
+ to_organization_id = dest_token_info.get("organizationId")
771
+ Console.write_stdout(f"Destination organization ID: {to_organization_id}")
772
+
773
+ if migrate_all:
774
+ migrate_agents = True
775
+ migrate_tools = True
776
+ migrate_processes = True
777
+ migrate_tasks = True
778
+ migrate_usage_limits = True if from_organization_id and to_organization_id else False
779
+ migrate_rag_assistants = True
780
+ migrate_files = True if from_organization_id and to_organization_id else False
781
+ agent_ids = "all"
782
+ tool_ids = "all"
783
+ process_ids = "all"
784
+ task_ids = "all"
785
+ assistant_names = "all"
786
+ file_ids = "all"
787
+
788
+ strategies = []
789
+
790
+ lab_manager = AILabManager(api_key=from_api_key, base_url=from_instance)
791
+
792
+ if migrate_agents:
793
+ if agent_ids == "all":
794
+ agent_list = lab_manager.get_agent_list(FilterSettings(count=1000))
795
+ discovered_agents = [agent.id for agent in agent_list.agents if agent.id]
796
+ Console.write_stdout(f"Discovered {len(discovered_agents)} agents")
797
+ for agent_id in discovered_agents:
798
+ strategies.append(AgentMigrationStrategy(
799
+ from_api_key=from_api_key,
800
+ from_instance=from_instance,
801
+ agent_id=agent_id,
802
+ to_api_key=to_api_key,
803
+ to_instance=to_instance
804
+ ))
805
+ elif agent_ids:
806
+ for agent_id in agent_ids.split(','):
807
+ strategies.append(AgentMigrationStrategy(
808
+ from_api_key=from_api_key,
809
+ from_instance=from_instance,
810
+ agent_id=agent_id.strip(),
811
+ to_api_key=to_api_key,
812
+ to_instance=to_instance
813
+ ))
814
+
815
+ if migrate_tools:
816
+ if tool_ids == "all":
817
+ tool_list = lab_manager.list_tools(FilterSettings(count=1000))
818
+ discovered_tools = [tool.id for tool in tool_list.tools if tool.id]
819
+ Console.write_stdout(f"Discovered {len(discovered_tools)} tools")
820
+ for tool_id in discovered_tools:
821
+ strategies.append(ToolMigrationStrategy(
822
+ from_api_key=from_api_key,
823
+ from_instance=from_instance,
824
+ tool_id=tool_id,
825
+ to_api_key=to_api_key,
826
+ to_instance=to_instance
827
+ ))
828
+ elif tool_ids:
829
+ for tool_id in tool_ids.split(','):
830
+ strategies.append(ToolMigrationStrategy(
831
+ from_api_key=from_api_key,
832
+ from_instance=from_instance,
833
+ tool_id=tool_id.strip(),
834
+ to_api_key=to_api_key,
835
+ to_instance=to_instance
836
+ ))
837
+
838
+ if migrate_processes:
839
+ if process_ids == "all":
840
+ process_list = lab_manager.list_processes(FilterSettings(count=1000))
841
+ discovered_processes = [proc.id for proc in process_list.processes if proc.id]
842
+ Console.write_stdout(f"Discovered {len(discovered_processes)} agentic processes")
843
+ for process_id in discovered_processes:
844
+ strategies.append(AgenticProcessMigrationStrategy(
845
+ from_api_key=from_api_key,
846
+ from_instance=from_instance,
847
+ process_id=process_id,
848
+ to_api_key=to_api_key,
849
+ to_instance=to_instance
850
+ ))
851
+ elif process_ids:
852
+ for process_id in process_ids.split(','):
853
+ strategies.append(AgenticProcessMigrationStrategy(
854
+ from_api_key=from_api_key,
855
+ from_instance=from_instance,
856
+ process_id=process_id.strip(),
857
+ to_api_key=to_api_key,
858
+ to_instance=to_instance
859
+ ))
860
+
861
+ if migrate_tasks:
862
+ if task_ids == "all":
863
+ task_list = lab_manager.list_tasks(FilterSettings(count=1000))
864
+ discovered_tasks = [task.id for task in task_list.tasks if task.id]
865
+ Console.write_stdout(f"Discovered {len(discovered_tasks)} tasks")
866
+ for task_id in discovered_tasks:
867
+ strategies.append(TaskMigrationStrategy(
868
+ from_api_key=from_api_key,
869
+ from_instance=from_instance,
870
+ task_id=task_id,
871
+ to_api_key=to_api_key,
872
+ to_instance=to_instance
873
+ ))
874
+ elif task_ids:
875
+ for task_id in task_ids.split(','):
876
+ strategies.append(TaskMigrationStrategy(
877
+ from_api_key=from_api_key,
878
+ from_instance=from_instance,
879
+ task_id=task_id.strip(),
880
+ to_api_key=to_api_key,
881
+ to_instance=to_instance
882
+ ))
883
+
884
+ if migrate_usage_limits and from_organization_id and to_organization_id:
885
+ strategies.append(UsageLimitMigrationStrategy(
886
+ from_api_key=from_organization_api_key,
887
+ from_instance=from_instance,
888
+ from_organization_id=from_organization_id,
889
+ to_organization_id=to_organization_id,
890
+ to_api_key=to_organization_api_key or from_organization_api_key,
891
+ to_instance=to_instance
892
+ ))
893
+
894
+ if migrate_rag_assistants:
895
+ rag_client = RAGAssistantClient(api_key=from_api_key, base_url=from_instance)
896
+ if assistant_names == "all":
897
+ assistant_data = rag_client.get_assistants_from_project()
898
+ assistants_raw = assistant_data.get("assistants", [])
899
+ assistant_list = [RAGAssistantMapper.map_to_rag_assistant(a) for a in assistants_raw] if assistants_raw else []
900
+ discovered_assistants = [assistant.name for assistant in assistant_list if assistant.name]
901
+ Console.write_stdout(f"Discovered {len(discovered_assistants)} RAG assistants")
902
+ for assistant_name in discovered_assistants:
903
+ strategies.append(RAGAssistantMigrationStrategy(
904
+ from_api_key=from_api_key,
905
+ from_instance=from_instance,
906
+ assistant_name=assistant_name,
907
+ to_api_key=to_api_key,
908
+ to_instance=to_instance
909
+ ))
910
+ elif assistant_names:
911
+ for assistant_name in assistant_names.split(','):
912
+ strategies.append(RAGAssistantMigrationStrategy(
913
+ from_api_key=from_api_key,
914
+ from_instance=from_instance,
915
+ assistant_name=assistant_name.strip(),
916
+ to_api_key=to_api_key,
917
+ to_instance=to_instance
918
+ ))
919
+
920
+ if migrate_files and from_organization_id and from_project_id and to_organization_id and to_project_id:
921
+ file_manager = FileManager(
922
+ api_key=from_api_key,
923
+ base_url=from_instance,
924
+ organization_id=from_organization_id,
925
+ project_id=from_project_id
926
+ )
927
+ if file_ids == "all":
928
+ file_list_response = file_manager.get_file_list()
929
+ discovered_files = [f.id for f in file_list_response.files if f.id]
930
+ Console.write_stdout(f"Discovered {len(discovered_files)} files")
931
+ for file_id in discovered_files:
932
+ strategies.append(FileMigrationStrategy(
933
+ from_api_key=from_api_key,
934
+ from_instance=from_instance,
935
+ from_organization_id=from_organization_id,
936
+ from_project_id=from_project_id,
937
+ to_organization_id=to_organization_id,
938
+ to_project_id=to_project_id,
939
+ file_id=file_id,
940
+ to_api_key=to_api_key,
941
+ to_instance=to_instance
942
+ ))
943
+ elif file_ids:
944
+ for file_id in file_ids.split(','):
945
+ strategies.append(FileMigrationStrategy(
946
+ from_api_key=from_api_key,
947
+ from_instance=from_instance,
948
+ from_organization_id=from_organization_id,
949
+ from_project_id=from_project_id,
950
+ to_organization_id=to_organization_id,
951
+ to_project_id=to_project_id,
952
+ file_id=file_id.strip(),
953
+ to_api_key=to_api_key,
954
+ to_instance=to_instance
955
+ ))
956
+
957
+ if not strategies:
958
+ Console.write_stdout("No migration strategies configured. Use flags like --agents, --tools, --all, etc.")
959
+ return
288
960
 
961
+ plan = MigrationPlan(strategies=strategies, stop_on_error=stop_on_error)
962
+ orchestrator = MigrationOrchestrator(plan)
963
+
964
+ try:
965
+ result = orchestrator.execute()
966
+ Console.write_stdout(f"Migration completed: {result['completed']}/{result['total']} successful")
967
+ logger.info(f"Project cloning completed: {result}")
968
+ except Exception as e:
969
+ Console.write_stderr(f"Migration failed: {e}")
970
+ logger.error(f"Project cloning failed: {e}")
971
+ raise
289
972
 
290
- clone_tool_options = [
973
+
974
+ clone_project_options = [
975
+ Option(
976
+ "interactive",
977
+ ["-i", "--interactive"],
978
+ "Interactive mode: guided step-by-step migration wizard",
979
+ False
980
+ ),
291
981
  Option(
292
982
  "from_api_key",
293
- ["--from-api-key", "--fak"],
294
- "API key for the source instance",
983
+ ["--from-api-key", "--from-key"],
984
+ "Source instance project scope API key (required)",
295
985
  True
296
986
  ),
297
987
  Option(
298
- "from_project_id",
299
- ["--from-project-id", "--fpid"],
300
- "ID of the source project to migrate from",
988
+ "from_organization_api_key",
989
+ ["--from-org-key", "--from-organization-key"],
990
+ "Source instance organization scope API key (optional, for project creation)",
301
991
  True
302
992
  ),
303
993
  Option(
304
994
  "from_instance",
305
- ["--from-instance", "--fi"],
306
- "URL from the source instance to migrate from",
995
+ ["--from-instance", "--from-url"],
996
+ "Source instance URL (required)",
307
997
  True
308
998
  ),
309
999
  Option(
310
- "to_api_key",
311
- ["--to-api-key", "--tak"],
312
- "API key for the destination instance. If not specified, the same instance's API key will be used",
1000
+ "from_project_id",
1001
+ ["--from-project-id", "--from-pid"],
1002
+ "Source project ID (required)",
313
1003
  True
314
1004
  ),
315
1005
  Option(
316
- "to_project_id",
317
- ["--to-project-id", "--tpid"],
318
- "ID of the destination project to migrate to",
1006
+ "from_organization_id",
1007
+ ["--from-organization-id", "--from-oid"],
1008
+ "Source organization ID (required for usage limits and files)",
319
1009
  True
320
1010
  ),
321
1011
  Option(
322
- "to_instance",
323
- ["--to-instance", "--ti"],
324
- "URL from the destination instance to migrate to. If not specified, the same instance's URL will be used",
1012
+ "to_api_key",
1013
+ ["--to-api-key", "--to-key"],
1014
+ "Destination instance project scope API key (optional, defaults to source key)",
325
1015
  True
326
1016
  ),
327
1017
  Option(
328
- "tool_id",
329
- ["--tool-id", "--tid"],
330
- "Unique identifier from the tool to be migrated",
1018
+ "to_organization_api_key",
1019
+ ["--to-org-key", "--to-organization-key"],
1020
+ "Destination instance organization scope API key (optional, for project creation)",
331
1021
  True
332
1022
  ),
333
- ]
334
-
335
-
336
- def clone_process(option_list: list):
337
- from_api_key = None
338
- from_project_id = None
339
- from_instance = None
340
- to_api_key = None
341
- to_project_id = None
342
- to_instance = None
343
- process_id = None
344
-
345
- for option_flag, option_arg in option_list:
346
- if option_flag.name == "from_api_key":
347
- from_api_key = option_arg
348
- if option_flag.name == "from_project_id":
349
- from_project_id = option_arg
350
- if option_flag.name == "from_instance":
351
- from_instance = option_arg
352
- if option_flag.name == "to_api_key":
353
- to_api_key = option_arg
354
- if option_flag.name == "to_project_id":
355
- to_project_id = option_arg
356
- if option_flag.name == "to_instance":
357
- to_instance = option_arg
358
- if option_flag.name == "process_id":
359
- process_id = option_arg
360
-
361
- if not (from_project_id and from_instance and process_id):
362
- raise MissingRequirementException("Cannot migrate resources without indicating source: project, instance, and process ID")
363
-
364
- migration_strategy = AgenticProcessMigrationStrategy(
365
- from_api_key=from_api_key,
366
- from_project_id=from_project_id,
367
- from_instance=from_instance,
368
- to_api_key=to_api_key,
369
- to_project_id=to_project_id,
370
- to_instance=to_instance,
371
- process_id=process_id
372
- )
373
- tool = MigrationTool(migration_strategy)
374
- tool.run_migration()
375
-
376
-
377
- clone_process_options = [
378
1023
  Option(
379
- "from_api_key",
380
- ["--from-api-key", "--fak"],
381
- "API key for the source instance",
1024
+ "to_instance",
1025
+ ["--to-instance", "--to-url"],
1026
+ "Destination instance URL (optional, defaults to source URL)",
382
1027
  True
383
1028
  ),
384
1029
  Option(
385
- "from_project_id",
386
- ["--from-project-id", "--fpid"],
387
- "ID of the source project to migrate from",
1030
+ "to_project_name",
1031
+ ["--to-project-name", "--to-name"],
1032
+ "Name for the new destination project (creates new project if specified with --admin-email)",
388
1033
  True
389
1034
  ),
390
1035
  Option(
391
- "from_instance",
392
- ["--from-instance", "--fi"],
393
- "URL from the source instance to migrate from",
1036
+ "admin_email",
1037
+ ["--admin-email"],
1038
+ "Admin email for new project (required when creating new project)",
394
1039
  True
395
1040
  ),
396
1041
  Option(
397
- "to_api_key",
398
- ["--to-api-key", "--tak"],
399
- "API key for the destination instance. If not specified, the same instance's API key will be used",
1042
+ "to_project_id",
1043
+ ["--to-project-id", "--to-pid"],
1044
+ "Destination project ID (optional for files)",
400
1045
  True
401
1046
  ),
402
1047
  Option(
403
- "to_project_id",
404
- ["--to-project-id", "--tpid"],
405
- "ID of the destination project to migrate to",
1048
+ "to_organization_id",
1049
+ ["--to-organization-id", "--to-oid"],
1050
+ "Destination organization ID (optional for usage limits and files)",
406
1051
  True
407
1052
  ),
408
1053
  Option(
409
- "to_instance",
410
- ["--to-instance", "--ti"],
411
- "URL from the destination instance to migrate to. If not specified, the same instance's URL will be used",
412
- True
1054
+ "all",
1055
+ ["--all"],
1056
+ "Migrate all available components",
1057
+ False
413
1058
  ),
414
1059
  Option(
415
- "process_id",
416
- ["--process-id", "--pid"],
417
- "Unique identifier from the process to be migrated",
1060
+ "agents",
1061
+ ["--agents"],
1062
+ "Agent IDs to migrate: comma-separated IDs or 'all'",
418
1063
  True
419
1064
  ),
420
- ]
421
-
422
-
423
- def clone_task(option_list: list):
424
- from_api_key = None
425
- from_project_id = None
426
- from_instance = None
427
- to_api_key = None
428
- to_project_id = None
429
- to_instance = None
430
- task_id = None
431
-
432
- for option_flag, option_arg in option_list:
433
- if option_flag.name == "from_api_key":
434
- from_api_key = option_arg
435
- if option_flag.name == "from_project_id":
436
- from_project_id = option_arg
437
- if option_flag.name == "from_instance":
438
- from_instance = option_arg
439
- if option_flag.name == "to_api_key":
440
- to_api_key = option_arg
441
- if option_flag.name == "to_project_id":
442
- to_project_id = option_arg
443
- if option_flag.name == "to_instance":
444
- to_instance = option_arg
445
- if option_flag.name == "task_id":
446
- task_id = option_arg
447
-
448
- if not (from_project_id and from_instance and task_id):
449
- raise MissingRequirementException("Cannot migrate resources without indicating source: project, instance, and task ID")
450
-
451
- migration_strategy = TaskMigrationStrategy(
452
- from_api_key=from_api_key,
453
- from_project_id=from_project_id,
454
- from_instance=from_instance,
455
- to_api_key=to_api_key,
456
- to_project_id=to_project_id,
457
- to_instance=to_instance,
458
- task_id=task_id
459
- )
460
- tool = MigrationTool(migration_strategy)
461
- response = tool.run_migration()
462
-
463
- Console.write_stdout(f"Migration result: \n{response}")
464
-
465
-
466
- clone_task_options = [
467
1065
  Option(
468
- "from_api_key",
469
- ["--from-api-key", "--fak"],
470
- "API key for the source instance",
1066
+ "tools",
1067
+ ["--tools"],
1068
+ "Tool IDs to migrate: comma-separated IDs or 'all'",
471
1069
  True
472
1070
  ),
473
1071
  Option(
474
- "from_project_id",
475
- ["--from-project-id", "--fpid"],
476
- "ID of the source project to migrate from",
1072
+ "agentic_processes",
1073
+ ["--agentic-processes", "--processes"],
1074
+ "Agentic process IDs to migrate: comma-separated IDs or 'all'",
477
1075
  True
478
1076
  ),
479
1077
  Option(
480
- "from_instance",
481
- ["--from-instance", "--fi"],
482
- "URL from the source instance to migrate from",
1078
+ "tasks",
1079
+ ["--tasks"],
1080
+ "Task IDs to migrate: comma-separated IDs or 'all'",
483
1081
  True
484
1082
  ),
485
1083
  Option(
486
- "to_api_key",
487
- ["--to-api-key", "--tak"],
488
- "API key for the destination instance. If not specified, the same instance's API key will be used",
489
- True
1084
+ "usage_limits",
1085
+ ["--usage-limits"],
1086
+ "Migrate usage limits (requires organization IDs)",
1087
+ False
490
1088
  ),
491
1089
  Option(
492
- "to_project_id",
493
- ["--to-project-id", "--tpid"],
494
- "ID of the destination project to migrate to",
1090
+ "rag_assistants",
1091
+ ["--rag-assistants"],
1092
+ "RAG assistant names to migrate: comma-separated names or 'all'",
495
1093
  True
496
1094
  ),
497
1095
  Option(
498
- "to_instance",
499
- ["--to-instance", "--ti"],
500
- "URL from the destination instance to migrate to. If not specified, the same instance's URL will be used",
1096
+ "files",
1097
+ ["--files"],
1098
+ "File IDs to migrate: comma-separated IDs or 'all' (requires org/project IDs)",
501
1099
  True
502
1100
  ),
503
1101
  Option(
504
- "task_id",
505
- ["--task-id", "--tid"],
506
- "Unique identifier from the task to be migrated",
1102
+ "stop_on_error",
1103
+ ["--stop-on-error", "--soe"],
1104
+ "Stop migration on first error: 0: False, 1: True (default: 1)",
507
1105
  True
508
1106
  ),
509
1107
  ]
510
1108
 
511
1109
 
512
-
513
1110
  migrate_commands = [
514
1111
  Command(
515
1112
  "help",
@@ -523,47 +1120,10 @@ migrate_commands = [
523
1120
  Command(
524
1121
  "clone_project",
525
1122
  ["clone-project"],
526
- "Clone project from instance",
1123
+ "Clone project components between instances",
527
1124
  clone_project,
528
1125
  ArgumentsEnum.REQUIRED,
529
1126
  [],
530
1127
  clone_project_options
531
1128
  ),
532
- Command(
533
- "clone_agent",
534
- ["clone-agent"],
535
- "Clone agentt from instance",
536
- clone_agent,
537
- ArgumentsEnum.REQUIRED,
538
- [],
539
- clone_agent_options
540
- ),
541
- Command(
542
- "clone_tool",
543
- ["clone-tool"],
544
- "Clone tool from instance",
545
- clone_tool,
546
- ArgumentsEnum.REQUIRED,
547
- [],
548
- clone_tool_options
549
- ),
550
- Command(
551
- "clone_process",
552
- ["clone-process"],
553
- "Clone process from instance",
554
- clone_process,
555
- ArgumentsEnum.REQUIRED,
556
- [],
557
- clone_process_options
558
- ),
559
- # TODO -> Remove clone-task: clone-process includes cloning tasks
560
- # Command(
561
- # "clone_task",
562
- # ["clone-task"],
563
- # "Clone task from instance",
564
- # clone_task,
565
- # ArgumentsEnum.REQUIRED,
566
- # [],
567
- # clone_task_options
568
- #),
569
1129
  ]