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
@@ -0,0 +1,248 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import pathlib
16
+
17
+ import click
18
+ from rich.console import Console
19
+
20
+ from ..utils.logging import display_welcome_banner, handle_cli_error
21
+ from .create import (
22
+ create,
23
+ get_available_base_templates,
24
+ shared_template_options,
25
+ validate_base_template,
26
+ )
27
+
28
+ console = Console()
29
+
30
+
31
+ @click.command()
32
+ @click.pass_context
33
+ @click.argument(
34
+ "template_path",
35
+ type=click.Path(path_type=pathlib.Path),
36
+ default=".",
37
+ required=False,
38
+ )
39
+ @click.option(
40
+ "--name",
41
+ "-n",
42
+ help="Project name for templating (defaults to current directory name)",
43
+ )
44
+ @click.option(
45
+ "--base-template",
46
+ help="Base template to inherit from (e.g., adk_base, langgraph_base_react, agentic_rag)",
47
+ )
48
+ @shared_template_options
49
+ @handle_cli_error
50
+ def enhance(
51
+ ctx: click.Context,
52
+ template_path: pathlib.Path,
53
+ name: str | None,
54
+ deployment_target: str | None,
55
+ cicd_runner: str | None,
56
+ include_data_ingestion: bool,
57
+ datastore: str | None,
58
+ session_type: str | None,
59
+ debug: bool,
60
+ auto_approve: bool,
61
+ region: str,
62
+ skip_checks: bool,
63
+ base_template: str | None,
64
+ ) -> None:
65
+ """Enhance your existing project with AI agent capabilities.
66
+
67
+ This command is an alias for 'create' with --in-folder mode enabled, designed to
68
+ add agent-starter-pack features to your existing project in-place rather than
69
+ creating a new project directory.
70
+
71
+ For best compatibility, your project should follow the agent-starter-pack structure
72
+ with agent code organized in an /app folder (containing agent.py, etc.).
73
+
74
+ TEMPLATE_PATH can be:
75
+ - A local directory path (e.g., . for current directory)
76
+ - An agent name (e.g., adk_base)
77
+ - A remote template (e.g., adk@data-science)
78
+
79
+ The command will validate your project structure and provide guidance if needed.
80
+ """
81
+
82
+ # Display welcome banner for enhance command
83
+ display_welcome_banner(enhance_mode=True)
84
+
85
+ # Validate base template if provided
86
+ if base_template and not validate_base_template(base_template):
87
+ available_templates = get_available_base_templates()
88
+ console.print(
89
+ f"Error: Base template '{base_template}' not found.", style="bold red"
90
+ )
91
+ console.print(
92
+ f"Available base templates: {', '.join(available_templates)}",
93
+ style="yellow",
94
+ )
95
+ return
96
+
97
+ # Determine project name
98
+ if name:
99
+ project_name = name
100
+ else:
101
+ # Use current directory name as default
102
+ current_dir = pathlib.Path.cwd()
103
+ project_name = current_dir.name
104
+ console.print(
105
+ f"Using current directory name as project name: {project_name}", style="dim"
106
+ )
107
+
108
+ # Show confirmation prompt for enhancement unless auto-approved
109
+ if not auto_approve:
110
+ current_dir = pathlib.Path.cwd()
111
+ console.print()
112
+ console.print(
113
+ "🚀 [blue]Ready to enhance your project with deployment capabilities[/blue]"
114
+ )
115
+ console.print(f"📂 {current_dir}")
116
+ console.print()
117
+ console.print("[bold]What will happen:[/bold]")
118
+ console.print("• New template files will be added to this directory")
119
+ console.print("• Your existing files will be preserved")
120
+ console.print("• A backup will be created before any changes")
121
+ console.print()
122
+
123
+ if not click.confirm(
124
+ f"Continue with enhancement? {click.style('[Y/n]: ', fg='blue', bold=True)}",
125
+ default=True,
126
+ show_default=False,
127
+ ):
128
+ console.print("✋ [yellow]Enhancement cancelled.[/yellow]")
129
+ return
130
+ console.print()
131
+
132
+ # Determine agent specification based on template_path
133
+ if template_path == pathlib.Path("."):
134
+ # Current directory - use local@ syntax
135
+ agent_spec = "local@."
136
+ elif template_path.is_dir():
137
+ # Other local directory
138
+ agent_spec = f"local@{template_path.resolve()}"
139
+ else:
140
+ # Assume it's an agent name or remote spec
141
+ agent_spec = str(template_path)
142
+
143
+ # Show base template inheritance info early for local projects
144
+ if agent_spec.startswith("local@"):
145
+ from ..utils.remote_template import (
146
+ get_base_template_name,
147
+ load_remote_template_config,
148
+ )
149
+
150
+ # Prepare CLI overrides for base template
151
+ cli_overrides = {}
152
+ if base_template:
153
+ cli_overrides["base_template"] = base_template
154
+
155
+ # Load config from current directory for inheritance info
156
+ current_dir = pathlib.Path.cwd()
157
+ source_config = load_remote_template_config(current_dir, cli_overrides)
158
+ base_template_name = get_base_template_name(source_config)
159
+
160
+ console.print()
161
+ console.print(
162
+ f"Template inherits from base: [cyan][link=https://github.com/GoogleCloudPlatform/agent-starter-pack/tree/main/agents/{base_template_name}]{base_template_name}[/link][/cyan]"
163
+ )
164
+
165
+ # Show available alternatives and guidance
166
+ available_bases = get_available_base_templates()
167
+ if len(available_bases) > 1:
168
+ other_bases = [b for b in available_bases if b != base_template_name]
169
+ if other_bases:
170
+ console.print(
171
+ f"[dim]💡 To use a different base template (e.g., {', '.join(other_bases[:2])}), use:[/dim]"
172
+ )
173
+ console.print(
174
+ "[dim] asp enhance . --base-template langgraph_base_react[/dim]"
175
+ )
176
+ console.print()
177
+
178
+ # Validate project structure when using current directory template
179
+ if template_path == pathlib.Path("."):
180
+ current_dir = pathlib.Path.cwd()
181
+ app_folder = current_dir / "app"
182
+
183
+ if not app_folder.exists() or not app_folder.is_dir():
184
+ console.print()
185
+ console.print(
186
+ "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
187
+ )
188
+ console.print("⚠️ [bold yellow]PROJECT STRUCTURE WARNING[/bold yellow] ⚠️")
189
+ console.print(
190
+ "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
191
+ )
192
+ console.print()
193
+ console.print(
194
+ "📁 [bold]Expected Structure:[/bold] [cyan]/app[/cyan] folder containing your agent code"
195
+ )
196
+ console.print(f"📍 [bold]Current Directory:[/bold] {current_dir}")
197
+ console.print("❌ [bold red]Missing:[/bold red] /app folder")
198
+ console.print()
199
+ console.print(
200
+ "[dim]The enhance command can still proceed, but for best compatibility"
201
+ " your agent code should be organized in an /app folder structure.[/dim]"
202
+ )
203
+ console.print()
204
+
205
+ # Ask for confirmation after showing the structure warning
206
+ console.print(
207
+ "💡 [dim]Consider creating an /app folder for better compatibility.[/dim]"
208
+ )
209
+ console.print()
210
+
211
+ if not auto_approve:
212
+ if not click.confirm(
213
+ "Continue with enhancement despite missing /app folder?",
214
+ default=True,
215
+ ):
216
+ console.print("✋ [yellow]Enhancement cancelled.[/yellow]")
217
+ return
218
+ else:
219
+ # Check for common agent files
220
+ agent_py = app_folder / "agent.py"
221
+ if agent_py.exists():
222
+ console.print(
223
+ "Detected existing agent structure with [cyan]/app/agent.py[/cyan]"
224
+ )
225
+ else:
226
+ console.print(
227
+ "ℹ️ [blue]Found /app folder[/blue] - ensure your agent code is properly organized within it"
228
+ )
229
+
230
+ # Call the create command with in-folder mode enabled
231
+ ctx.invoke(
232
+ create,
233
+ project_name=project_name,
234
+ agent=agent_spec,
235
+ deployment_target=deployment_target,
236
+ cicd_runner=cicd_runner,
237
+ include_data_ingestion=include_data_ingestion,
238
+ datastore=datastore,
239
+ session_type=session_type,
240
+ debug=debug,
241
+ output_dir=None, # Use current directory
242
+ auto_approve=auto_approve,
243
+ region=region,
244
+ skip_checks=skip_checks,
245
+ in_folder=True, # Always use in-folder mode for enhance
246
+ base_template=base_template,
247
+ skip_welcome=True, # Skip welcome message since enhance shows its own
248
+ )
src/cli/commands/list.py CHANGED
@@ -16,7 +16,7 @@ import logging
16
16
  import pathlib
17
17
 
18
18
  import click
19
- import yaml
19
+ import tomllib
20
20
  from rich.console import Console
21
21
  from rich.table import Table
22
22
 
@@ -42,25 +42,37 @@ def display_agents_from_path(base_path: pathlib.Path, source_name: str) -> None:
42
42
  return
43
43
 
44
44
  found_agents = False
45
- # Search for templateconfig.yaml files to identify agents
46
- for config_path in sorted(base_path.glob("**/templateconfig.yaml")):
45
+ # Search for pyproject.toml files to identify agents (explicit opt-in)
46
+ for config_path in sorted(base_path.glob("**/pyproject.toml")):
47
47
  try:
48
- with open(config_path, encoding="utf-8") as f:
49
- config = yaml.safe_load(f)
48
+ with open(config_path, "rb") as f:
49
+ pyproject_data = tomllib.load(f)
50
50
 
51
- agent_name = config.get("name", config_path.parent.parent.name)
52
- description = config.get("description", "No description.")
51
+ config = pyproject_data.get("tool", {}).get("agent-starter-pack", {})
52
+
53
+ # Skip pyproject.toml files that don't have agent-starter-pack config
54
+ if not config:
55
+ continue
56
+
57
+ template_root = config_path.parent
58
+
59
+ # Use fallbacks to [project] section if needed
60
+ project_info = pyproject_data.get("project", {})
61
+ agent_name = (
62
+ config.get("name") or project_info.get("name") or template_root.name
63
+ )
64
+ description = (
65
+ config.get("description") or project_info.get("description") or ""
66
+ )
53
67
 
54
68
  # Display the agent's path relative to the scanned directory
55
- relative_path = config_path.parent.parent.relative_to(base_path)
69
+ relative_path = template_root.relative_to(base_path)
56
70
 
57
71
  table.add_row(agent_name, f"/{relative_path}", description)
58
72
  found_agents = True
59
73
 
60
74
  except Exception as e:
61
- logging.warning(
62
- f"Could not load agent from {config_path.parent.parent}: {e}"
63
- )
75
+ logging.warning(f"Could not load agent from {config_path.parent}: {e}")
64
76
 
65
77
  if not found_agents:
66
78
  console.print(f"No agents found in {source_name}", style="yellow")
@@ -373,7 +373,7 @@ def create_or_update_secret(secret_id: str, secret_value: str, project_id: str)
373
373
  Raises:
374
374
  subprocess.CalledProcessError: If secret creation/update fails
375
375
  """
376
- with tempfile.NamedTemporaryFile(mode="w") as temp_file:
376
+ with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8") as temp_file:
377
377
  temp_file.write(secret_value)
378
378
  temp_file.flush()
379
379
 
src/cli/main.py CHANGED
@@ -18,6 +18,7 @@ import click
18
18
  from rich.console import Console
19
19
 
20
20
  from .commands.create import create
21
+ from .commands.enhance import enhance
21
22
  from .commands.list import list_agents
22
23
  from .commands.setup_cicd import setup_cicd
23
24
  from .utils import display_update_message
@@ -53,6 +54,7 @@ def cli() -> None:
53
54
 
54
55
  # Register commands
55
56
  cli.add_command(create)
57
+ cli.add_command(enhance)
56
58
  cli.add_command(setup_cicd)
57
59
  cli.add_command(list_agents, name="list")
58
60
 
src/cli/utils/logging.py CHANGED
@@ -24,6 +24,46 @@ console = Console()
24
24
  F = TypeVar("F", bound=Callable[..., Any])
25
25
 
26
26
 
27
+ def display_welcome_banner(
28
+ agent: str | None = None, enhance_mode: bool = False
29
+ ) -> None:
30
+ """Display the Agent Starter Pack welcome banner.
31
+
32
+ Args:
33
+ agent: Optional agent specification to customize the welcome message
34
+ enhance_mode: Whether this is for enhancement mode
35
+ """
36
+ if enhance_mode:
37
+ console.print(
38
+ "\n=== Google Cloud Agent Starter Pack 🚀===",
39
+ style="bold blue",
40
+ )
41
+ console.print(
42
+ "Enhancing your existing project with production-ready agent capabilities!\n",
43
+ style="green",
44
+ )
45
+ elif agent and agent.startswith("adk@"):
46
+ console.print(
47
+ "\n=== Welcome to [link=https://github.com/google/adk-samples]google/adk-samples[/link]! ✨ ===",
48
+ style="bold blue",
49
+ )
50
+ console.print(
51
+ "Powered by [link=https://goo.gle/agent-starter-pack]Google Cloud - Agent Starter Pack [/link]\n",
52
+ )
53
+ console.print(
54
+ "This tool will help you create an end-to-end production-ready AI agent in Google Cloud!\n"
55
+ )
56
+ else:
57
+ console.print(
58
+ "\n=== Google Cloud Agent Starter Pack 🚀===",
59
+ style="bold blue",
60
+ )
61
+ console.print("Welcome to the Agent Starter Pack!")
62
+ console.print(
63
+ "This tool will help you create an end-to-end production-ready AI agent in Google Cloud!\n"
64
+ )
65
+
66
+
27
67
  def handle_cli_error(f: F) -> F:
28
68
  """Decorator to handle CLI errors gracefully.
29
69
 
@@ -22,7 +22,7 @@ import tempfile
22
22
  from dataclasses import dataclass
23
23
  from typing import Any
24
24
 
25
- import yaml
25
+ import tomllib
26
26
  from jinja2 import Environment
27
27
 
28
28
 
@@ -183,27 +183,66 @@ def fetch_remote_template(
183
183
  ) from e
184
184
 
185
185
 
186
- def load_remote_template_config(template_dir: pathlib.Path) -> dict[str, Any]:
187
- """Load template configuration from remote template.
186
+ def load_remote_template_config(
187
+ template_dir: pathlib.Path, cli_overrides: dict[str, Any] | None = None
188
+ ) -> dict[str, Any]:
189
+ """Load template configuration from remote template's pyproject.toml with CLI overrides.
190
+
191
+ Loads configuration from [tool.agent-starter-pack] section with fallbacks
192
+ to [project] section for name and description if not specified. CLI overrides
193
+ take precedence over all other sources.
188
194
 
189
195
  Args:
190
196
  template_dir: Path to template directory
197
+ cli_overrides: Configuration overrides from CLI (takes highest precedence)
191
198
 
192
199
  Returns:
193
- Template configuration dictionary
200
+ Template configuration dictionary with merged sources
194
201
  """
195
- config_path = template_dir / ".template" / "templateconfig.yaml"
196
-
197
- if not config_path.exists():
198
- return {}
199
-
200
- try:
201
- with open(config_path, encoding="utf-8") as f:
202
- config = yaml.safe_load(f)
203
- return config if config else {}
204
- except Exception as e:
205
- logging.error(f"Error loading remote template config: {e}")
206
- return {}
202
+ config = {}
203
+
204
+ # Start with defaults
205
+ defaults = {
206
+ "base_template": "adk_base",
207
+ "name": template_dir.name,
208
+ "description": "",
209
+ }
210
+ config.update(defaults)
211
+
212
+ # Load from pyproject.toml if it exists
213
+ pyproject_path = template_dir / "pyproject.toml"
214
+ if pyproject_path.exists():
215
+ try:
216
+ with open(pyproject_path, "rb") as f:
217
+ pyproject_data = tomllib.load(f)
218
+
219
+ # Extract the agent-starter-pack configuration
220
+ toml_config = pyproject_data.get("tool", {}).get("agent-starter-pack", {})
221
+
222
+ # Fallback to [project] fields if not specified in agent-starter-pack section
223
+ project_info = pyproject_data.get("project", {})
224
+
225
+ # Apply pyproject.toml configuration (overrides defaults)
226
+ if toml_config:
227
+ config.update(toml_config)
228
+
229
+ # Apply [project] fallbacks if not already set
230
+ if "name" not in toml_config and "name" in project_info:
231
+ config["name"] = project_info["name"]
232
+
233
+ if "description" not in toml_config and "description" in project_info:
234
+ config["description"] = project_info["description"]
235
+
236
+ logging.debug(f"Loaded template config from {pyproject_path}")
237
+ except Exception as e:
238
+ logging.error(f"Error loading pyproject.toml config: {e}")
239
+
240
+ # Apply CLI overrides (highest precedence) using deep merge
241
+ if cli_overrides:
242
+ config = merge_template_configs(config, cli_overrides)
243
+ logging.debug(f"Applied CLI overrides: {cli_overrides}")
244
+
245
+ return config
207
246
 
208
247
 
209
248
  def get_base_template_name(config: dict[str, Any]) -> str: