@@ -88,6 +88,86 @@ fn prompt_skills() -> Result<bool> {
8888 Ok ( install)
8989}
9090
91+ /// Prompt the user to link this repo to a Harmont Cloud organization.
92+ ///
93+ /// Flow:
94+ /// - If not logged in → offer to log in first (Confirm, default no).
95+ /// - If logged in (or just logged in) → fetch orgs → Select with "No, skip" as first item.
96+ /// - On org selection → write a sparse `.hm/config.toml` with `backend = "cloud"` and the org slug.
97+ ///
98+ /// Silently returns `Ok(())` on any user-cancellation (Esc, Ctrl-C on a prompt).
99+ async fn prompt_cloud_registration ( dir : & std:: path:: Path ) -> Result < ( ) > {
100+ let cfg = hm_config:: Config :: load ( None ) . unwrap_or_default ( ) ;
101+ let api_url = & cfg. cloud . api_url ;
102+ let is_logged_in = hm_config:: creds:: cloud_token ( api_url) . is_some ( ) ;
103+
104+ if !is_logged_in {
105+ let want_login = dialoguer:: Confirm :: new ( )
106+ . with_prompt ( "You are not logged in to Harmont Cloud. Log in now?" )
107+ . default ( false )
108+ . interact ( )
109+ . unwrap_or ( false ) ;
110+
111+ if !want_login {
112+ return Ok ( ( ) ) ;
113+ }
114+
115+ hm_plugin_cloud:: login_interactive ( ) . await ?;
116+ }
117+
118+ let ( client, _ctx) = hm_plugin_cloud:: settings:: client ( )
119+ . context ( "could not build authenticated cloud client" ) ?;
120+
121+ let orgs = client
122+ . raw ( )
123+ . list_organizations ( None , None )
124+ . await
125+ . map_err ( hm_plugin_cloud:: settings:: map_raw)
126+ . context ( "fetching organizations" ) ?
127+ . into_inner ( ) ;
128+
129+ if orgs. data . is_empty ( ) {
130+ tracing:: warn!( "no organizations found — create one at https://app.harmont.dev" ) ;
131+ return Ok ( ( ) ) ;
132+ }
133+
134+ let mut items: Vec < String > = vec ! [ "No, skip" . to_string( ) ] ;
135+ items. extend ( orgs. data . iter ( ) . map ( |o| format ! ( "{} ({})" , o. name, o. slug) ) ) ;
136+
137+ let selection = dialoguer:: Select :: new ( )
138+ . with_prompt ( "Link this repo to Harmont Cloud?" )
139+ . items ( & items)
140+ . default ( 0 )
141+ . interact ( )
142+ . unwrap_or ( 0 ) ;
143+
144+ if selection == 0 {
145+ return Ok ( ( ) ) ;
146+ }
147+
148+ let chosen = & orgs. data [ selection - 1 ] ;
149+ write_cloud_project_config ( dir, & chosen. slug ) ?;
150+ tracing:: info!(
151+ "linked to {} ({}) — `hm run` will now use Harmont Cloud by default" ,
152+ chosen. name,
153+ chosen. slug,
154+ ) ;
155+ Ok ( ( ) )
156+ }
157+
158+ fn write_cloud_project_config ( dir : & std:: path:: Path , org_slug : & str ) -> Result < ( ) > {
159+ let config_path = dir. join ( ".hm/config.toml" ) ;
160+ let content = format ! (
161+ "backend = \" cloud\" \n \
162+ \n \
163+ [cloud]\n \
164+ org = \" {org_slug}\" \n "
165+ ) ;
166+ std:: fs:: write ( & config_path, & content)
167+ . with_context ( || format ! ( "writing {}" , config_path. display( ) ) ) ?;
168+ Ok ( ( ) )
169+ }
170+
91171fn write_template ( dir : & Path , tmpl : & Template , force : bool ) -> Result < bool > {
92172 let harmont_dir = dir. join ( ".hm" ) ;
93173 let already_has_pipeline = detect:: has_pipeline_files ( dir) ;
@@ -164,7 +244,6 @@ fn has_github_workflows(dir: &Path) -> bool {
164244///
165245/// Returns an error if the target directory is unwritable, or if no template
166246/// can be determined in a non-interactive context.
167- #[ allow( clippy:: unused_async) ]
168247pub async fn handle ( args : InitArgs ) -> Result < ( ) > {
169248 let tty = std:: io:: stdin ( ) . is_terminal ( ) ;
170249 let has_pipeline = detect:: has_pipeline_files ( & args. dir ) ;
@@ -202,6 +281,10 @@ pub async fn handle(args: InitArgs) -> Result<()> {
202281 }
203282 }
204283
284+ if tty && let Err ( e) = prompt_cloud_registration ( & args. dir ) . await {
285+ tracing:: warn!( "cloud registration skipped: {e:#}" ) ;
286+ }
287+
205288 if has_github_workflows ( & args. dir ) {
206289 tracing:: info!(
207290 "detected GitHub Actions workflows in .github/workflows/\n \
@@ -215,6 +298,17 @@ pub async fn handle(args: InitArgs) -> Result<()> {
215298 write_skills ( & args. dir ) ?;
216299 }
217300
218- tracing:: info!( "next step: run `hm run` to execute your pipeline locally" ) ;
301+ let project_config = hm_config:: Config :: project_config_path ( & args. dir ) ;
302+ if project_config. exists ( ) {
303+ let cfg =
304+ hm_config:: Config :: load_from_paths ( None , Some ( & project_config) ) . unwrap_or_default ( ) ;
305+ if cfg. backend == "cloud" {
306+ tracing:: info!( "next step: run `hm run` to execute your pipeline on Harmont Cloud" ) ;
307+ } else {
308+ tracing:: info!( "next step: run `hm run` to execute your pipeline locally" ) ;
309+ }
310+ } else {
311+ tracing:: info!( "next step: run `hm run` to execute your pipeline locally" ) ;
312+ }
219313 Ok ( ( ) )
220314}
0 commit comments