agent-starter-pack 0.8.0__py3-none-any.whl → 0.9.1__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.8.0.dist-info → agent_starter_pack-0.9.1.dist-info}/METADATA +3 -2
- {agent_starter_pack-0.8.0.dist-info → agent_starter_pack-0.9.1.dist-info}/RECORD +30 -28
- agents/adk_base/{template/.templateconfig.yaml → .template/templateconfig.yaml} +1 -1
- agents/adk_gemini_fullstack/{template/.templateconfig.yaml → .template/templateconfig.yaml} +2 -2
- agents/agentic_rag/{template/.templateconfig.yaml → .template/templateconfig.yaml} +1 -1
- src/base_template/Makefile +5 -1
- src/base_template/pyproject.toml +2 -2
- src/cli/commands/create.py +155 -43
- src/cli/commands/list.py +158 -0
- src/cli/main.py +2 -0
- src/cli/utils/remote_template.py +254 -0
- src/cli/utils/template.py +100 -25
- src/resources/locks/uv-adk_base-agent_engine.lock +151 -119
- src/resources/locks/uv-adk_base-cloud_run.lock +181 -146
- src/resources/locks/uv-adk_gemini_fullstack-agent_engine.lock +151 -119
- src/resources/locks/uv-adk_gemini_fullstack-cloud_run.lock +181 -146
- src/resources/locks/uv-agentic_rag-agent_engine.lock +154 -122
- src/resources/locks/uv-agentic_rag-cloud_run.lock +184 -149
- src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +130 -130
- src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +160 -160
- src/resources/locks/uv-langgraph_base_react-agent_engine.lock +109 -109
- src/resources/locks/uv-langgraph_base_react-cloud_run.lock +139 -139
- src/resources/locks/uv-live_api-cloud_run.lock +136 -136
- src/utils/lock_utils.py +1 -1
- {agent_starter_pack-0.8.0.dist-info → agent_starter_pack-0.9.1.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.8.0.dist-info → agent_starter_pack-0.9.1.dist-info}/entry_points.txt +0 -0
- {agent_starter_pack-0.8.0.dist-info → agent_starter_pack-0.9.1.dist-info}/licenses/LICENSE +0 -0
- /agents/crewai_coding_crew/{template/.templateconfig.yaml → .template/templateconfig.yaml} +0 -0
- /agents/langgraph_base_react/{template/.templateconfig.yaml → .template/templateconfig.yaml} +0 -0
- /agents/live_api/{template/.templateconfig.yaml → .template/templateconfig.yaml} +0 -0
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.list import list_agents
|
21
22
|
from .commands.setup_cicd import setup_cicd
|
22
23
|
from .utils import display_update_message
|
23
24
|
|
@@ -53,6 +54,7 @@ def cli() -> None:
|
|
53
54
|
# Register commands
|
54
55
|
cli.add_command(create)
|
55
56
|
cli.add_command(setup_cicd)
|
57
|
+
cli.add_command(list_agents, name="list")
|
56
58
|
|
57
59
|
|
58
60
|
if __name__ == "__main__":
|
@@ -0,0 +1,254 @@
|
|
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 logging
|
16
|
+
import os
|
17
|
+
import pathlib
|
18
|
+
import re
|
19
|
+
import shutil
|
20
|
+
import subprocess
|
21
|
+
import tempfile
|
22
|
+
from dataclasses import dataclass
|
23
|
+
from typing import Any
|
24
|
+
|
25
|
+
import yaml
|
26
|
+
|
27
|
+
|
28
|
+
@dataclass
|
29
|
+
class RemoteTemplateSpec:
|
30
|
+
"""Parsed remote template specification."""
|
31
|
+
|
32
|
+
repo_url: str
|
33
|
+
template_path: str
|
34
|
+
git_ref: str
|
35
|
+
is_adk_samples: bool = False
|
36
|
+
|
37
|
+
|
38
|
+
def parse_agent_spec(agent_spec: str) -> RemoteTemplateSpec | None:
|
39
|
+
"""Parse agent specification to determine if it's a remote template.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
agent_spec: Agent specification string
|
43
|
+
|
44
|
+
Returns:
|
45
|
+
RemoteTemplateSpec if remote template, None if local template
|
46
|
+
"""
|
47
|
+
# Check for local@ prefix
|
48
|
+
if agent_spec.startswith("local@"):
|
49
|
+
return None
|
50
|
+
|
51
|
+
# Check for adk@ shortcut
|
52
|
+
if agent_spec.startswith("adk@"):
|
53
|
+
sample_name = agent_spec[4:] # Remove "adk@" prefix
|
54
|
+
return RemoteTemplateSpec(
|
55
|
+
repo_url="https://github.com/google/adk-samples",
|
56
|
+
template_path=f"python/agents/{sample_name}",
|
57
|
+
git_ref="main",
|
58
|
+
is_adk_samples=True,
|
59
|
+
)
|
60
|
+
|
61
|
+
# GitHub /tree/ URL pattern
|
62
|
+
tree_pattern = r"^(https?://[^/]+/[^/]+/[^/]+)/tree/([^/]+)/(.*)$"
|
63
|
+
match = re.match(tree_pattern, agent_spec)
|
64
|
+
if match:
|
65
|
+
repo_url = match.group(1)
|
66
|
+
git_ref = match.group(2)
|
67
|
+
template_path = match.group(3)
|
68
|
+
return RemoteTemplateSpec(
|
69
|
+
repo_url=repo_url,
|
70
|
+
template_path=template_path.strip("/"),
|
71
|
+
git_ref=git_ref,
|
72
|
+
)
|
73
|
+
|
74
|
+
# General remote pattern: <repo_url>[/<path>][@<ref>]
|
75
|
+
# Handles github.com, gitlab.com, etc.
|
76
|
+
remote_pattern = r"^(https?://[^/]+/[^/]+/[^/]+)(?:/(.*?))?(?:@([^/]+))?/?$"
|
77
|
+
match = re.match(remote_pattern, agent_spec)
|
78
|
+
if match:
|
79
|
+
repo_url = match.group(1)
|
80
|
+
template_path_with_ref = match.group(2) or ""
|
81
|
+
git_ref_from_url = match.group(3)
|
82
|
+
|
83
|
+
# Separate path and ref if ref is part of the path
|
84
|
+
template_path = template_path_with_ref
|
85
|
+
git_ref = git_ref_from_url or "main"
|
86
|
+
|
87
|
+
if "@" in template_path:
|
88
|
+
path_parts = template_path.split("@")
|
89
|
+
template_path = path_parts[0]
|
90
|
+
git_ref = path_parts[1]
|
91
|
+
|
92
|
+
return RemoteTemplateSpec(
|
93
|
+
repo_url=repo_url,
|
94
|
+
template_path=template_path.strip("/"),
|
95
|
+
git_ref=git_ref,
|
96
|
+
)
|
97
|
+
|
98
|
+
# GitHub shorthand: <org>/<repo>[/<path>][@<ref>]
|
99
|
+
github_shorthand_pattern = r"^([^/]+)/([^/]+)(?:/(.*?))?(?:@([^/]+))?/?$"
|
100
|
+
match = re.match(github_shorthand_pattern, agent_spec)
|
101
|
+
if match and "/" in agent_spec: # Ensure it has at least one slash
|
102
|
+
org = match.group(1)
|
103
|
+
repo = match.group(2)
|
104
|
+
template_path = match.group(3) or ""
|
105
|
+
git_ref = match.group(4) or "main"
|
106
|
+
return RemoteTemplateSpec(
|
107
|
+
repo_url=f"https://github.com/{org}/{repo}",
|
108
|
+
template_path=template_path,
|
109
|
+
git_ref=git_ref,
|
110
|
+
)
|
111
|
+
|
112
|
+
return None
|
113
|
+
|
114
|
+
|
115
|
+
def fetch_remote_template(spec: RemoteTemplateSpec) -> pathlib.Path:
|
116
|
+
"""Fetch remote template and return path to template directory.
|
117
|
+
|
118
|
+
Uses Git to clone the remote repository.
|
119
|
+
|
120
|
+
Args:
|
121
|
+
spec: Remote template specification
|
122
|
+
|
123
|
+
Returns:
|
124
|
+
Path to the fetched template directory
|
125
|
+
"""
|
126
|
+
temp_dir = tempfile.mkdtemp(prefix="asp_remote_template_")
|
127
|
+
temp_path = pathlib.Path(temp_dir)
|
128
|
+
repo_path = temp_path / "repo"
|
129
|
+
|
130
|
+
# Attempt Git Clone
|
131
|
+
try:
|
132
|
+
clone_url = spec.repo_url
|
133
|
+
clone_cmd = [
|
134
|
+
"git",
|
135
|
+
"clone",
|
136
|
+
"--depth",
|
137
|
+
"1",
|
138
|
+
"--branch",
|
139
|
+
spec.git_ref,
|
140
|
+
clone_url,
|
141
|
+
str(repo_path),
|
142
|
+
]
|
143
|
+
logging.debug(
|
144
|
+
f"Attempting to clone remote template with Git: {' '.join(clone_cmd)}"
|
145
|
+
)
|
146
|
+
# GIT_TERMINAL_PROMPT=0 prevents git from prompting for credentials
|
147
|
+
subprocess.run(
|
148
|
+
clone_cmd,
|
149
|
+
capture_output=True,
|
150
|
+
text=True,
|
151
|
+
check=True,
|
152
|
+
encoding="utf-8",
|
153
|
+
env={**os.environ, "GIT_TERMINAL_PROMPT": "0"},
|
154
|
+
)
|
155
|
+
logging.debug("Git clone successful.")
|
156
|
+
except subprocess.CalledProcessError as e:
|
157
|
+
shutil.rmtree(temp_path, ignore_errors=True)
|
158
|
+
raise RuntimeError(f"Git clone failed: {e.stderr.strip()}") from e
|
159
|
+
|
160
|
+
# Process the successfully fetched template
|
161
|
+
try:
|
162
|
+
if spec.template_path:
|
163
|
+
template_dir = repo_path / spec.template_path
|
164
|
+
else:
|
165
|
+
template_dir = repo_path
|
166
|
+
|
167
|
+
if not template_dir.exists():
|
168
|
+
raise FileNotFoundError(
|
169
|
+
f"Template path not found in the repository: {spec.template_path}"
|
170
|
+
)
|
171
|
+
|
172
|
+
# Exclude Makefile and README.md from remote template to avoid conflicts
|
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
|
184
|
+
except Exception as e:
|
185
|
+
# Clean up on error
|
186
|
+
shutil.rmtree(temp_path, ignore_errors=True)
|
187
|
+
raise RuntimeError(
|
188
|
+
f"An unexpected error occurred after fetching remote template: {e}"
|
189
|
+
) from e
|
190
|
+
|
191
|
+
|
192
|
+
def load_remote_template_config(template_dir: pathlib.Path) -> dict[str, Any]:
|
193
|
+
"""Load template configuration from remote template.
|
194
|
+
|
195
|
+
Args:
|
196
|
+
template_dir: Path to template directory
|
197
|
+
|
198
|
+
Returns:
|
199
|
+
Template configuration dictionary
|
200
|
+
"""
|
201
|
+
config_path = template_dir / ".template" / "templateconfig.yaml"
|
202
|
+
|
203
|
+
if not config_path.exists():
|
204
|
+
return {}
|
205
|
+
|
206
|
+
try:
|
207
|
+
with open(config_path) as f:
|
208
|
+
config = yaml.safe_load(f)
|
209
|
+
return config if config else {}
|
210
|
+
except Exception as e:
|
211
|
+
logging.error(f"Error loading remote template config: {e}")
|
212
|
+
return {}
|
213
|
+
|
214
|
+
|
215
|
+
def get_base_template_name(config: dict[str, Any]) -> str:
|
216
|
+
"""Get base template name from remote template config.
|
217
|
+
|
218
|
+
Args:
|
219
|
+
config: Template configuration dictionary
|
220
|
+
|
221
|
+
Returns:
|
222
|
+
Base template name (defaults to "adk_base")
|
223
|
+
"""
|
224
|
+
return config.get("base_template", "adk_base")
|
225
|
+
|
226
|
+
|
227
|
+
def merge_template_configs(
|
228
|
+
base_config: dict[str, Any], remote_config: dict[str, Any]
|
229
|
+
) -> dict[str, Any]:
|
230
|
+
"""Merge base template config with remote template config using a deep merge.
|
231
|
+
|
232
|
+
Args:
|
233
|
+
base_config: Base template configuration
|
234
|
+
remote_config: Remote template configuration
|
235
|
+
|
236
|
+
Returns:
|
237
|
+
Merged configuration with remote overriding base
|
238
|
+
"""
|
239
|
+
import copy
|
240
|
+
|
241
|
+
def deep_merge(d1: dict[str, Any], d2: dict[str, Any]) -> dict[str, Any]:
|
242
|
+
"""Recursively merges d2 into d1."""
|
243
|
+
for k, v in d2.items():
|
244
|
+
if k in d1 and isinstance(d1[k], dict) and isinstance(v, dict):
|
245
|
+
d1[k] = deep_merge(d1[k], v)
|
246
|
+
else:
|
247
|
+
d1[k] = v
|
248
|
+
return d1
|
249
|
+
|
250
|
+
# Start with a deep copy of the base to avoid modifying it
|
251
|
+
merged_config = copy.deepcopy(base_config)
|
252
|
+
|
253
|
+
# Perform the deep merge
|
254
|
+
return deep_merge(merged_config, remote_config)
|
src/cli/utils/template.py
CHANGED
@@ -29,6 +29,9 @@ from rich.prompt import IntPrompt, Prompt
|
|
29
29
|
from src.cli.utils.version import get_current_version
|
30
30
|
|
31
31
|
from .datastores import DATASTORES
|
32
|
+
from .remote_template import (
|
33
|
+
get_base_template_name,
|
34
|
+
)
|
32
35
|
|
33
36
|
ADK_FILES = ["app/__init__.py"]
|
34
37
|
NON_ADK_FILES: list[str] = []
|
@@ -69,7 +72,7 @@ class TemplateConfig:
|
|
69
72
|
|
70
73
|
|
71
74
|
OVERWRITE_FOLDERS = ["app", "frontend", "tests", "notebooks"]
|
72
|
-
TEMPLATE_CONFIG_FILE = "
|
75
|
+
TEMPLATE_CONFIG_FILE = "templateconfig.yaml"
|
73
76
|
DEPLOYMENT_FOLDERS = ["cloud_run", "agent_engine"]
|
74
77
|
DEFAULT_FRONTEND = "streamlit"
|
75
78
|
|
@@ -94,7 +97,7 @@ def get_available_agents(deployment_target: str | None = None) -> dict:
|
|
94
97
|
|
95
98
|
for agent_dir in agents_dir.iterdir():
|
96
99
|
if agent_dir.is_dir() and not agent_dir.name.startswith("__"):
|
97
|
-
template_config_path = agent_dir / "template" / "
|
100
|
+
template_config_path = agent_dir / ".template" / "templateconfig.yaml"
|
98
101
|
if template_config_path.exists():
|
99
102
|
try:
|
100
103
|
with open(template_config_path) as f:
|
@@ -154,15 +157,20 @@ def load_template_config(template_dir: pathlib.Path) -> dict[str, Any]:
|
|
154
157
|
return {}
|
155
158
|
|
156
159
|
|
157
|
-
def get_deployment_targets(
|
160
|
+
def get_deployment_targets(
|
161
|
+
agent_name: str, remote_config: dict[str, Any] | None = None
|
162
|
+
) -> list:
|
158
163
|
"""Get available deployment targets for the selected agent."""
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
164
|
+
if remote_config:
|
165
|
+
config = remote_config
|
166
|
+
else:
|
167
|
+
template_path = (
|
168
|
+
pathlib.Path(__file__).parent.parent.parent.parent
|
169
|
+
/ "agents"
|
170
|
+
/ agent_name
|
171
|
+
/ ".template"
|
172
|
+
)
|
173
|
+
config = load_template_config(template_path)
|
166
174
|
|
167
175
|
if not config:
|
168
176
|
return []
|
@@ -171,9 +179,11 @@ def get_deployment_targets(agent_name: str) -> list:
|
|
171
179
|
return targets if isinstance(targets, list) else [targets]
|
172
180
|
|
173
181
|
|
174
|
-
def prompt_deployment_target(
|
182
|
+
def prompt_deployment_target(
|
183
|
+
agent_name: str, remote_config: dict[str, Any] | None = None
|
184
|
+
) -> str:
|
175
185
|
"""Ask user to select a deployment target for the agent."""
|
176
|
-
targets = get_deployment_targets(agent_name)
|
186
|
+
targets = get_deployment_targets(agent_name, remote_config=remote_config)
|
177
187
|
|
178
188
|
# Define deployment target friendly names and descriptions
|
179
189
|
TARGET_INFO = {
|
@@ -272,7 +282,7 @@ def prompt_datastore_selection(
|
|
272
282
|
pathlib.Path(__file__).parent.parent.parent.parent
|
273
283
|
/ "agents"
|
274
284
|
/ agent_name
|
275
|
-
/ "template"
|
285
|
+
/ ".template"
|
276
286
|
)
|
277
287
|
config = load_template_config(template_path)
|
278
288
|
|
@@ -349,7 +359,7 @@ def prompt_datastore_selection(
|
|
349
359
|
def get_template_path(agent_name: str, debug: bool = False) -> pathlib.Path:
|
350
360
|
"""Get the absolute path to the agent template directory."""
|
351
361
|
current_dir = pathlib.Path(__file__).parent.parent.parent.parent
|
352
|
-
template_path = current_dir / "agents" / agent_name / "template"
|
362
|
+
template_path = current_dir / "agents" / agent_name / ".template"
|
353
363
|
if debug:
|
354
364
|
logging.debug(f"Looking for template in: {template_path}")
|
355
365
|
logging.debug(f"Template exists: {template_path.exists()}")
|
@@ -397,6 +407,8 @@ def process_template(
|
|
397
407
|
datastore: str | None = None,
|
398
408
|
session_type: str | None = None,
|
399
409
|
output_dir: pathlib.Path | None = None,
|
410
|
+
remote_template_path: pathlib.Path | None = None,
|
411
|
+
remote_config: dict[str, Any] | None = None,
|
400
412
|
) -> None:
|
401
413
|
"""Process the template directory and create a new project.
|
402
414
|
|
@@ -409,14 +421,30 @@ def process_template(
|
|
409
421
|
datastore: Optional datastore type for data ingestion
|
410
422
|
session_type: Optional session type for cloud_run deployment
|
411
423
|
output_dir: Optional output directory path, defaults to current directory
|
424
|
+
remote_template_path: Optional path to remote template for overlay
|
425
|
+
remote_config: Optional remote template configuration
|
412
426
|
"""
|
413
427
|
logging.debug(f"Processing template from {template_dir}")
|
414
428
|
logging.debug(f"Project name: {project_name}")
|
415
429
|
logging.debug(f"Include pipeline: {datastore}")
|
416
430
|
logging.debug(f"Output directory: {output_dir}")
|
417
431
|
|
418
|
-
#
|
419
|
-
|
432
|
+
# Handle remote vs local templates
|
433
|
+
is_remote = remote_template_path is not None
|
434
|
+
|
435
|
+
if is_remote:
|
436
|
+
# For remote templates, determine the base template
|
437
|
+
base_template_name = get_base_template_name(remote_config or {})
|
438
|
+
agent_path = (
|
439
|
+
pathlib.Path(__file__).parent.parent.parent.parent
|
440
|
+
/ "agents"
|
441
|
+
/ base_template_name
|
442
|
+
)
|
443
|
+
logging.debug(f"Remote template using base: {base_template_name}")
|
444
|
+
else:
|
445
|
+
# For local templates, use the existing logic
|
446
|
+
agent_path = template_dir.parent # Get parent of template dir
|
447
|
+
|
420
448
|
logging.debug(f"agent path: {agent_path}")
|
421
449
|
logging.debug(f"agent path exists: {agent_path.exists()}")
|
422
450
|
logging.debug(
|
@@ -489,7 +517,7 @@ def process_template(
|
|
489
517
|
copy_frontend_files(frontend_type, project_template)
|
490
518
|
logging.debug(f"4. Processed frontend files for type: {frontend_type}")
|
491
519
|
|
492
|
-
# 5.
|
520
|
+
# 5. Copy agent-specific files to override base template
|
493
521
|
if agent_path.exists():
|
494
522
|
for folder in OVERWRITE_FOLDERS:
|
495
523
|
agent_folder = agent_path / folder
|
@@ -500,11 +528,27 @@ def process_template(
|
|
500
528
|
agent_folder, project_folder, agent_name, overwrite=True
|
501
529
|
)
|
502
530
|
|
531
|
+
# 6. Finally, overlay remote template files if present
|
532
|
+
if is_remote and remote_template_path:
|
533
|
+
logging.debug(
|
534
|
+
f"6. Overlaying remote template files from {remote_template_path}"
|
535
|
+
)
|
536
|
+
copy_files(
|
537
|
+
remote_template_path,
|
538
|
+
project_template,
|
539
|
+
agent_name=agent_name,
|
540
|
+
overwrite=True,
|
541
|
+
)
|
542
|
+
|
503
543
|
# Load and validate template config first
|
504
|
-
|
505
|
-
|
544
|
+
if is_remote:
|
545
|
+
config = remote_config or {}
|
546
|
+
else:
|
547
|
+
template_path = pathlib.Path(template_dir)
|
548
|
+
config = load_template_config(template_path)
|
549
|
+
|
506
550
|
if not config:
|
507
|
-
raise ValueError(
|
551
|
+
raise ValueError("Could not load template config")
|
508
552
|
|
509
553
|
# Validate deployment target
|
510
554
|
available_targets = config.get("settings", {}).get("deployment_targets", [])
|
@@ -516,8 +560,8 @@ def process_template(
|
|
516
560
|
f"Invalid deployment target '{deployment_target}'. Available targets: {available_targets}"
|
517
561
|
)
|
518
562
|
|
519
|
-
#
|
520
|
-
template_config =
|
563
|
+
# Use the already loaded config
|
564
|
+
template_config = config
|
521
565
|
|
522
566
|
# Check if data processing should be included
|
523
567
|
if include_data_ingestion and datastore:
|
@@ -625,9 +669,36 @@ def process_template(
|
|
625
669
|
file_path.unlink()
|
626
670
|
logging.debug(f"Deleted {file_path}")
|
627
671
|
|
628
|
-
#
|
629
|
-
if
|
630
|
-
#
|
672
|
+
# Handle pyproject.toml and uv.lock files
|
673
|
+
if is_remote and remote_template_path:
|
674
|
+
# For remote templates, use their pyproject.toml and uv.lock if they exist
|
675
|
+
remote_pyproject = remote_template_path / "pyproject.toml"
|
676
|
+
remote_uv_lock = remote_template_path / "uv.lock"
|
677
|
+
|
678
|
+
if remote_pyproject.exists():
|
679
|
+
shutil.copy2(
|
680
|
+
remote_pyproject, final_destination / "pyproject.toml"
|
681
|
+
)
|
682
|
+
logging.debug("Used pyproject.toml from remote template")
|
683
|
+
|
684
|
+
if remote_uv_lock.exists():
|
685
|
+
shutil.copy2(remote_uv_lock, final_destination / "uv.lock")
|
686
|
+
logging.debug("Used uv.lock from remote template")
|
687
|
+
elif deployment_target:
|
688
|
+
# Fallback to base template lock file
|
689
|
+
base_template_name = get_base_template_name(remote_config or {})
|
690
|
+
lock_path = (
|
691
|
+
pathlib.Path(__file__).parent.parent.parent.parent
|
692
|
+
/ "src"
|
693
|
+
/ "resources"
|
694
|
+
/ "locks"
|
695
|
+
/ f"uv-{base_template_name}-{deployment_target}.lock"
|
696
|
+
)
|
697
|
+
if lock_path.exists():
|
698
|
+
shutil.copy2(lock_path, final_destination / "uv.lock")
|
699
|
+
logging.debug(f"Used fallback lock file from {lock_path}")
|
700
|
+
elif deployment_target:
|
701
|
+
# For local templates, use the existing logic
|
631
702
|
lock_path = (
|
632
703
|
pathlib.Path(__file__).parent.parent.parent.parent
|
633
704
|
/ "src"
|
@@ -706,8 +777,12 @@ def copy_files(
|
|
706
777
|
return True
|
707
778
|
if "__pycache__" in str(path) or path.name == "__pycache__":
|
708
779
|
return True
|
780
|
+
if ".git" in path.parts:
|
781
|
+
return True
|
709
782
|
if agent_name is not None and should_exclude_path(path, agent_name):
|
710
783
|
return True
|
784
|
+
if path.is_dir() and path.name == ".template":
|
785
|
+
return True
|
711
786
|
return False
|
712
787
|
|
713
788
|
if src.is_dir():
|