Repository: run-house/runhouse

Issue:
Consider adding `-y` option to `runhouse login` CLI command
## Feature
Simple use case is logging in with `system` command instead of Python API:
```
!runhouse login [TOKEN]
```
Currently, the CLI is hardcoded with `interactive=True`:
https://github.com/run-house/runhouse/blob/560a52880a333e17e8a1aca01c1048f4527fc375/runhouse/main.py#L27

## Motivation
It's a minor quality of life improvement.

## Ideal Solution
See above

## Additional context
Excited to get Runhouse integration up on NatML 😄 



Discussion:
Following up on this, I seem to have stumbled upon a bug. The following:
```python
import runhouse as rh

rh.login(token)
```
Still requires interaction:
<img width="842" alt="Screenshot 2023-04-13 at 11 47 30 AM" src="https://user-images.githubusercontent.com/17663473/231814411-aaca763f-a5a9-4f1b-8557-92e7ef48c785.png">
Even though `runhouse.login` uses a default `interactive=False`:
https://github.com/run-house/runhouse/blob/560a52880a333e17e8a1aca01c1048f4527fc375/runhouse/rns/login.py#L17-L25

This prevents me from automatically logging in on e.g. a Dockerfile build.

Resolving patch:
diff --git a/runhouse/main.py b/runhouse/main.py
--- a/runhouse/main.py
+++ b/runhouse/main.py
@@ -20,11 +20,27 @@
 
 
 @app.command()
-def login(token: Optional[str] = typer.Argument(None, help="Your Runhouse API token")):
+def login(
+    token: Optional[str] = typer.Argument(None, help="Your Runhouse API token"),
+    yes: Optional[bool] = typer.Option(
+        False, "--yes", "-y", help="Sets any confirmations to 'yes' automatically."
+    ),
+):
     """Login to Runhouse. Validates token provided, with options to upload or download stored secrets or config between
     local environment and Runhouse / Vault.
     """
-    valid_token: str = login_module.login(token=token, interactive=True, ret_token=True)
+    valid_token: str = (
+        login_module.login(
+            token=token,
+            download_config=True,
+            upload_config=True,
+            download_secrets=True,
+            upload_secrets=True,
+        )
+        if yes
+        else login_module.login(token=token, interactive=True, ret_token=True)
+    )
+
     if valid_token:
         webbrowser.open(
             f"{configs.get('api_server_url')}/dashboard?token={valid_token}"
diff --git a/runhouse/rns/login.py b/runhouse/rns/login.py
--- a/runhouse/rns/login.py
+++ b/runhouse/rns/login.py
@@ -21,14 +21,24 @@ def login(
     download_secrets: bool = None,
     upload_secrets: bool = None,
     ret_token: bool = False,
-    interactive: bool = False,
+    interactive: bool = None,
 ):
     """Login to Runhouse. Validates token provided, with options to upload or download stored secrets or config between
     local environment and Runhouse / Vault.
     """
     from runhouse import Secrets
 
-    if is_interactive() or interactive:
+    all_options_set = token and not any(
+        arg is None
+        for arg in (download_config, upload_config, download_secrets, upload_secrets)
+    )
+
+    if interactive is False and not token:
+        raise Exception(
+            "`interactive` can only be set to `False` if token is provided."
+        )
+
+    if interactive or (interactive is None and not all_options_set):
         from getpass import getpass
 
         from rich.console import Console
@@ -48,12 +58,12 @@ def login(
             if is_interactive()
             else f'{configs.get("api_server_url")}/dashboard/?option=token'
         )
-        console.print(
-            f"Retrieve your token :key: here to use :person_running: :house: Runhouse for "
-            f"secrets and artifact management: {link}",
-            style="bold yellow",
-        )
         if not token:
+            console.print(
+                f"Retrieve your token :key: here to use :person_running: :house: Runhouse for "
+                f"secrets and artifact management: {link}",
+                style="bold yellow",
+            )
             token = getpass("Token: ")
 
         download_config = (
@@ -132,6 +142,8 @@ def logout(
     )
     for provider in Secrets.enabled_providers():
         provider_name: str = provider.PROVIDER_NAME
+        if provider_name == "ssh":
+            continue
         provider_creds_path: Union[str, tuple] = provider.default_credentials_path()
 
         if interactive_session:


Test patch:
diff --git a/tests/test_login.py b/tests/test_login.py
--- a/tests/test_login.py
+++ b/tests/test_login.py
@@ -46,7 +46,7 @@ def test_login_flow_in_new_env():
     providers_in_vault = list(secrets_in_vault)
 
     # Login and download secrets stored in Vault into the new env
-    rh.login(token=token, download_secrets=True)
+    rh.login(token=token, download_secrets=True, interactive=False)
 
     # Once secrets are saved down to their local config, confirm we have sky enabled
     sky.check.check(quiet=True)