agent-starter-pack 0.10.0__py3-none-any.whl → 0.11.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. {agent_starter_pack-0.10.0.dist-info → agent_starter_pack-0.11.0.dist-info}/METADATA +2 -2
  2. {agent_starter_pack-0.10.0.dist-info → agent_starter_pack-0.11.0.dist-info}/RECORD +46 -45
  3. agents/adk_gemini_fullstack/.template/templateconfig.yaml +1 -0
  4. agents/crewai_coding_crew/.template/templateconfig.yaml +2 -2
  5. agents/crewai_coding_crew/tests/integration/test_agent.py +1 -1
  6. agents/langgraph_base_react/.template/templateconfig.yaml +1 -1
  7. agents/langgraph_base_react/tests/integration/test_agent.py +1 -1
  8. agents/live_api/tests/unit/test_server.py +2 -1
  9. src/base_template/deployment/terraform/dev/iam.tf +12 -11
  10. src/base_template/deployment/terraform/dev/variables.tf +2 -7
  11. src/base_template/deployment/terraform/github.tf +14 -0
  12. src/base_template/deployment/terraform/iam.tf +10 -7
  13. src/base_template/deployment/terraform/service_accounts.tf +4 -5
  14. src/base_template/deployment/terraform/variables.tf +2 -7
  15. src/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}build_triggers.tf{% else %}unused_build_triggers.tf{% endif %} +4 -2
  16. src/base_template/pyproject.toml +2 -2
  17. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +1 -0
  18. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +1 -0
  19. src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +1 -0
  20. src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +1 -0
  21. src/cli/commands/create.py +232 -101
  22. src/cli/commands/enhance.py +248 -0
  23. src/cli/commands/list.py +23 -11
  24. src/cli/commands/setup_cicd.py +1 -1
  25. src/cli/main.py +2 -0
  26. src/cli/utils/logging.py +40 -0
  27. src/cli/utils/remote_template.py +55 -16
  28. src/cli/utils/template.py +212 -94
  29. src/deployment_targets/agent_engine/app/agent_engine_app.py +8 -0
  30. src/deployment_targets/cloud_run/app/server.py +1 -1
  31. src/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +1 -1
  32. src/deployment_targets/cloud_run/deployment/terraform/service.tf +2 -2
  33. src/resources/locks/uv-adk_base-agent_engine.lock +312 -312
  34. src/resources/locks/uv-adk_base-cloud_run.lock +403 -404
  35. src/resources/locks/uv-adk_gemini_fullstack-agent_engine.lock +312 -312
  36. src/resources/locks/uv-adk_gemini_fullstack-cloud_run.lock +403 -404
  37. src/resources/locks/uv-agentic_rag-agent_engine.lock +371 -371
  38. src/resources/locks/uv-agentic_rag-cloud_run.lock +477 -478
  39. src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +661 -591
  40. src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +868 -760
  41. src/resources/locks/uv-langgraph_base_react-agent_engine.lock +496 -446
  42. src/resources/locks/uv-langgraph_base_react-cloud_run.lock +639 -565
  43. src/resources/locks/uv-live_api-cloud_run.lock +584 -510
  44. {agent_starter_pack-0.10.0.dist-info → agent_starter_pack-0.11.0.dist-info}/WHEEL +0 -0
  45. {agent_starter_pack-0.10.0.dist-info → agent_starter_pack-0.11.0.dist-info}/entry_points.txt +0 -0
  46. {agent_starter_pack-0.10.0.dist-info → agent_starter_pack-0.11.0.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}")
@@ -277,37 +369,51 @@ def create(
277
369
  )
278
370
  final_agent = display_agent_selection(deployment_target)
279
371
 
372
+ # If browse functionality returned a remote agent spec, process it like CLI input
373
+ if final_agent and final_agent.startswith("adk@"):
374
+ # Set agent to the returned spec for remote processing
375
+ agent = final_agent
376
+
377
+ # Process the remote template spec just like CLI input
378
+ remote_spec = parse_agent_spec(agent)
379
+ if remote_spec:
380
+ if remote_spec.is_adk_samples:
381
+ console.print(
382
+ f"> Fetching template: {remote_spec.template_path}",
383
+ style="bold blue",
384
+ )
385
+ else:
386
+ console.print(f"Fetching remote template: {agent}")
387
+ template_source_path, temp_dir_path = fetch_remote_template(
388
+ remote_spec
389
+ )
390
+ temp_dir_to_clean = str(temp_dir_path)
391
+ final_agent = f"remote_{hash(agent)}" # Generate unique name for remote template
392
+
280
393
  if debug:
281
394
  logging.debug(f"Selected agent: {final_agent}")
282
395
 
283
396
  # Load template configuration based on whether it's remote or local
284
397
  if template_source_path:
285
- # Load remote template config
286
- 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
287
402
 
288
- # Check if remote template has the required structure
289
- template_config_path = (
290
- template_source_path / ".template" / "templateconfig.yaml"
403
+ # Load remote template config
404
+ source_config = load_remote_template_config(
405
+ template_source_path, cli_overrides
291
406
  )
292
407
 
293
- if not template_config_path.exists():
294
- console.print(
295
- "Error: Template is invalid or improperly structured.",
296
- style="bold red",
297
- )
298
- console.print(
299
- "Templates must contain a '.template/templateconfig.yaml' file.\n\n"
300
- "Expected structure:\n"
301
- " your-template/\n"
302
- " └── .template/\n"
303
- " ├── templateconfig.yaml\n"
304
- " └── [other template files...]",
305
- style="yellow",
306
- )
307
- 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}")
308
411
 
309
412
  # Load base template config for inheritance
310
413
  base_template_name = get_base_template_name(source_config)
414
+ if debug:
415
+ logging.debug(f"Using base template: {base_template_name}")
416
+
311
417
  base_template_path = (
312
418
  pathlib.Path(__file__).parent.parent.parent.parent
313
419
  / "agents"
@@ -518,6 +624,7 @@ def create(
518
624
  output_dir=destination_dir,
519
625
  remote_template_path=template_source_path,
520
626
  remote_config=config if template_source_path else None,
627
+ in_folder=in_folder,
521
628
  )
522
629
 
523
630
  # Replace region in all files if a different region was specified
@@ -536,8 +643,12 @@ def create(
536
643
  f"Failed to clean up temporary directory {temp_dir_to_clean}: {e}"
537
644
  )
538
645
 
539
- project_path = destination_dir / project_name
540
- 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 = "."
541
652
 
542
653
  if include_data_ingestion:
543
654
  project_id = creds_info.get("project", "")
@@ -644,25 +755,45 @@ def display_adk_samples_selection() -> str:
644
755
  adk_agents = {}
645
756
  agent_count = 1
646
757
 
647
- # Search for templateconfig.yaml files to identify agents
648
- 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")):
649
760
  try:
650
- 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
651
769
 
652
- with open(config_path, encoding="utf-8") as f:
653
- config = yaml.safe_load(f)
770
+ template_root = config_path.parent
654
771
 
655
- agent_name = config.get("name", config_path.parent.parent.name)
656
- 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
+ )
657
780
 
658
781
  # Get the relative path from repo root
659
- relative_path = config_path.parent.parent.relative_to(repo_path)
782
+ relative_path = template_root.relative_to(repo_path)
783
+
784
+ # For adk-samples, use just the agent name for the spec
785
+ # This handles cases like python/agents/gemini-fullstack -> gemini-fullstack
786
+ agent_spec_name = (
787
+ relative_path.name
788
+ if relative_path != relative_path.parent
789
+ else str(relative_path)
790
+ )
660
791
 
661
792
  adk_agents[agent_count] = {
662
793
  "name": agent_name,
663
794
  "description": description,
664
795
  "path": str(relative_path),
665
- "spec": f"adk@{relative_path}",
796
+ "spec": f"adk@{agent_spec_name}",
666
797
  }
667
798
  agent_count += 1
668
799