2020 format_run ,
2121 format_runs ,
2222 format_strikes ,
23- format_templates ,
2423)
25- from dreadnode_cli .agent .templates import Template , install_template , install_template_from_dir
24+ from dreadnode_cli .agent .templates import cli as templates_cli
25+ from dreadnode_cli .agent .templates .format import format_templates
26+ from dreadnode_cli .agent .templates .manager import TemplateManager
2627from dreadnode_cli .config import UserConfig
2728from dreadnode_cli .profile .cli import switch as switch_profile
2829from dreadnode_cli .types import GithubRepo
29- from dreadnode_cli .utils import download_and_unzip_archive , pretty_cli , repo_exists
30+ from dreadnode_cli .utils import download_and_unzip_archive , get_repo_archive_source_path , pretty_cli
3031
3132cli = typer .Typer (no_args_is_help = True )
3233
34+ cli .add_typer (templates_cli , name = "templates" , help = "Interact with Strike templates" )
35+
3336
3437def ensure_profile (agent_config : AgentConfig , * , user_config : UserConfig | None = None ) -> None :
3538 """Ensure the active agent link matches the current server profile."""
@@ -66,26 +69,6 @@ def ensure_profile(agent_config: AgentConfig, *, user_config: UserConfig | None
6669 switch_profile (agent_config .active_link .profile )
6770
6871
69- def get_repo_archive_source_path (source_dir : pathlib .Path ) -> pathlib .Path :
70- """Return the actual source directory from a git repositoryZIP archive."""
71-
72- if not (source_dir / "Dockerfile" ).exists () and not (source_dir / "Dockerfile.j2" ).exists ():
73- # if src has been downloaded from a ZIP archive, it may contain a single
74- # '<user>-<repo>-<commit hash>' folder, that is the actual source we want to use.
75- # Check if source_dir contains only one folder and update it if so.
76- children = list (source_dir .iterdir ())
77- if len (children ) == 1 and children [0 ].is_dir ():
78- source_dir = children [0 ]
79-
80- return source_dir
81-
82-
83- @cli .command (help = "List available agent templates with their descriptions" )
84- @pretty_cli
85- def templates () -> None :
86- print (format_templates ())
87-
88-
8972@cli .command (help = "Initialize a new agent project" )
9073@pretty_cli
9174def init (
@@ -98,8 +81,8 @@ def init(
9881 str | None , typer .Option ("--name" , "-n" , help = "The project name (used for container naming)" )
9982 ] = None ,
10083 template : t .Annotated [
101- Template , typer .Option ("--template" , "-t" , help = "The template to use for the agent" )
102- ] = Template . rigging_basic ,
84+ str | None , typer .Option ("--template" , "-t" , help = "The template to use for the agent" )
85+ ] = None ,
10386 source : t .Annotated [
10487 str | None ,
10588 typer .Option (
@@ -137,18 +120,45 @@ def init(
137120 print (f":crossed_swords: Linking to strike '{ strike_response .name } ' ({ strike_response .type } )" )
138121 print ()
139122
140- project_name = Prompt .ask ("Project name?" , default = name or directory .name )
123+ project_name = Prompt .ask (":toolbox: Project name?" , default = name or directory .name )
141124 print ()
142125
143126 directory .mkdir (exist_ok = True )
144127
128+ template_manager = TemplateManager ()
145129 context = {"project_name" : project_name , "strike" : strike_response }
146130
147131 if source is None :
148- # initialize from builtin template
149- template = Template (Prompt .ask ("Template?" , choices = [t .value for t in Template ], default = template .value ))
132+ # get the templates that match the strike
133+ available_templates = template_manager .get_templates_for_strike (strike_response .name , strike_response .type )
134+ available : list [str ] = list (available_templates .keys ())
135+
136+ # none available
137+ if not available :
138+ if not template_manager .templates :
139+ raise Exception (
140+ "No templates installed, use [bold]dreadnode agent templates install[/] to install some."
141+ )
142+ else :
143+ raise Exception ("No templates found for the given strike." )
144+
145+ # ask the user if the template has not been passed via command line
146+ if template is None :
147+ print (":notebook: Compatible templates:\n " )
148+ print (format_templates (available_templates , with_index = True ))
149+ print ()
150+
151+ choice = Prompt .ask ("Choice " , choices = [str (i + 1 ) for i in range (len (available ))])
152+ template = available [int (choice ) - 1 ]
153+
154+ # validate the template
155+ if template not in available :
156+ raise Exception (
157+ f"Template '{ template } ' not found, use [bold]dreadnode agent templates show[/] to see available templates."
158+ )
150159
151- install_template (template , directory , context )
160+ # install the template
161+ template_manager .install (template , directory , context )
152162 else :
153163 source_dir = pathlib .Path (source )
154164 cleanup = False
@@ -162,7 +172,7 @@ def init(
162172 github_repo = GithubRepo (source )
163173
164174 # Check if the repo is accessible
165- if repo_exists ( github_repo ) :
175+ if github_repo . exists :
166176 source_dir = download_and_unzip_archive (github_repo .zip_url )
167177
168178 # This could be a private repo that the user can access
@@ -193,7 +203,8 @@ def init(
193203 if path is not None :
194204 source_dir = source_dir / path
195205
196- install_template_from_dir (source_dir , directory , context )
206+ # install the template
207+ template_manager .install_from_dir (source_dir , directory , context )
197208 except Exception :
198209 if cleanup and source_dir .exists ():
199210 shutil .rmtree (source_dir )
@@ -521,7 +532,7 @@ def clone(
521532 shutil .rmtree (target )
522533
523534 # Check if the repo is accessible
524- if repo_exists ( github_repo ) :
535+ if github_repo . exists :
525536 temp_dir = download_and_unzip_archive (github_repo .zip_url )
526537
527538 # This could be a private repo that the user can access
0 commit comments