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.
- {agent_starter_pack-0.10.0.dist-info → agent_starter_pack-0.11.0.dist-info}/METADATA +2 -2
- {agent_starter_pack-0.10.0.dist-info → agent_starter_pack-0.11.0.dist-info}/RECORD +46 -45
- agents/adk_gemini_fullstack/.template/templateconfig.yaml +1 -0
- 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
- agents/live_api/tests/unit/test_server.py +2 -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 +232 -101
- src/cli/commands/enhance.py +248 -0
- src/cli/commands/list.py +23 -11
- src/cli/commands/setup_cicd.py +1 -1
- 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.0.dist-info → agent_starter_pack-0.11.0.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.10.0.dist-info → agent_starter_pack-0.11.0.dist-info}/entry_points.txt +0 -0
- {agent_starter_pack-0.10.0.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}")
|
@@ -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
|
-
#
|
286
|
-
|
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
|
-
#
|
289
|
-
|
290
|
-
template_source_path
|
403
|
+
# Load remote template config
|
404
|
+
source_config = load_remote_template_config(
|
405
|
+
template_source_path, cli_overrides
|
291
406
|
)
|
292
407
|
|
293
|
-
|
294
|
-
|
295
|
-
|
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
|
-
|
540
|
-
|
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
|
648
|
-
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")):
|
649
760
|
try:
|
650
|
-
|
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
|
-
|
653
|
-
config = yaml.safe_load(f)
|
770
|
+
template_root = config_path.parent
|
654
771
|
|
655
|
-
|
656
|
-
|
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 =
|
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@{
|
796
|
+
"spec": f"adk@{agent_spec_name}",
|
666
797
|
}
|
667
798
|
agent_count += 1
|
668
799
|
|