agent-starter-pack 0.9.1__py3-none-any.whl → 0.9.2__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.9.1.dist-info → agent_starter_pack-0.9.2.dist-info}/METADATA +4 -6
- {agent_starter_pack-0.9.1.dist-info → agent_starter_pack-0.9.2.dist-info}/RECORD +10 -10
- src/cli/commands/create.py +103 -37
- src/cli/commands/list.py +3 -9
- src/cli/utils/remote_template.py +81 -14
- src/cli/utils/template.py +12 -0
- src/resources/containers/e2e-tests/Dockerfile +39 -17
- {agent_starter_pack-0.9.1.dist-info → agent_starter_pack-0.9.2.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.9.1.dist-info → agent_starter_pack-0.9.2.dist-info}/entry_points.txt +0 -0
- {agent_starter_pack-0.9.1.dist-info → agent_starter_pack-0.9.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: agent-starter-pack
|
3
|
-
Version: 0.9.
|
3
|
+
Version: 0.9.2
|
4
4
|
Summary: CLI to bootstrap production-ready Google Cloud GenAI agent projects from templates.
|
5
5
|
Author-email: Google LLC <agent-starter-pack@google.com>
|
6
6
|
License: Apache-2.0
|
@@ -16,11 +16,9 @@ Provides-Extra: jupyter
|
|
16
16
|
Requires-Dist: ipykernel>=6.29.5; extra == 'jupyter'
|
17
17
|
Requires-Dist: jupyter; extra == 'jupyter'
|
18
18
|
Provides-Extra: lint
|
19
|
-
Requires-Dist: codespell
|
20
|
-
Requires-Dist: mypy
|
21
|
-
Requires-Dist: ruff
|
22
|
-
Requires-Dist: types-pyyaml~=6.0.12.20240917; extra == 'lint'
|
23
|
-
Requires-Dist: types-requests~=2.32.0.20240914; extra == 'lint'
|
19
|
+
Requires-Dist: codespell; extra == 'lint'
|
20
|
+
Requires-Dist: mypy; extra == 'lint'
|
21
|
+
Requires-Dist: ruff; extra == 'lint'
|
24
22
|
Description-Content-Type: text/markdown
|
25
23
|
|
26
24
|
# 🚀 Agent Starter Pack
|
@@ -74,16 +74,16 @@ src/base_template/deployment/terraform/dev/vars/env.tfvars,sha256=LoQMjh1AAMR-MG
|
|
74
74
|
src/base_template/deployment/terraform/vars/env.tfvars,sha256=Nze8q1x2Aj6ZUeWC2hDeZWqNUkLp13DgzA_LFmmOzCo,1216
|
75
75
|
src/base_template/tests/unit/test_dummy.py,sha256=2exfCH8qhkZrLWvK04ZxNTO9MV3fdTbZkJN3uK6zvok,850
|
76
76
|
src/cli/main.py,sha256=fyJKjU1gvRQmuqS-J6sExvqXo0-z7n6Bce8PSbDPa6E,1769
|
77
|
-
src/cli/commands/create.py,sha256=
|
78
|
-
src/cli/commands/list.py,sha256=
|
77
|
+
src/cli/commands/create.py,sha256=rdN_mWiIxwddsbIYVkrQCApK2x9ABQeSXZZoGtcE074,34764
|
78
|
+
src/cli/commands/list.py,sha256=sMMGJiW_IjRUe-Md1bI_8B9wruYJ87JLSEoJfHaypbg,5038
|
79
79
|
src/cli/commands/setup_cicd.py,sha256=1ZvgTD-Z2bZk6pWuinz4IP2G1Eb8H1MibRLPS-mZ6Ng,31721
|
80
80
|
src/cli/utils/__init__.py,sha256=_cTmsXGPqOtK0q8UW5164QTltbJRJFR_Efxq_BRL1-o,1311
|
81
81
|
src/cli/utils/cicd.py,sha256=VSkJTL7OBnXQ6Zbb2gzgw5gLWpwUjfr9XthqTpJ2Oj8,26197
|
82
82
|
src/cli/utils/datastores.py,sha256=gv1V6eDcOEKx4MRNG5C3Y-VfixYq1AzQuaYMLp8QRNo,1058
|
83
83
|
src/cli/utils/gcp.py,sha256=cnuCyN144eiyYc9aJNEK9JnyWN66rdevugoMdDYC1UU,4032
|
84
84
|
src/cli/utils/logging.py,sha256=0lHe4EPi1A8sOx9xkA7gS4UNl0GsIyp2ahydkkuCzLY,1570
|
85
|
-
src/cli/utils/remote_template.py,sha256=
|
86
|
-
src/cli/utils/template.py,sha256=
|
85
|
+
src/cli/utils/remote_template.py,sha256=RYphQS1IZfzzgtgWJOLdgie0S8HtEWjr2wikyI2doTc,10454
|
86
|
+
src/cli/utils/template.py,sha256=ciElaBHEMBLYIzZJLQwNe_rOc0C_HqJfEGJ6z9p1ep4,34320
|
87
87
|
src/cli/utils/version.py,sha256=F4udQmzniPStqWZFIgnv3Qg3l9non4mfy2An-Oveqmc,2916
|
88
88
|
src/data_ingestion/README.md,sha256=LNxSQoJW9JozK-TbyGQLj5L_MGWNwrfLk6V6RmQ2oBQ,4032
|
89
89
|
src/data_ingestion/pyproject.toml,sha256=-1Mf2QB8K70ICQV5UPZDpf-fN3UwEQLVzQyxfakCSTY,445
|
@@ -179,7 +179,7 @@ src/frontends/streamlit/frontend/utils/multimodal_utils.py,sha256=v6YbCkz_YcnEo-
|
|
179
179
|
src/frontends/streamlit/frontend/utils/stream_handler.py,sha256=-XVRfLH6ck1KP6NS4vrhcvPyZe6z3NLHliEg17s9jjo,12551
|
180
180
|
src/frontends/streamlit/frontend/utils/title_summary.py,sha256=B0cadS_KPW-tsbABauI4J681aqjEtuKFDa25e9R1WKc,3030
|
181
181
|
src/resources/containers/data_processing/Dockerfile,sha256=VoB9d5yZiiWnqRfWrIq0gGNMzZg-eVy733OgP72ZgO0,950
|
182
|
-
src/resources/containers/e2e-tests/Dockerfile,sha256=
|
182
|
+
src/resources/containers/e2e-tests/Dockerfile,sha256=yA3HxeX0HNpl8B4oEQUICCVZDCXdRn2Igmii4jt-r7k,1895
|
183
183
|
src/resources/docs/adk-cheatsheet.md,sha256=KTCdM7hOvsv1-GgwEBMMU6kLk3WHQ69RAs6VWltNaDA,58722
|
184
184
|
src/resources/idx/idx-template.json,sha256=07OQZCPp45Iqor2O7Tm1e1Gmsdd-AmmUofSvA7y-oRs,793
|
185
185
|
src/resources/idx/idx-template.nix,sha256=sesHGev_PYtVDg0J5tHkg0OO7IR1Bz2iAtl_if3Ar3M,892
|
@@ -201,8 +201,8 @@ src/resources/setup_cicd/providers.tf,sha256=Km4z6IJt7x7PLaa0kyZbBrO2m3lpuIJZFD5
|
|
201
201
|
src/utils/generate_locks.py,sha256=6V1B8V2BEuevWnXUsxZVTrLjXwFRII8UfsIGrQqZxVs,4320
|
202
202
|
src/utils/lock_utils.py,sha256=IFOMUWtb-ypm2Y8w8J5y2oI_-MaPuwPF_JOAAlnNudA,2275
|
203
203
|
src/utils/watch_and_rebuild.py,sha256=vP4yIiA7E_lj5sfQdJUl8TXas6V7msDg8XWUutAC05Q,6679
|
204
|
-
agent_starter_pack-0.9.
|
205
|
-
agent_starter_pack-0.9.
|
206
|
-
agent_starter_pack-0.9.
|
207
|
-
agent_starter_pack-0.9.
|
208
|
-
agent_starter_pack-0.9.
|
204
|
+
agent_starter_pack-0.9.2.dist-info/METADATA,sha256=bC09qQ-oHxiJGHYA-3aa1AaSE42t6IIa_Y2BE3Vd33o,11138
|
205
|
+
agent_starter_pack-0.9.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
206
|
+
agent_starter_pack-0.9.2.dist-info/entry_points.txt,sha256=U7uCxR7YulIhZ0L8R8Hui0Bsy6J7oyESBeDYJYMrQjA,56
|
207
|
+
agent_starter_pack-0.9.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
208
|
+
agent_starter_pack-0.9.2.dist-info/RECORD,,
|
src/cli/commands/create.py
CHANGED
@@ -15,14 +15,16 @@
|
|
15
15
|
import logging
|
16
16
|
import os
|
17
17
|
import pathlib
|
18
|
+
import shutil
|
18
19
|
import subprocess
|
20
|
+
import tempfile
|
19
21
|
|
20
22
|
import click
|
21
23
|
from click.core import ParameterSource
|
22
24
|
from rich.console import Console
|
23
25
|
from rich.prompt import IntPrompt, Prompt
|
24
26
|
|
25
|
-
from ..utils.datastores import DATASTORE_TYPES
|
27
|
+
from ..utils.datastores import DATASTORE_TYPES, DATASTORES
|
26
28
|
from ..utils.gcp import verify_credentials, verify_vertex_connection
|
27
29
|
from ..utils.logging import handle_cli_error
|
28
30
|
from ..utils.remote_template import (
|
@@ -34,6 +36,7 @@ from ..utils.remote_template import (
|
|
34
36
|
)
|
35
37
|
from ..utils.template import (
|
36
38
|
get_available_agents,
|
39
|
+
get_deployment_targets,
|
37
40
|
get_template_path,
|
38
41
|
load_template_config,
|
39
42
|
process_template,
|
@@ -128,7 +131,7 @@ def normalize_project_name(project_name: str) -> str:
|
|
128
131
|
@click.option(
|
129
132
|
"--skip-checks",
|
130
133
|
is_flag=True,
|
131
|
-
help="Skip verification checks for
|
134
|
+
help="Skip verification checks for GCP and Vertex AI",
|
132
135
|
default=False,
|
133
136
|
)
|
134
137
|
@handle_cli_error
|
@@ -201,17 +204,28 @@ def create(
|
|
201
204
|
# Agent selection - handle remote templates
|
202
205
|
selected_agent = None
|
203
206
|
template_source_path = None
|
207
|
+
temp_dir_to_clean = None
|
204
208
|
|
205
209
|
if agent:
|
206
210
|
if agent.startswith("local@"):
|
207
211
|
path_str = agent.split("@", 1)[1]
|
208
|
-
|
209
|
-
if not
|
212
|
+
local_path = pathlib.Path(path_str).resolve()
|
213
|
+
if not local_path.is_dir():
|
210
214
|
raise click.ClickException(
|
211
|
-
f"Local path not found or not a directory: {
|
215
|
+
f"Local path not found or not a directory: {local_path}"
|
212
216
|
)
|
217
|
+
|
218
|
+
# Create a temporary directory and copy the local template to it
|
219
|
+
temp_dir = tempfile.mkdtemp(prefix="asp_local_template_")
|
220
|
+
temp_dir_to_clean = temp_dir
|
221
|
+
template_source_path = pathlib.Path(temp_dir) / local_path.name
|
222
|
+
shutil.copytree(local_path, template_source_path)
|
223
|
+
|
213
224
|
selected_agent = f"local_{template_source_path.name}"
|
214
|
-
console.print(f"Using local template: {
|
225
|
+
console.print(f"Using local template: {local_path}")
|
226
|
+
logging.debug(
|
227
|
+
f"Copied local template to temporary dir: {template_source_path}"
|
228
|
+
)
|
215
229
|
else:
|
216
230
|
# Check if it's a remote template specification
|
217
231
|
remote_spec = parse_agent_spec(agent)
|
@@ -223,7 +237,10 @@ def create(
|
|
223
237
|
)
|
224
238
|
else:
|
225
239
|
console.print(f"Fetching remote template: {agent}")
|
226
|
-
template_source_path = fetch_remote_template(
|
240
|
+
template_source_path, temp_dir_path = fetch_remote_template(
|
241
|
+
remote_spec
|
242
|
+
)
|
243
|
+
temp_dir_to_clean = str(temp_dir_path)
|
227
244
|
selected_agent = f"remote_{hash(agent)}" # Generate unique name for remote template
|
228
245
|
else:
|
229
246
|
# Handle local agent selection
|
@@ -244,13 +261,17 @@ def create(
|
|
244
261
|
f"Invalid agent name or number: {agent}"
|
245
262
|
) from err
|
246
263
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
264
|
+
# Agent selection
|
265
|
+
final_agent = selected_agent
|
266
|
+
if not final_agent:
|
267
|
+
if auto_approve:
|
268
|
+
raise click.ClickException(
|
269
|
+
"Error: --agent is required when running with --auto-approve."
|
270
|
+
)
|
271
|
+
final_agent = display_agent_selection(deployment_target)
|
272
|
+
|
252
273
|
if debug:
|
253
|
-
logging.debug(f"Selected agent: {
|
274
|
+
logging.debug(f"Selected agent: {final_agent}")
|
254
275
|
|
255
276
|
# Load template configuration based on whether it's remote or local
|
256
277
|
if template_source_path:
|
@@ -302,14 +323,19 @@ def create(
|
|
302
323
|
config = load_template_config(template_path)
|
303
324
|
# Data ingestion and datastore selection
|
304
325
|
if include_data_ingestion or datastore:
|
305
|
-
# If datastore is specified but include_data_ingestion is not, set it to True
|
306
326
|
include_data_ingestion = True
|
307
|
-
|
308
|
-
# If include_data_ingestion is True but no datastore is specified, prompt for it
|
309
327
|
if not datastore:
|
310
|
-
|
311
|
-
|
312
|
-
|
328
|
+
if auto_approve:
|
329
|
+
# Default to the first available datastore in non-interactive mode
|
330
|
+
datastore = next(iter(DATASTORES.keys()))
|
331
|
+
console.print(
|
332
|
+
f"Info: --datastore not specified. Defaulting to '{datastore}' in auto-approve mode.",
|
333
|
+
style="yellow",
|
334
|
+
)
|
335
|
+
else:
|
336
|
+
datastore = prompt_datastore_selection(
|
337
|
+
final_agent, from_cli_flag=True
|
338
|
+
)
|
313
339
|
if debug:
|
314
340
|
logging.debug(f"Data ingestion enabled: {include_data_ingestion}")
|
315
341
|
logging.debug(f"Selected datastore type: {datastore}")
|
@@ -317,8 +343,15 @@ def create(
|
|
317
343
|
# Check if the agent requires data ingestion
|
318
344
|
if config and config.get("settings", {}).get("requires_data_ingestion"):
|
319
345
|
include_data_ingestion = True
|
320
|
-
|
321
|
-
|
346
|
+
if not datastore:
|
347
|
+
if auto_approve:
|
348
|
+
datastore = next(iter(DATASTORES.keys()))
|
349
|
+
console.print(
|
350
|
+
f"Info: --datastore not specified. Defaulting to '{datastore}' in auto-approve mode.",
|
351
|
+
style="yellow",
|
352
|
+
)
|
353
|
+
else:
|
354
|
+
datastore = prompt_datastore_selection(final_agent)
|
322
355
|
if debug:
|
323
356
|
logging.debug(
|
324
357
|
f"Data ingestion required by agent: {include_data_ingestion}"
|
@@ -334,13 +367,25 @@ def create(
|
|
334
367
|
deployment_agent_name = get_base_template_name(config)
|
335
368
|
remote_config = config
|
336
369
|
|
337
|
-
final_deployment =
|
338
|
-
|
339
|
-
|
340
|
-
else prompt_deployment_target(
|
370
|
+
final_deployment = deployment_target
|
371
|
+
if not final_deployment:
|
372
|
+
available_targets = get_deployment_targets(
|
341
373
|
deployment_agent_name, remote_config=remote_config
|
342
374
|
)
|
343
|
-
|
375
|
+
if auto_approve:
|
376
|
+
if not available_targets:
|
377
|
+
raise click.ClickException(
|
378
|
+
f"Error: No deployment targets available for agent '{deployment_agent_name}'."
|
379
|
+
)
|
380
|
+
final_deployment = available_targets[0]
|
381
|
+
console.print(
|
382
|
+
f"Info: --deployment-target not specified. Defaulting to '{final_deployment}' in auto-approve mode.",
|
383
|
+
style="yellow",
|
384
|
+
)
|
385
|
+
else:
|
386
|
+
final_deployment = prompt_deployment_target(
|
387
|
+
deployment_agent_name, remote_config=remote_config
|
388
|
+
)
|
344
389
|
if debug:
|
345
390
|
logging.debug(f"Selected deployment target: {final_deployment}")
|
346
391
|
|
@@ -359,8 +404,19 @@ def create(
|
|
359
404
|
)
|
360
405
|
return
|
361
406
|
|
362
|
-
if
|
363
|
-
|
407
|
+
if (
|
408
|
+
final_deployment is not None
|
409
|
+
and final_deployment in ("cloud_run")
|
410
|
+
and not session_type
|
411
|
+
):
|
412
|
+
if auto_approve:
|
413
|
+
final_session_type = "in_memory"
|
414
|
+
console.print(
|
415
|
+
"Info: --session-type not specified. Defaulting to 'in_memory' in auto-approve mode.",
|
416
|
+
style="yellow",
|
417
|
+
)
|
418
|
+
else:
|
419
|
+
final_session_type = prompt_session_type_selection()
|
364
420
|
else:
|
365
421
|
# Agents that don't require session management always use in-memory sessions
|
366
422
|
final_session_type = "in_memory"
|
@@ -441,10 +497,22 @@ def create(
|
|
441
497
|
remote_template_path=template_source_path,
|
442
498
|
remote_config=config if template_source_path else None,
|
443
499
|
)
|
444
|
-
|
500
|
+
|
445
501
|
# Replace region in all files if a different region was specified
|
446
502
|
if region != "us-central1":
|
447
503
|
replace_region_in_files(project_path, region, debug=debug)
|
504
|
+
finally:
|
505
|
+
# Clean up the temporary directory if one was created
|
506
|
+
if temp_dir_to_clean:
|
507
|
+
try:
|
508
|
+
shutil.rmtree(temp_dir_to_clean)
|
509
|
+
logging.debug(
|
510
|
+
f"Successfully cleaned up temporary directory: {temp_dir_to_clean}"
|
511
|
+
)
|
512
|
+
except OSError as e:
|
513
|
+
logging.warning(
|
514
|
+
f"Failed to clean up temporary directory {temp_dir_to_clean}: {e}"
|
515
|
+
)
|
448
516
|
|
449
517
|
project_path = destination_dir / project_name
|
450
518
|
cd_path = project_path if output_dir else project_name
|
@@ -474,14 +542,12 @@ def create(
|
|
474
542
|
console.print("\n🚀 To get started, run the following command:")
|
475
543
|
|
476
544
|
# Check if the agent has a 'dev' command in its settings
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
f" [bold bright_green]cd {cd_path} && make install && make playground[/]"
|
484
|
-
)
|
545
|
+
interactive_command = config.get("settings", {}).get(
|
546
|
+
"interactive_command", "playground"
|
547
|
+
)
|
548
|
+
console.print(
|
549
|
+
f" [bold bright_green]cd {cd_path} && make install && make {interactive_command}[/]"
|
550
|
+
)
|
485
551
|
except Exception:
|
486
552
|
if debug:
|
487
553
|
logging.exception(
|
src/cli/commands/list.py
CHANGED
@@ -82,15 +82,9 @@ def list_remote_agents(remote_source: str, scan_from_root: bool = False) -> None
|
|
82
82
|
# specific template directory within the repo.
|
83
83
|
template_dir_path = fetch_remote_template(spec)
|
84
84
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
scan_path = template_dir_path
|
89
|
-
while not (scan_path / ".git").exists() and scan_path.parent != scan_path:
|
90
|
-
scan_path = scan_path.parent
|
91
|
-
else:
|
92
|
-
# For other git repos, respect the path given in the URL.
|
93
|
-
scan_path = template_dir_path
|
85
|
+
# fetch_remote_template always returns a tuple of (repo_path, template_path)
|
86
|
+
repo_path, template_path = template_dir_path
|
87
|
+
scan_path = repo_path if scan_from_root else template_path
|
94
88
|
|
95
89
|
display_agents_from_path(scan_path, remote_source)
|
96
90
|
|
src/cli/utils/remote_template.py
CHANGED
@@ -23,6 +23,7 @@ from dataclasses import dataclass
|
|
23
23
|
from typing import Any
|
24
24
|
|
25
25
|
import yaml
|
26
|
+
from jinja2 import Environment
|
26
27
|
|
27
28
|
|
28
29
|
@dataclass
|
@@ -112,7 +113,9 @@ def parse_agent_spec(agent_spec: str) -> RemoteTemplateSpec | None:
|
|
112
113
|
return None
|
113
114
|
|
114
115
|
|
115
|
-
def fetch_remote_template(
|
116
|
+
def fetch_remote_template(
|
117
|
+
spec: RemoteTemplateSpec,
|
118
|
+
) -> tuple[pathlib.Path, pathlib.Path]:
|
116
119
|
"""Fetch remote template and return path to template directory.
|
117
120
|
|
118
121
|
Uses Git to clone the remote repository.
|
@@ -121,7 +124,9 @@ def fetch_remote_template(spec: RemoteTemplateSpec) -> pathlib.Path:
|
|
121
124
|
spec: Remote template specification
|
122
125
|
|
123
126
|
Returns:
|
124
|
-
|
127
|
+
A tuple containing:
|
128
|
+
- Path to the fetched template directory.
|
129
|
+
- Path to the top-level temporary directory that should be cleaned up.
|
125
130
|
"""
|
126
131
|
temp_dir = tempfile.mkdtemp(prefix="asp_remote_template_")
|
127
132
|
temp_path = pathlib.Path(temp_dir)
|
@@ -169,18 +174,7 @@ def fetch_remote_template(spec: RemoteTemplateSpec) -> pathlib.Path:
|
|
169
174
|
f"Template path not found in the repository: {spec.template_path}"
|
170
175
|
)
|
171
176
|
|
172
|
-
|
173
|
-
makefile_path = template_dir / "Makefile"
|
174
|
-
if makefile_path.exists():
|
175
|
-
logging.debug(f"Removing Makefile from remote template: {makefile_path}")
|
176
|
-
makefile_path.unlink()
|
177
|
-
|
178
|
-
readme_path = template_dir / "README.md"
|
179
|
-
if readme_path.exists():
|
180
|
-
logging.debug(f"Removing README.md from remote template: {readme_path}")
|
181
|
-
readme_path.unlink()
|
182
|
-
|
183
|
-
return template_dir
|
177
|
+
return template_dir, temp_path
|
184
178
|
except Exception as e:
|
185
179
|
# Clean up on error
|
186
180
|
shutil.rmtree(temp_path, ignore_errors=True)
|
@@ -252,3 +246,76 @@ def merge_template_configs(
|
|
252
246
|
|
253
247
|
# Perform the deep merge
|
254
248
|
return deep_merge(merged_config, remote_config)
|
249
|
+
|
250
|
+
|
251
|
+
def render_and_merge_makefiles(
|
252
|
+
base_template_path: pathlib.Path,
|
253
|
+
final_destination: pathlib.Path,
|
254
|
+
cookiecutter_config: dict,
|
255
|
+
remote_template_path: pathlib.Path | None = None,
|
256
|
+
) -> None:
|
257
|
+
"""
|
258
|
+
Renders the base and remote Makefiles separately, then merges them.
|
259
|
+
|
260
|
+
If remote_template_path is not provided, only the base Makefile is rendered.
|
261
|
+
"""
|
262
|
+
|
263
|
+
env = Environment()
|
264
|
+
|
265
|
+
# Render the base Makefile
|
266
|
+
base_makefile_path = base_template_path / "Makefile"
|
267
|
+
if base_makefile_path.exists():
|
268
|
+
with open(base_makefile_path) as f:
|
269
|
+
base_template = env.from_string(f.read())
|
270
|
+
rendered_base_makefile = base_template.render(cookiecutter=cookiecutter_config)
|
271
|
+
else:
|
272
|
+
rendered_base_makefile = ""
|
273
|
+
|
274
|
+
# Render the remote Makefile if a path is provided
|
275
|
+
rendered_remote_makefile = ""
|
276
|
+
if remote_template_path:
|
277
|
+
remote_makefile_path = remote_template_path / "Makefile"
|
278
|
+
if remote_makefile_path.exists():
|
279
|
+
with open(remote_makefile_path) as f:
|
280
|
+
remote_template = env.from_string(f.read())
|
281
|
+
rendered_remote_makefile = remote_template.render(
|
282
|
+
cookiecutter=cookiecutter_config
|
283
|
+
)
|
284
|
+
|
285
|
+
# Merge the rendered Makefiles
|
286
|
+
if rendered_base_makefile and rendered_remote_makefile:
|
287
|
+
# A simple merge: remote content first, then append missing commands from base
|
288
|
+
base_commands = set(
|
289
|
+
re.findall(r"^([a-zA-Z0-9_-]+):", rendered_base_makefile, re.MULTILINE)
|
290
|
+
)
|
291
|
+
remote_commands = set(
|
292
|
+
re.findall(r"^([a-zA-Z0-9_-]+):", rendered_remote_makefile, re.MULTILINE)
|
293
|
+
)
|
294
|
+
missing_commands = base_commands - remote_commands
|
295
|
+
|
296
|
+
if missing_commands:
|
297
|
+
commands_to_append = ["\n\n# --- Commands from Agent Starter Pack ---\n\n"]
|
298
|
+
for command in sorted(missing_commands):
|
299
|
+
command_block_match = re.search(
|
300
|
+
rf"^{command}:.*?(?=\n\n(?:^#.*\n)*?^[a-zA-Z0-9_-]+:|" + r"\Z)",
|
301
|
+
rendered_base_makefile,
|
302
|
+
re.MULTILINE | re.DOTALL,
|
303
|
+
)
|
304
|
+
if command_block_match:
|
305
|
+
commands_to_append.append(command_block_match.group(0))
|
306
|
+
commands_to_append.append("\n\n")
|
307
|
+
|
308
|
+
final_makefile_content = rendered_remote_makefile + "".join(
|
309
|
+
commands_to_append
|
310
|
+
)
|
311
|
+
else:
|
312
|
+
final_makefile_content = rendered_remote_makefile
|
313
|
+
elif rendered_remote_makefile:
|
314
|
+
final_makefile_content = rendered_remote_makefile
|
315
|
+
else:
|
316
|
+
final_makefile_content = rendered_base_makefile
|
317
|
+
|
318
|
+
# Write the final merged Makefile
|
319
|
+
with open(final_destination / "Makefile", "w") as f:
|
320
|
+
f.write(final_makefile_content)
|
321
|
+
logging.debug("Rendered and merged Makefile written to final destination.")
|
src/cli/utils/template.py
CHANGED
@@ -31,6 +31,7 @@ from src.cli.utils.version import get_current_version
|
|
31
31
|
from .datastores import DATASTORES
|
32
32
|
from .remote_template import (
|
33
33
|
get_base_template_name,
|
34
|
+
render_and_merge_makefiles,
|
34
35
|
)
|
35
36
|
|
36
37
|
ADK_FILES = ["app/__init__.py"]
|
@@ -622,6 +623,7 @@ def process_template(
|
|
622
623
|
".pytest_cache/*",
|
623
624
|
".venv/*",
|
624
625
|
"*templates.py", # Don't render templates files
|
626
|
+
"Makefile", # Don't render Makefile - handled by render_and_merge_makefiles
|
625
627
|
# Don't render agent.py unless it's agentic_rag
|
626
628
|
"app/agent.py" if agent_name != "agentic_rag" else "",
|
627
629
|
],
|
@@ -658,6 +660,16 @@ def process_template(
|
|
658
660
|
shutil.copytree(output_dir, final_destination, dirs_exist_ok=True)
|
659
661
|
logging.debug(f"Project successfully created at {final_destination}")
|
660
662
|
|
663
|
+
# Render and merge Makefiles.
|
664
|
+
# If it's a local template, remote_template_path will be None,
|
665
|
+
# and only the base Makefile will be rendered.
|
666
|
+
render_and_merge_makefiles(
|
667
|
+
base_template_path=base_template_path,
|
668
|
+
final_destination=final_destination,
|
669
|
+
cookiecutter_config=cookiecutter_config,
|
670
|
+
remote_template_path=remote_template_path,
|
671
|
+
)
|
672
|
+
|
661
673
|
# Delete appropriate files based on ADK tag
|
662
674
|
if "adk" in tags:
|
663
675
|
files_to_delete = [final_destination / f for f in NON_ADK_FILES]
|
@@ -1,19 +1,41 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
apt-
|
16
|
-
|
17
|
-
|
1
|
+
# Stage 1: Builder - Prepare APT repositories
|
2
|
+
FROM ghcr.io/astral-sh/uv:python3.11-bookworm-slim AS builder
|
3
|
+
|
4
|
+
# Install tools needed to add repositories
|
5
|
+
RUN apt-get update && \
|
6
|
+
apt-get install -y --no-install-recommends \
|
7
|
+
ca-certificates \
|
8
|
+
curl \
|
9
|
+
gnupg && \
|
10
|
+
# GitHub CLI
|
11
|
+
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | gpg --dearmor -o /usr/share/keyrings/githubcli-archive-keyring.gpg && \
|
12
|
+
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list && \
|
13
|
+
# Google Cloud SDK
|
14
|
+
curl -sS https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg && \
|
15
|
+
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" > /etc/apt/sources.list.d/google-cloud-sdk.list && \
|
16
|
+
# Terraform
|
17
|
+
curl -fsSL https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg && \
|
18
|
+
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com bookworm main" > /etc/apt/sources.list.d/hashicorp.list && \
|
19
|
+
# Clean up builder stage
|
18
20
|
apt-get clean && \
|
19
21
|
rm -rf /var/lib/apt/lists/*
|
22
|
+
|
23
|
+
# Stage 2: Final Image
|
24
|
+
FROM ghcr.io/astral-sh/uv:python3.11-bookworm-slim
|
25
|
+
|
26
|
+
# Copy repository configurations from the builder stage
|
27
|
+
COPY --from=builder /etc/apt/sources.list.d/ /etc/apt/sources.list.d/
|
28
|
+
COPY --from=builder /usr/share/keyrings/ /usr/share/keyrings/
|
29
|
+
|
30
|
+
# Install the final packages
|
31
|
+
RUN apt-get update && \
|
32
|
+
apt-get install -y --no-install-recommends \
|
33
|
+
gh \
|
34
|
+
google-cloud-cli \
|
35
|
+
terraform \
|
36
|
+
make \
|
37
|
+
nodejs \
|
38
|
+
npm && \
|
39
|
+
# Clean up apt cache
|
40
|
+
apt-get clean && \
|
41
|
+
rm -rf /var/lib/apt/lists/*
|
File without changes
|
File without changes
|
File without changes
|