agent-starter-pack 0.10.1__py3-none-any.whl → 0.11.1__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 (50) hide show
  1. {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.1.dist-info}/METADATA +10 -3
  2. {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.1.dist-info}/RECORD +43 -49
  3. agents/crewai_coding_crew/.template/templateconfig.yaml +2 -2
  4. agents/crewai_coding_crew/tests/integration/test_agent.py +1 -1
  5. agents/langgraph_base_react/.template/templateconfig.yaml +1 -1
  6. agents/langgraph_base_react/tests/integration/test_agent.py +1 -1
  7. src/base_template/deployment/terraform/dev/iam.tf +12 -11
  8. src/base_template/deployment/terraform/dev/variables.tf +2 -7
  9. src/base_template/deployment/terraform/github.tf +14 -0
  10. src/base_template/deployment/terraform/iam.tf +10 -7
  11. src/base_template/deployment/terraform/service_accounts.tf +4 -5
  12. src/base_template/deployment/terraform/variables.tf +2 -7
  13. src/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}build_triggers.tf{% else %}unused_build_triggers.tf{% endif %} +4 -2
  14. src/base_template/pyproject.toml +2 -2
  15. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +3 -0
  16. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +1 -0
  17. src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +1 -0
  18. src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +1 -0
  19. src/cli/commands/create.py +202 -100
  20. src/cli/commands/enhance.py +302 -0
  21. src/cli/commands/list.py +23 -11
  22. src/cli/main.py +2 -0
  23. src/cli/utils/logging.py +40 -0
  24. src/cli/utils/remote_template.py +55 -16
  25. src/cli/utils/template.py +212 -94
  26. src/deployment_targets/agent_engine/app/agent_engine_app.py +8 -0
  27. src/deployment_targets/cloud_run/app/server.py +1 -1
  28. src/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +1 -1
  29. src/deployment_targets/cloud_run/deployment/terraform/service.tf +2 -2
  30. src/resources/locks/uv-adk_base-agent_engine.lock +312 -312
  31. src/resources/locks/uv-adk_base-cloud_run.lock +403 -404
  32. src/resources/locks/uv-adk_gemini_fullstack-agent_engine.lock +312 -312
  33. src/resources/locks/uv-adk_gemini_fullstack-cloud_run.lock +403 -404
  34. src/resources/locks/uv-agentic_rag-agent_engine.lock +371 -371
  35. src/resources/locks/uv-agentic_rag-cloud_run.lock +477 -478
  36. src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +661 -591
  37. src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +868 -760
  38. src/resources/locks/uv-langgraph_base_react-agent_engine.lock +496 -446
  39. src/resources/locks/uv-langgraph_base_react-cloud_run.lock +639 -565
  40. src/resources/locks/uv-live_api-cloud_run.lock +584 -510
  41. agents/adk_gemini_fullstack/.template/templateconfig.yaml +0 -39
  42. agents/adk_gemini_fullstack/README.md +0 -27
  43. agents/adk_gemini_fullstack/app/agent.py +0 -412
  44. agents/adk_gemini_fullstack/app/config.py +0 -46
  45. agents/adk_gemini_fullstack/notebooks/adk_app_testing.ipynb +0 -355
  46. agents/adk_gemini_fullstack/notebooks/evaluating_adk_agent.ipynb +0 -1528
  47. agents/adk_gemini_fullstack/tests/integration/test_agent.py +0 -58
  48. {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.1.dist-info}/WHEEL +0 -0
  49. {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.1.dist-info}/entry_points.txt +0 -0
  50. {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.1.dist-info}/licenses/LICENSE +0 -0
@@ -12,21 +12,24 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ import datetime
15
16
  import logging
16
17
  import os
17
18
  import pathlib
18
19
  import shutil
19
20
  import subprocess
20
21
  import tempfile
22
+ from collections.abc import Callable
21
23
 
22
24
  import click
25
+ import tomllib
23
26
  from click.core import ParameterSource
24
27
  from rich.console import Console
25
28
  from rich.prompt import IntPrompt, Prompt
26
29
 
27
30
  from ..utils.datastores import DATASTORE_TYPES, DATASTORES
28
31
  from ..utils.gcp import verify_credentials, verify_vertex_connection
29
- from ..utils.logging import handle_cli_error
32
+ from ..utils.logging import display_welcome_banner, handle_cli_error
30
33
  from ..utils.remote_template import (
31
34
  fetch_remote_template,
32
35
  get_base_template_name,
@@ -48,6 +51,114 @@ from ..utils.template import (
48
51
 
49
52
  console = Console()
50
53
 
54
+ # Export the shared decorator for use by other commands
55
+ __all__ = ["create", "shared_template_options"]
56
+
57
+
58
+ def shared_template_options(f: Callable) -> Callable:
59
+ """Decorator to add shared options for template-based commands."""
60
+ # Apply options in reverse order since decorators are applied bottom-up
61
+ f = click.option(
62
+ "--skip-checks",
63
+ is_flag=True,
64
+ help="Skip verification checks for GCP and Vertex AI",
65
+ default=False,
66
+ )(f)
67
+ f = click.option(
68
+ "--region",
69
+ help="GCP region for deployment (default: us-central1)",
70
+ default="us-central1",
71
+ )(f)
72
+ f = click.option(
73
+ "--auto-approve", is_flag=True, help="Skip credential confirmation prompts"
74
+ )(f)
75
+ f = click.option("--debug", is_flag=True, help="Enable debug logging")(f)
76
+ f = click.option(
77
+ "--session-type",
78
+ type=click.Choice(["in_memory", "alloydb", "agent_engine"]),
79
+ help="Type of session storage to use",
80
+ )(f)
81
+ f = click.option(
82
+ "--datastore",
83
+ "-ds",
84
+ type=click.Choice(DATASTORE_TYPES),
85
+ help="Type of datastore to use for data ingestion (requires --include-data-ingestion)",
86
+ )(f)
87
+ f = click.option(
88
+ "--include-data-ingestion",
89
+ "-i",
90
+ is_flag=True,
91
+ help="Include data ingestion pipeline in the project",
92
+ )(f)
93
+ f = click.option(
94
+ "--cicd-runner",
95
+ type=click.Choice(["google_cloud_build", "github_actions"]),
96
+ help="CI/CD runner to use",
97
+ )(f)
98
+ f = click.option(
99
+ "--deployment-target",
100
+ "-d",
101
+ type=click.Choice(["agent_engine", "cloud_run"]),
102
+ help="Deployment target name",
103
+ )(f)
104
+ return f
105
+
106
+
107
+ def get_available_base_templates() -> list[str]:
108
+ """Get list of available base templates for inheritance.
109
+
110
+ Returns:
111
+ List of base template names.
112
+ """
113
+ agents = get_available_agents()
114
+ return sorted([agent_info["name"] for agent_info in agents.values()])
115
+
116
+
117
+ def validate_base_template(base_template: str) -> bool:
118
+ """Validate that a base template exists.
119
+
120
+ Args:
121
+ base_template: Name of the base template to validate
122
+
123
+ Returns:
124
+ True if the base template exists, False otherwise
125
+ """
126
+ available_templates = get_available_base_templates()
127
+ return base_template in available_templates
128
+
129
+
130
+ def get_standard_ignore_patterns() -> Callable[[str, list[str]], list[str]]:
131
+ """Get standard ignore patterns for copying directories.
132
+
133
+ Returns:
134
+ A callable that can be used with shutil.copytree's ignore parameter.
135
+ """
136
+ exclude_dirs = {
137
+ ".git",
138
+ ".venv",
139
+ "venv",
140
+ "__pycache__",
141
+ ".pytest_cache",
142
+ "node_modules",
143
+ ".next",
144
+ "dist",
145
+ "build",
146
+ ".DS_Store",
147
+ ".vscode",
148
+ ".idea",
149
+ "*.egg-info",
150
+ ".mypy_cache",
151
+ ".coverage",
152
+ "htmlcov",
153
+ ".tox",
154
+ ".cache",
155
+ }
156
+
157
+ def ignore_patterns(dir: str, files: list[str]) -> list[str]:
158
+ return [f for f in files if f in exclude_dirs or f.startswith(".backup_")]
159
+
160
+ return ignore_patterns
161
+
51
162
 
52
163
  def normalize_project_name(project_name: str) -> str:
53
164
  """Normalize project name for better compatibility with cloud resources and tools."""
@@ -91,35 +202,6 @@ def normalize_project_name(project_name: str) -> str:
91
202
  "-a",
92
203
  help="Template identifier to use. Can be a local agent name (e.g., `chat_agent`), a local path (`local@/path/to/template`), an `adk-samples` shortcut (e.g., `adk@data-science`), or a remote Git URL. Both shorthand (e.g., `github.com/org/repo/path@main`) and full URLs from your browser (e.g., `https://github.com/org/repo/tree/main/path`) are supported. Lists available local templates if omitted.",
93
204
  )
94
- @click.option(
95
- "--deployment-target",
96
- "-d",
97
- type=click.Choice(["agent_engine", "cloud_run"]),
98
- help="Deployment target name",
99
- )
100
- @click.option(
101
- "--cicd-runner",
102
- type=click.Choice(["google_cloud_build", "github_actions"]),
103
- help="CI/CD runner to use",
104
- )
105
- @click.option(
106
- "--include-data-ingestion",
107
- "-i",
108
- is_flag=True,
109
- help="Include data ingestion pipeline in the project",
110
- )
111
- @click.option(
112
- "--datastore",
113
- "-ds",
114
- type=click.Choice(DATASTORE_TYPES),
115
- help="Type of datastore to use for data ingestion (requires --include-data-ingestion)",
116
- )
117
- @click.option(
118
- "--session-type",
119
- type=click.Choice(["in_memory", "alloydb", "agent_engine"]),
120
- help="Type of session storage to use",
121
- )
122
- @click.option("--debug", is_flag=True, help="Enable debug logging")
123
205
  @click.option(
124
206
  "--output-dir",
125
207
  "-o",
@@ -127,19 +209,13 @@ def normalize_project_name(project_name: str) -> str:
127
209
  help="Output directory for the project (default: current directory)",
128
210
  )
129
211
  @click.option(
130
- "--auto-approve", is_flag=True, help="Skip credential confirmation prompts"
131
- )
132
- @click.option(
133
- "--region",
134
- help="GCP region for deployment (default: us-central1)",
135
- default="us-central1",
136
- )
137
- @click.option(
138
- "--skip-checks",
212
+ "--in-folder",
213
+ "-if",
139
214
  is_flag=True,
140
- help="Skip verification checks for GCP and Vertex AI",
215
+ help="Template files directly into the current directory instead of creating a new project directory",
141
216
  default=False,
142
217
  )
218
+ @shared_template_options
143
219
  @handle_cli_error
144
220
  def create(
145
221
  ctx: click.Context,
@@ -155,30 +231,15 @@ def create(
155
231
  auto_approve: bool,
156
232
  region: str,
157
233
  skip_checks: bool,
234
+ in_folder: bool,
235
+ base_template: str | None = None,
236
+ skip_welcome: bool = False,
158
237
  ) -> None:
159
238
  """Create GCP-based AI agent projects from templates."""
160
239
  try:
161
- # Display welcome banner
162
- if agent and agent.startswith("adk@"):
163
- console.print(
164
- "\n=== Welcome to [link=https://github.com/google/adk-samples]google/adk-samples[/link]! ✨ ===",
165
- style="bold blue",
166
- )
167
- console.print(
168
- "Powered by [link=https://goo.gle/agent-starter-pack]Google Cloud - Agent Starter Pack [/link]\n",
169
- )
170
- console.print(
171
- "This tool will help you create an end-to-end production-ready AI agent in Google Cloud!\n"
172
- )
173
- else:
174
- console.print(
175
- "\n=== Google Cloud Agent Starter Pack :rocket:===",
176
- style="bold blue",
177
- )
178
- console.print("Welcome to the Agent Starter Pack!")
179
- console.print(
180
- "This tool will help you create an end-to-end production-ready AI agent in Google Cloud!\n"
181
- )
240
+ # Display welcome banner (unless skipped)
241
+ if not skip_welcome:
242
+ display_welcome_banner(agent)
182
243
  # Validate project name
183
244
  if len(project_name) > 26:
184
245
  console.print(
@@ -199,14 +260,41 @@ def create(
199
260
  destination_dir = pathlib.Path(output_dir) if output_dir else pathlib.Path.cwd()
200
261
  destination_dir = destination_dir.resolve() # Convert to absolute path
201
262
 
202
- # Check if project would exist in output directory
203
- project_path = destination_dir / project_name
204
- if project_path.exists():
205
- console.print(
206
- f"Error: Project directory '{project_path}' already exists",
207
- style="bold red",
208
- )
209
- return
263
+ if in_folder:
264
+ # For in-folder templating, use the current directory directly
265
+ project_path = destination_dir
266
+ # In-folder mode is permissive - we assume the user wants to enhance their existing repo
267
+
268
+ # Create backup of entire directory before in-folder templating
269
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
270
+ backup_dir = project_path / f".backup_{project_path.name}_{timestamp}"
271
+
272
+ console.print("📦 [blue]Creating backup before modification...[/blue]")
273
+
274
+ try:
275
+ shutil.copytree(
276
+ project_path, backup_dir, ignore=get_standard_ignore_patterns()
277
+ )
278
+ console.print(f"Backup created: [cyan]{backup_dir.name}[/cyan]")
279
+ except Exception as e:
280
+ console.print(
281
+ f"⚠️ [yellow]Warning: Could not create backup: {e}[/yellow]"
282
+ )
283
+ if not auto_approve:
284
+ if not click.confirm("Continue without backup?", default=True):
285
+ console.print("✋ [red]Operation cancelled.[/red]")
286
+ return
287
+
288
+ console.print()
289
+ else:
290
+ # Check if project would exist in output directory
291
+ project_path = destination_dir / project_name
292
+ if project_path.exists():
293
+ console.print(
294
+ f"Error: Project directory '{project_path}' already exists",
295
+ style="bold red",
296
+ )
297
+ return
210
298
 
211
299
  # Agent selection - handle remote templates
212
300
  selected_agent = None
@@ -226,7 +314,11 @@ def create(
226
314
  temp_dir = tempfile.mkdtemp(prefix="asp_local_template_")
227
315
  temp_dir_to_clean = temp_dir
228
316
  template_source_path = pathlib.Path(temp_dir) / local_path.name
229
- shutil.copytree(local_path, template_source_path)
317
+ shutil.copytree(
318
+ local_path,
319
+ template_source_path,
320
+ ignore=get_standard_ignore_patterns(),
321
+ )
230
322
 
231
323
  selected_agent = f"local_{template_source_path.name}"
232
324
  console.print(f"Using local template: {local_path}")
@@ -303,32 +395,25 @@ def create(
303
395
 
304
396
  # Load template configuration based on whether it's remote or local
305
397
  if template_source_path:
306
- # Load remote template config
307
- source_config = load_remote_template_config(template_source_path)
398
+ # Prepare CLI overrides for remote template config
399
+ cli_overrides = {}
400
+ if base_template:
401
+ cli_overrides["base_template"] = base_template
308
402
 
309
- # Check if remote template has the required structure
310
- template_config_path = (
311
- template_source_path / ".template" / "templateconfig.yaml"
403
+ # Load remote template config
404
+ source_config = load_remote_template_config(
405
+ template_source_path, cli_overrides
312
406
  )
313
407
 
314
- if not template_config_path.exists():
315
- console.print(
316
- "Error: Template is invalid or improperly structured.",
317
- style="bold red",
318
- )
319
- console.print(
320
- "Templates must contain a '.template/templateconfig.yaml' file.\n\n"
321
- "Expected structure:\n"
322
- " your-template/\n"
323
- " └── .template/\n"
324
- " ├── templateconfig.yaml\n"
325
- " └── [other template files...]",
326
- style="yellow",
327
- )
328
- return
408
+ # Remote templates now work even without pyproject.toml thanks to defaults
409
+ if debug and source_config:
410
+ logging.debug(f"Final remote template config: {source_config}")
329
411
 
330
412
  # Load base template config for inheritance
331
413
  base_template_name = get_base_template_name(source_config)
414
+ if debug:
415
+ logging.debug(f"Using base template: {base_template_name}")
416
+
332
417
  base_template_path = (
333
418
  pathlib.Path(__file__).parent.parent.parent.parent
334
419
  / "agents"
@@ -539,6 +624,7 @@ def create(
539
624
  output_dir=destination_dir,
540
625
  remote_template_path=template_source_path,
541
626
  remote_config=config if template_source_path else None,
627
+ in_folder=in_folder,
542
628
  )
543
629
 
544
630
  # Replace region in all files if a different region was specified
@@ -557,8 +643,12 @@ def create(
557
643
  f"Failed to clean up temporary directory {temp_dir_to_clean}: {e}"
558
644
  )
559
645
 
560
- project_path = destination_dir / project_name
561
- cd_path = project_path if output_dir else project_name
646
+ if not in_folder:
647
+ project_path = destination_dir / project_name
648
+ cd_path = project_path if output_dir else project_name
649
+ else:
650
+ project_path = destination_dir
651
+ cd_path = "."
562
652
 
563
653
  if include_data_ingestion:
564
654
  project_id = creds_info.get("project", "")
@@ -665,19 +755,31 @@ def display_adk_samples_selection() -> str:
665
755
  adk_agents = {}
666
756
  agent_count = 1
667
757
 
668
- # Search for templateconfig.yaml files to identify agents
669
- for config_path in sorted(repo_path.glob("**/templateconfig.yaml")):
758
+ # Search for pyproject.toml files to identify agents
759
+ for config_path in sorted(repo_path.glob("**/pyproject.toml")):
670
760
  try:
671
- import yaml
761
+ with open(config_path, "rb") as f:
762
+ pyproject_data = tomllib.load(f)
763
+
764
+ config = pyproject_data.get("tool", {}).get("agent-starter-pack", {})
765
+
766
+ # Skip pyproject.toml files that don't have agent-starter-pack config
767
+ if not config:
768
+ continue
672
769
 
673
- with open(config_path, encoding="utf-8") as f:
674
- config = yaml.safe_load(f)
770
+ template_root = config_path.parent
675
771
 
676
- agent_name = config.get("name", config_path.parent.parent.name)
677
- description = config.get("description", "No description available")
772
+ # Use fallbacks to [project] section if needed
773
+ project_info = pyproject_data.get("project", {})
774
+ agent_name = (
775
+ config.get("name") or project_info.get("name") or template_root.name
776
+ )
777
+ description = (
778
+ config.get("description") or project_info.get("description") or ""
779
+ )
678
780
 
679
781
  # Get the relative path from repo root
680
- relative_path = config_path.parent.parent.relative_to(repo_path)
782
+ relative_path = template_root.relative_to(repo_path)
681
783
 
682
784
  # For adk-samples, use just the agent name for the spec
683
785
  # This handles cases like python/agents/gemini-fullstack -> gemini-fullstack