agent-starter-pack 0.10.1__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.
- {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.0.dist-info}/METADATA +2 -2
- {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.0.dist-info}/RECORD +43 -42
- agents/crewai_coding_crew/.template/templateconfig.yaml +2 -2
- agents/crewai_coding_crew/tests/integration/test_agent.py +1 -1
- agents/langgraph_base_react/.template/templateconfig.yaml +1 -1
- agents/langgraph_base_react/tests/integration/test_agent.py +1 -1
- src/base_template/deployment/terraform/dev/iam.tf +12 -11
- src/base_template/deployment/terraform/dev/variables.tf +2 -7
- src/base_template/deployment/terraform/github.tf +14 -0
- src/base_template/deployment/terraform/iam.tf +10 -7
- src/base_template/deployment/terraform/service_accounts.tf +4 -5
- src/base_template/deployment/terraform/variables.tf +2 -7
- src/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}build_triggers.tf{% else %}unused_build_triggers.tf{% endif %} +4 -2
- src/base_template/pyproject.toml +2 -2
- src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +1 -0
- src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +1 -0
- src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +1 -0
- src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +1 -0
- src/cli/commands/create.py +202 -100
- src/cli/commands/enhance.py +248 -0
- src/cli/commands/list.py +23 -11
- src/cli/main.py +2 -0
- src/cli/utils/logging.py +40 -0
- src/cli/utils/remote_template.py +55 -16
- src/cli/utils/template.py +212 -94
- src/deployment_targets/agent_engine/app/agent_engine_app.py +8 -0
- src/deployment_targets/cloud_run/app/server.py +1 -1
- src/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +1 -1
- src/deployment_targets/cloud_run/deployment/terraform/service.tf +2 -2
- src/resources/locks/uv-adk_base-agent_engine.lock +312 -312
- src/resources/locks/uv-adk_base-cloud_run.lock +403 -404
- src/resources/locks/uv-adk_gemini_fullstack-agent_engine.lock +312 -312
- src/resources/locks/uv-adk_gemini_fullstack-cloud_run.lock +403 -404
- src/resources/locks/uv-agentic_rag-agent_engine.lock +371 -371
- src/resources/locks/uv-agentic_rag-cloud_run.lock +477 -478
- src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +661 -591
- src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +868 -760
- src/resources/locks/uv-langgraph_base_react-agent_engine.lock +496 -446
- src/resources/locks/uv-langgraph_base_react-cloud_run.lock +639 -565
- src/resources/locks/uv-live_api-cloud_run.lock +584 -510
- {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.0.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.0.dist-info}/entry_points.txt +0 -0
- {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.0.dist-info}/licenses/LICENSE +0 -0
src/cli/commands/create.py
CHANGED
@@ -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
|
-
"--
|
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="
|
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
|
163
|
-
|
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
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
)
|
209
|
-
|
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(
|
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
|
-
#
|
307
|
-
|
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
|
-
#
|
310
|
-
|
311
|
-
template_source_path
|
403
|
+
# Load remote template config
|
404
|
+
source_config = load_remote_template_config(
|
405
|
+
template_source_path, cli_overrides
|
312
406
|
)
|
313
407
|
|
314
|
-
|
315
|
-
|
316
|
-
|
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
|
-
|
561
|
-
|
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
|
669
|
-
for config_path in sorted(repo_path.glob("**/
|
758
|
+
# Search for pyproject.toml files to identify agents
|
759
|
+
for config_path in sorted(repo_path.glob("**/pyproject.toml")):
|
670
760
|
try:
|
671
|
-
|
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
|
-
|
674
|
-
config = yaml.safe_load(f)
|
770
|
+
template_root = config_path.parent
|
675
771
|
|
676
|
-
|
677
|
-
|
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 =
|
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
|
@@ -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
|
+
)
|