We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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 😄 


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `runhouse/rns/login.py`
Content:
```
1 import logging
2 from typing import Union
3 
4 import typer
5 
6 from runhouse.rh_config import configs, rns_client
7 
8 logger = logging.getLogger(__name__)
9 
10 
11 def is_interactive():
12     import __main__ as main
13 
14     return not hasattr(main, "__file__")
15 
16 
17 def login(
18     token: str = None,
19     download_config: bool = None,
20     upload_config: bool = None,
21     download_secrets: bool = None,
22     upload_secrets: bool = None,
23     ret_token: bool = False,
24     interactive: bool = False,
25 ):
26     """Login to Runhouse. Validates token provided, with options to upload or download stored secrets or config between
27     local environment and Runhouse / Vault.
28     """
29     from runhouse import Secrets
30 
31     if is_interactive() or interactive:
32         from getpass import getpass
33 
34         from rich.console import Console
35 
36         console = Console()
37         console.print(
38             """
39             ____              __                             @ @ @
40            / __ \__  ______  / /_  ____  __  __________     []___
41           / /_/ / / / / __ \/ __ \/ __ \/ / / / ___/ _ \   /    /\____    @@
42          / _, _/ /_/ / / / / / / / /_/ / /_/ (__  )  __/  /_/\_//____/\  @@@@
43         /_/ |_|\__,_/_/ /_/_/ /_/\____/\__,_/____/\___/   | || |||__|||   ||
44         """
45         )
46         link = (
47             f'[link={configs.get("api_server_url")}/dashboard/?option=token]https://api.run.house[/link]'
48             if is_interactive()
49             else f'{configs.get("api_server_url")}/dashboard/?option=token'
50         )
51         console.print(
52             f"Retrieve your token :key: here to use :person_running: :house: Runhouse for "
53             f"secrets and artifact management: {link}",
54             style="bold yellow",
55         )
56         if not token:
57             token = getpass("Token: ")
58 
59         download_config = (
60             download_config
61             if download_config is not None
62             else typer.confirm(
63                 "Download config from Runhouse to your local .rh folder?"
64             )
65         )
66         download_secrets = (
67             download_secrets
68             if download_secrets is not None
69             else typer.confirm(
70                 "Download secrets from Vault to your local Runhouse config?"
71             )
72         )
73         upload_config = (
74             upload_config
75             if upload_config is not None
76             else typer.confirm("Upload your local config to Runhouse?")
77         )
78         upload_secrets = (
79             upload_secrets
80             if upload_secrets is not None
81             else typer.confirm("Upload your enabled cloud provider secrets to Vault?")
82         )
83 
84     if token:
85         configs.set("token", token)
86 
87     if download_config:
88         configs.download_and_save_defaults()
89         # We need to fresh the RNSClient to use the newly loaded configs
90         rns_client.refresh_defaults()
91     elif upload_config:
92         configs.upload_defaults(defaults=configs.defaults_cache)
93     else:
94         # If we are not downloading or uploading config, we still want to make sure the token is valid
95         try:
96             configs.download_defaults()
97         except:
98             logger.error("Failed to validate token")
99             return None
100 
101     if download_secrets:
102         Secrets.download_into_env()
103 
104     if upload_secrets:
105         Secrets.extract_and_upload(interactive=interactive)
106 
107     logger.info("Successfully logged into Runhouse.")
108     if ret_token:
109         return token
110 
111 
112 def logout(
113     delete_loaded_secrets: bool = None,
114     delete_rh_config_file: bool = None,
115     interactive: bool = None,
116 ):
117     """Logout from Runhouse. Provides option to delete credentials from the Runhouse config and the underlying
118      credentials file. Token is also deleted from the config.
119 
120     Args:
121         delete_loaded_secrets (bool, optional): If True, deletes the provider credentials file. Defaults to None.
122         delete_rh_config_file (bool, optional): If True, deletes the rh config file. Defaults to None.
123         interactive (bool, optional): If True, runs the logout process in interactive mode. Defaults to None.
124 
125     Returns:
126         None
127     """
128     from runhouse import Secrets
129 
130     interactive_session: bool = (
131         interactive if interactive is not None else is_interactive()
132     )
133     for provider in Secrets.enabled_providers():
134         provider_name: str = provider.PROVIDER_NAME
135         provider_creds_path: Union[str, tuple] = provider.default_credentials_path()
136 
137         if interactive_session:
138             delete_loaded_secrets = typer.confirm(
139                 f"Delete credentials file for {provider_name}?"
140             )
141 
142         configs.delete(provider_name)
143 
144         if delete_loaded_secrets:
145             provider.delete_secrets_file(provider_creds_path)
146             logger.info(
147                 f"Deleted {provider_name} credentials file from path: {provider_creds_path}"
148             )
149 
150     # Delete token from rh config file
151     configs.delete(key="token")
152 
153     rh_config_path = configs.CONFIG_PATH
154     if not delete_rh_config_file and interactive_session:
155         delete_rh_config_file = typer.confirm("Delete your local Runhouse config file?")
156 
157     if delete_rh_config_file:
158         # Delete the credentials file on the file system
159         configs.delete_defaults(rh_config_path)
160         logger.info(f"Deleted Runhouse config file from path: {rh_config_path}")
161 
162     logger.info("Successfully logged out of Runhouse.")
163
```

Path: `runhouse/main.py`
Content:
```
1 import subprocess
2 import webbrowser
3 from typing import Optional
4 
5 import pkg_resources
6 import typer
7 from rich.console import Console
8 
9 from runhouse import cluster, configs
10 from runhouse.rns import (  # Need to rename it because it conflicts with the login command
11     login as login_module,
12 )
13 
14 # create an explicit Typer application
15 app = typer.Typer(add_completion=False)
16 state = {"verbose": False}
17 
18 # For printing with typer
19 console = Console()
20 
21 
22 @app.command()
23 def login(token: Optional[str] = typer.Argument(None, help="Your Runhouse API token")):
24     """Login to Runhouse. Validates token provided, with options to upload or download stored secrets or config between
25     local environment and Runhouse / Vault.
26     """
27     valid_token: str = login_module.login(token=token, interactive=True, ret_token=True)
28     if valid_token:
29         webbrowser.open(
30             f"{configs.get('api_server_url')}/dashboard?token={valid_token}"
31         )
32         raise typer.Exit()
33     else:
34         raise typer.Exit(code=1)
35 
36 
37 @app.command()
38 def logout():
39     """Logout of Runhouse. Provides options to delete locally configured secrets and local Runhouse configs"""
40     login_module.logout(interactive=True)
41     raise typer.Exit()
42 
43 
44 @app.command()
45 def notebook(
46     cluster_name: str, up: bool = typer.Option(False, help="Start the cluster")
47 ):
48     """Open a Jupyter notebook on a cluster."""
49     c = cluster(name=cluster_name)
50     if up:
51         c.up_if_not()
52     if not c.is_up():
53         console.print(
54             f"Cluster {cluster_name} is not up. Please run `runhouse notebook {cluster_name} --up`."
55         )
56         raise typer.Exit(1)
57     c.notebook()
58 
59 
60 @app.command()
61 def ssh(cluster_name: str, up: bool = typer.Option(False, help="Start the cluster")):
62     """SSH into a cluster created elsewhere (so `ssh cluster` doesn't work out of the box) or not yet up."""
63     c = cluster(name=cluster_name)
64     if up:
65         c.up_if_not()
66     if not c.is_up():
67         console.print(
68             f"Cluster {cluster_name} is not up. Please run `runhouse ssh {cluster_name} --up`."
69         )
70         raise typer.Exit(1)
71     subprocess.call(f"ssh {c.name}", shell=True)
72 
73 
74 @app.command()
75 def cancel(
76     cluster_name: str,
77     run_key: str,
78     force: Optional[bool] = typer.Option(False, help="Force cancel"),
79     all: Optional[bool] = typer.Option(False, help="Cancel all jobs"),
80 ):
81     """Cancel a run on a cluster."""
82     c = cluster(name=cluster_name)
83     c.cancel(run_key, force=force, all=all)
84 
85 
86 @app.command()
87 def logs(
88     cluster_name: str,
89     run_key: str,
90     print_results: Optional[bool] = typer.Option(False, help="Print results"),
91 ):
92     """Get logs from a run on a cluster."""
93     c = cluster(name=cluster_name)
94     res = c.get(run_key, stream_logs=True)
95     if print_results:
96         console.print(res)
97 
98 
99 def load_cluster(cluster_name: str):
100     """Load a cluster from RNS into the local environment, e.g. to be able to ssh."""
101     c = cluster(name=cluster_name)
102     if not c.address:
103         c.update_from_sky_status(dryrun=True)
104 
105 
106 @app.command()
107 def restart_grpc(
108     cluster_name: str,
109     restart_ray: bool = typer.Option(False, help="Restart the Ray runtime"),
110     resync_rh: bool = typer.Option(False, help="Resync the Runhouse package"),
111 ):
112     """Restart the gRPC server on a cluster."""
113     c = cluster(name=cluster_name)
114     c.restart_grpc_server(resync_rh=resync_rh, restart_ray=restart_ray)
115 
116 
117 @app.callback()
118 def main(verbose: bool = False):
119     """
120     Runhouse CLI
121     """
122     if verbose:
123         name = "runhouse"
124         version = pkg_resources.get_distribution(name).version
125         console.print(f"{name}=={version}", style="bold green")
126         state["verbose"] = True
127
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 @@
     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 @@
             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 @@
     )
     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:

{"golden_diff": "diff --git a/runhouse/main.py b/runhouse/main.py\n--- a/runhouse/main.py\n+++ b/runhouse/main.py\n@@ -20,11 +20,27 @@\n \n \n @app.command()\n-def login(token: Optional[str] = typer.Argument(None, help=\"Your Runhouse API token\")):\n+def login(\n+    token: Optional[str] = typer.Argument(None, help=\"Your Runhouse API token\"),\n+    yes: Optional[bool] = typer.Option(\n+        False, \"--yes\", \"-y\", help=\"Sets any confirmations to 'yes' automatically.\"\n+    ),\n+):\n     \"\"\"Login to Runhouse. Validates token provided, with options to upload or download stored secrets or config between\n     local environment and Runhouse / Vault.\n     \"\"\"\n-    valid_token: str = login_module.login(token=token, interactive=True, ret_token=True)\n+    valid_token: str = (\n+        login_module.login(\n+            token=token,\n+            download_config=True,\n+            upload_config=True,\n+            download_secrets=True,\n+            upload_secrets=True,\n+        )\n+        if yes\n+        else login_module.login(token=token, interactive=True, ret_token=True)\n+    )\n+\n     if valid_token:\n         webbrowser.open(\n             f\"{configs.get('api_server_url')}/dashboard?token={valid_token}\"\ndiff --git a/runhouse/rns/login.py b/runhouse/rns/login.py\n--- a/runhouse/rns/login.py\n+++ b/runhouse/rns/login.py\n@@ -21,14 +21,24 @@\n     download_secrets: bool = None,\n     upload_secrets: bool = None,\n     ret_token: bool = False,\n-    interactive: bool = False,\n+    interactive: bool = None,\n ):\n     \"\"\"Login to Runhouse. Validates token provided, with options to upload or download stored secrets or config between\n     local environment and Runhouse / Vault.\n     \"\"\"\n     from runhouse import Secrets\n \n-    if is_interactive() or interactive:\n+    all_options_set = token and not any(\n+        arg is None\n+        for arg in (download_config, upload_config, download_secrets, upload_secrets)\n+    )\n+\n+    if interactive is False and not token:\n+        raise Exception(\n+            \"`interactive` can only be set to `False` if token is provided.\"\n+        )\n+\n+    if interactive or (interactive is None and not all_options_set):\n         from getpass import getpass\n \n         from rich.console import Console\n@@ -48,12 +58,12 @@\n             if is_interactive()\n             else f'{configs.get(\"api_server_url\")}/dashboard/?option=token'\n         )\n-        console.print(\n-            f\"Retrieve your token :key: here to use :person_running: :house: Runhouse for \"\n-            f\"secrets and artifact management: {link}\",\n-            style=\"bold yellow\",\n-        )\n         if not token:\n+            console.print(\n+                f\"Retrieve your token :key: here to use :person_running: :house: Runhouse for \"\n+                f\"secrets and artifact management: {link}\",\n+                style=\"bold yellow\",\n+            )\n             token = getpass(\"Token: \")\n \n         download_config = (\n@@ -132,6 +142,8 @@\n     )\n     for provider in Secrets.enabled_providers():\n         provider_name: str = provider.PROVIDER_NAME\n+        if provider_name == \"ssh\":\n+            continue\n         provider_creds_path: Union[str, tuple] = provider.default_credentials_path()\n \n         if interactive_session:\n", "issue": "Consider adding `-y` option to `runhouse login` CLI command\n## Feature\r\nSimple use case is logging in with `system` command instead of Python API:\r\n```\r\n!runhouse login [TOKEN]\r\n```\r\nCurrently, the CLI is hardcoded with `interactive=True`:\r\nhttps://github.com/run-house/runhouse/blob/560a52880a333e17e8a1aca01c1048f4527fc375/runhouse/main.py#L27\r\n\r\n## Motivation\r\nIt's a minor quality of life improvement.\r\n\r\n## Ideal Solution\r\nSee above\r\n\r\n## Additional context\r\nExcited to get Runhouse integration up on NatML \ud83d\ude04 \r\n\n", "before_files": [{"content": "import logging\nfrom typing import Union\n\nimport typer\n\nfrom runhouse.rh_config import configs, rns_client\n\nlogger = logging.getLogger(__name__)\n\n\ndef is_interactive():\n    import __main__ as main\n\n    return not hasattr(main, \"__file__\")\n\n\ndef login(\n    token: str = None,\n    download_config: bool = None,\n    upload_config: bool = None,\n    download_secrets: bool = None,\n    upload_secrets: bool = None,\n    ret_token: bool = False,\n    interactive: bool = False,\n):\n    \"\"\"Login to Runhouse. Validates token provided, with options to upload or download stored secrets or config between\n    local environment and Runhouse / Vault.\n    \"\"\"\n    from runhouse import Secrets\n\n    if is_interactive() or interactive:\n        from getpass import getpass\n\n        from rich.console import Console\n\n        console = Console()\n        console.print(\n            \"\"\"\n            ____              __                             @ @ @\n           / __ \\__  ______  / /_  ____  __  __________     []___\n          / /_/ / / / / __ \\/ __ \\/ __ \\/ / / / ___/ _ \\   /    /\\____    @@\n         / _, _/ /_/ / / / / / / / /_/ / /_/ (__  )  __/  /_/\\_//____/\\  @@@@\n        /_/ |_|\\__,_/_/ /_/_/ /_/\\____/\\__,_/____/\\___/   | || |||__|||   ||\n        \"\"\"\n        )\n        link = (\n            f'[link={configs.get(\"api_server_url\")}/dashboard/?option=token]https://api.run.house[/link]'\n            if is_interactive()\n            else f'{configs.get(\"api_server_url\")}/dashboard/?option=token'\n        )\n        console.print(\n            f\"Retrieve your token :key: here to use :person_running: :house: Runhouse for \"\n            f\"secrets and artifact management: {link}\",\n            style=\"bold yellow\",\n        )\n        if not token:\n            token = getpass(\"Token: \")\n\n        download_config = (\n            download_config\n            if download_config is not None\n            else typer.confirm(\n                \"Download config from Runhouse to your local .rh folder?\"\n            )\n        )\n        download_secrets = (\n            download_secrets\n            if download_secrets is not None\n            else typer.confirm(\n                \"Download secrets from Vault to your local Runhouse config?\"\n            )\n        )\n        upload_config = (\n            upload_config\n            if upload_config is not None\n            else typer.confirm(\"Upload your local config to Runhouse?\")\n        )\n        upload_secrets = (\n            upload_secrets\n            if upload_secrets is not None\n            else typer.confirm(\"Upload your enabled cloud provider secrets to Vault?\")\n        )\n\n    if token:\n        configs.set(\"token\", token)\n\n    if download_config:\n        configs.download_and_save_defaults()\n        # We need to fresh the RNSClient to use the newly loaded configs\n        rns_client.refresh_defaults()\n    elif upload_config:\n        configs.upload_defaults(defaults=configs.defaults_cache)\n    else:\n        # If we are not downloading or uploading config, we still want to make sure the token is valid\n        try:\n            configs.download_defaults()\n        except:\n            logger.error(\"Failed to validate token\")\n            return None\n\n    if download_secrets:\n        Secrets.download_into_env()\n\n    if upload_secrets:\n        Secrets.extract_and_upload(interactive=interactive)\n\n    logger.info(\"Successfully logged into Runhouse.\")\n    if ret_token:\n        return token\n\n\ndef logout(\n    delete_loaded_secrets: bool = None,\n    delete_rh_config_file: bool = None,\n    interactive: bool = None,\n):\n    \"\"\"Logout from Runhouse. Provides option to delete credentials from the Runhouse config and the underlying\n     credentials file. Token is also deleted from the config.\n\n    Args:\n        delete_loaded_secrets (bool, optional): If True, deletes the provider credentials file. Defaults to None.\n        delete_rh_config_file (bool, optional): If True, deletes the rh config file. Defaults to None.\n        interactive (bool, optional): If True, runs the logout process in interactive mode. Defaults to None.\n\n    Returns:\n        None\n    \"\"\"\n    from runhouse import Secrets\n\n    interactive_session: bool = (\n        interactive if interactive is not None else is_interactive()\n    )\n    for provider in Secrets.enabled_providers():\n        provider_name: str = provider.PROVIDER_NAME\n        provider_creds_path: Union[str, tuple] = provider.default_credentials_path()\n\n        if interactive_session:\n            delete_loaded_secrets = typer.confirm(\n                f\"Delete credentials file for {provider_name}?\"\n            )\n\n        configs.delete(provider_name)\n\n        if delete_loaded_secrets:\n            provider.delete_secrets_file(provider_creds_path)\n            logger.info(\n                f\"Deleted {provider_name} credentials file from path: {provider_creds_path}\"\n            )\n\n    # Delete token from rh config file\n    configs.delete(key=\"token\")\n\n    rh_config_path = configs.CONFIG_PATH\n    if not delete_rh_config_file and interactive_session:\n        delete_rh_config_file = typer.confirm(\"Delete your local Runhouse config file?\")\n\n    if delete_rh_config_file:\n        # Delete the credentials file on the file system\n        configs.delete_defaults(rh_config_path)\n        logger.info(f\"Deleted Runhouse config file from path: {rh_config_path}\")\n\n    logger.info(\"Successfully logged out of Runhouse.\")\n", "path": "runhouse/rns/login.py"}, {"content": "import subprocess\nimport webbrowser\nfrom typing import Optional\n\nimport pkg_resources\nimport typer\nfrom rich.console import Console\n\nfrom runhouse import cluster, configs\nfrom runhouse.rns import (  # Need to rename it because it conflicts with the login command\n    login as login_module,\n)\n\n# create an explicit Typer application\napp = typer.Typer(add_completion=False)\nstate = {\"verbose\": False}\n\n# For printing with typer\nconsole = Console()\n\n\n@app.command()\ndef login(token: Optional[str] = typer.Argument(None, help=\"Your Runhouse API token\")):\n    \"\"\"Login to Runhouse. Validates token provided, with options to upload or download stored secrets or config between\n    local environment and Runhouse / Vault.\n    \"\"\"\n    valid_token: str = login_module.login(token=token, interactive=True, ret_token=True)\n    if valid_token:\n        webbrowser.open(\n            f\"{configs.get('api_server_url')}/dashboard?token={valid_token}\"\n        )\n        raise typer.Exit()\n    else:\n        raise typer.Exit(code=1)\n\n\n@app.command()\ndef logout():\n    \"\"\"Logout of Runhouse. Provides options to delete locally configured secrets and local Runhouse configs\"\"\"\n    login_module.logout(interactive=True)\n    raise typer.Exit()\n\n\n@app.command()\ndef notebook(\n    cluster_name: str, up: bool = typer.Option(False, help=\"Start the cluster\")\n):\n    \"\"\"Open a Jupyter notebook on a cluster.\"\"\"\n    c = cluster(name=cluster_name)\n    if up:\n        c.up_if_not()\n    if not c.is_up():\n        console.print(\n            f\"Cluster {cluster_name} is not up. Please run `runhouse notebook {cluster_name} --up`.\"\n        )\n        raise typer.Exit(1)\n    c.notebook()\n\n\n@app.command()\ndef ssh(cluster_name: str, up: bool = typer.Option(False, help=\"Start the cluster\")):\n    \"\"\"SSH into a cluster created elsewhere (so `ssh cluster` doesn't work out of the box) or not yet up.\"\"\"\n    c = cluster(name=cluster_name)\n    if up:\n        c.up_if_not()\n    if not c.is_up():\n        console.print(\n            f\"Cluster {cluster_name} is not up. Please run `runhouse ssh {cluster_name} --up`.\"\n        )\n        raise typer.Exit(1)\n    subprocess.call(f\"ssh {c.name}\", shell=True)\n\n\n@app.command()\ndef cancel(\n    cluster_name: str,\n    run_key: str,\n    force: Optional[bool] = typer.Option(False, help=\"Force cancel\"),\n    all: Optional[bool] = typer.Option(False, help=\"Cancel all jobs\"),\n):\n    \"\"\"Cancel a run on a cluster.\"\"\"\n    c = cluster(name=cluster_name)\n    c.cancel(run_key, force=force, all=all)\n\n\n@app.command()\ndef logs(\n    cluster_name: str,\n    run_key: str,\n    print_results: Optional[bool] = typer.Option(False, help=\"Print results\"),\n):\n    \"\"\"Get logs from a run on a cluster.\"\"\"\n    c = cluster(name=cluster_name)\n    res = c.get(run_key, stream_logs=True)\n    if print_results:\n        console.print(res)\n\n\ndef load_cluster(cluster_name: str):\n    \"\"\"Load a cluster from RNS into the local environment, e.g. to be able to ssh.\"\"\"\n    c = cluster(name=cluster_name)\n    if not c.address:\n        c.update_from_sky_status(dryrun=True)\n\n\n@app.command()\ndef restart_grpc(\n    cluster_name: str,\n    restart_ray: bool = typer.Option(False, help=\"Restart the Ray runtime\"),\n    resync_rh: bool = typer.Option(False, help=\"Resync the Runhouse package\"),\n):\n    \"\"\"Restart the gRPC server on a cluster.\"\"\"\n    c = cluster(name=cluster_name)\n    c.restart_grpc_server(resync_rh=resync_rh, restart_ray=restart_ray)\n\n\n@app.callback()\ndef main(verbose: bool = False):\n    \"\"\"\n    Runhouse CLI\n    \"\"\"\n    if verbose:\n        name = \"runhouse\"\n        version = pkg_resources.get_distribution(name).version\n        console.print(f\"{name}=={version}\", style=\"bold green\")\n        state[\"verbose\"] = True\n", "path": "runhouse/main.py"}], "after_files": [{"content": "import logging\nfrom typing import Union\n\nimport typer\n\nfrom runhouse.rh_config import configs, rns_client\n\nlogger = logging.getLogger(__name__)\n\n\ndef is_interactive():\n    import __main__ as main\n\n    return not hasattr(main, \"__file__\")\n\n\ndef login(\n    token: str = None,\n    download_config: bool = None,\n    upload_config: bool = None,\n    download_secrets: bool = None,\n    upload_secrets: bool = None,\n    ret_token: bool = False,\n    interactive: bool = None,\n):\n    \"\"\"Login to Runhouse. Validates token provided, with options to upload or download stored secrets or config between\n    local environment and Runhouse / Vault.\n    \"\"\"\n    from runhouse import Secrets\n\n    all_options_set = token and not any(\n        arg is None\n        for arg in (download_config, upload_config, download_secrets, upload_secrets)\n    )\n\n    if interactive is False and not token:\n        raise Exception(\n            \"`interactive` can only be set to `False` if token is provided.\"\n        )\n\n    if interactive or (interactive is None and not all_options_set):\n        from getpass import getpass\n\n        from rich.console import Console\n\n        console = Console()\n        console.print(\n            \"\"\"\n            ____              __                             @ @ @\n           / __ \\__  ______  / /_  ____  __  __________     []___\n          / /_/ / / / / __ \\/ __ \\/ __ \\/ / / / ___/ _ \\   /    /\\____    @@\n         / _, _/ /_/ / / / / / / / /_/ / /_/ (__  )  __/  /_/\\_//____/\\  @@@@\n        /_/ |_|\\__,_/_/ /_/_/ /_/\\____/\\__,_/____/\\___/   | || |||__|||   ||\n        \"\"\"\n        )\n        link = (\n            f'[link={configs.get(\"api_server_url\")}/dashboard/?option=token]https://api.run.house[/link]'\n            if is_interactive()\n            else f'{configs.get(\"api_server_url\")}/dashboard/?option=token'\n        )\n        if not token:\n            console.print(\n                f\"Retrieve your token :key: here to use :person_running: :house: Runhouse for \"\n                f\"secrets and artifact management: {link}\",\n                style=\"bold yellow\",\n            )\n            token = getpass(\"Token: \")\n\n        download_config = (\n            download_config\n            if download_config is not None\n            else typer.confirm(\n                \"Download config from Runhouse to your local .rh folder?\"\n            )\n        )\n        download_secrets = (\n            download_secrets\n            if download_secrets is not None\n            else typer.confirm(\n                \"Download secrets from Vault to your local Runhouse config?\"\n            )\n        )\n        upload_config = (\n            upload_config\n            if upload_config is not None\n            else typer.confirm(\"Upload your local config to Runhouse?\")\n        )\n        upload_secrets = (\n            upload_secrets\n            if upload_secrets is not None\n            else typer.confirm(\"Upload your enabled cloud provider secrets to Vault?\")\n        )\n\n    if token:\n        configs.set(\"token\", token)\n\n    if download_config:\n        configs.download_and_save_defaults()\n        # We need to fresh the RNSClient to use the newly loaded configs\n        rns_client.refresh_defaults()\n    elif upload_config:\n        configs.upload_defaults(defaults=configs.defaults_cache)\n    else:\n        # If we are not downloading or uploading config, we still want to make sure the token is valid\n        try:\n            configs.download_defaults()\n        except:\n            logger.error(\"Failed to validate token\")\n            return None\n\n    if download_secrets:\n        Secrets.download_into_env()\n\n    if upload_secrets:\n        Secrets.extract_and_upload(interactive=interactive)\n\n    logger.info(\"Successfully logged into Runhouse.\")\n    if ret_token:\n        return token\n\n\ndef logout(\n    delete_loaded_secrets: bool = None,\n    delete_rh_config_file: bool = None,\n    interactive: bool = None,\n):\n    \"\"\"Logout from Runhouse. Provides option to delete credentials from the Runhouse config and the underlying\n     credentials file. Token is also deleted from the config.\n\n    Args:\n        delete_loaded_secrets (bool, optional): If True, deletes the provider credentials file. Defaults to None.\n        delete_rh_config_file (bool, optional): If True, deletes the rh config file. Defaults to None.\n        interactive (bool, optional): If True, runs the logout process in interactive mode. Defaults to None.\n\n    Returns:\n        None\n    \"\"\"\n    from runhouse import Secrets\n\n    interactive_session: bool = (\n        interactive if interactive is not None else is_interactive()\n    )\n    for provider in Secrets.enabled_providers():\n        provider_name: str = provider.PROVIDER_NAME\n        if provider_name == \"ssh\":\n            continue\n        provider_creds_path: Union[str, tuple] = provider.default_credentials_path()\n\n        if interactive_session:\n            delete_loaded_secrets = typer.confirm(\n                f\"Delete credentials file for {provider_name}?\"\n            )\n\n        configs.delete(provider_name)\n\n        if delete_loaded_secrets:\n            provider.delete_secrets_file(provider_creds_path)\n            logger.info(\n                f\"Deleted {provider_name} credentials file from path: {provider_creds_path}\"\n            )\n\n    # Delete token from rh config file\n    configs.delete(key=\"token\")\n\n    rh_config_path = configs.CONFIG_PATH\n    if not delete_rh_config_file and interactive_session:\n        delete_rh_config_file = typer.confirm(\"Delete your local Runhouse config file?\")\n\n    if delete_rh_config_file:\n        # Delete the credentials file on the file system\n        configs.delete_defaults(rh_config_path)\n        logger.info(f\"Deleted Runhouse config file from path: {rh_config_path}\")\n\n    logger.info(\"Successfully logged out of Runhouse.\")\n", "path": "runhouse/rns/login.py"}, {"content": "import subprocess\nimport webbrowser\nfrom typing import Optional\n\nimport pkg_resources\nimport typer\nfrom rich.console import Console\n\nfrom runhouse import cluster, configs\nfrom runhouse.rns import (  # Need to rename it because it conflicts with the login command\n    login as login_module,\n)\n\n# create an explicit Typer application\napp = typer.Typer(add_completion=False)\nstate = {\"verbose\": False}\n\n# For printing with typer\nconsole = Console()\n\n\n@app.command()\ndef login(\n    token: Optional[str] = typer.Argument(None, help=\"Your Runhouse API token\"),\n    yes: Optional[bool] = typer.Option(\n        False, \"--yes\", \"-y\", help=\"Sets any confirmations to 'yes' automatically.\"\n    ),\n):\n    \"\"\"Login to Runhouse. Validates token provided, with options to upload or download stored secrets or config between\n    local environment and Runhouse / Vault.\n    \"\"\"\n    valid_token: str = (\n        login_module.login(\n            token=token,\n            download_config=True,\n            upload_config=True,\n            download_secrets=True,\n            upload_secrets=True,\n        )\n        if yes\n        else login_module.login(token=token, interactive=True, ret_token=True)\n    )\n\n    if valid_token:\n        webbrowser.open(\n            f\"{configs.get('api_server_url')}/dashboard?token={valid_token}\"\n        )\n        raise typer.Exit()\n    else:\n        raise typer.Exit(code=1)\n\n\n@app.command()\ndef logout():\n    \"\"\"Logout of Runhouse. Provides options to delete locally configured secrets and local Runhouse configs\"\"\"\n    login_module.logout(interactive=True)\n    raise typer.Exit()\n\n\n@app.command()\ndef notebook(\n    cluster_name: str, up: bool = typer.Option(False, help=\"Start the cluster\")\n):\n    \"\"\"Open a Jupyter notebook on a cluster.\"\"\"\n    c = cluster(name=cluster_name)\n    if up:\n        c.up_if_not()\n    if not c.is_up():\n        console.print(\n            f\"Cluster {cluster_name} is not up. Please run `runhouse notebook {cluster_name} --up`.\"\n        )\n        raise typer.Exit(1)\n    c.notebook()\n\n\n@app.command()\ndef ssh(cluster_name: str, up: bool = typer.Option(False, help=\"Start the cluster\")):\n    \"\"\"SSH into a cluster created elsewhere (so `ssh cluster` doesn't work out of the box) or not yet up.\"\"\"\n    c = cluster(name=cluster_name)\n    if up:\n        c.up_if_not()\n    if not c.is_up():\n        console.print(\n            f\"Cluster {cluster_name} is not up. Please run `runhouse ssh {cluster_name} --up`.\"\n        )\n        raise typer.Exit(1)\n    subprocess.call(f\"ssh {c.name}\", shell=True)\n\n\n@app.command()\ndef cancel(\n    cluster_name: str,\n    run_key: str,\n    force: Optional[bool] = typer.Option(False, help=\"Force cancel\"),\n    all: Optional[bool] = typer.Option(False, help=\"Cancel all jobs\"),\n):\n    \"\"\"Cancel a run on a cluster.\"\"\"\n    c = cluster(name=cluster_name)\n    c.cancel(run_key, force=force, all=all)\n\n\n@app.command()\ndef logs(\n    cluster_name: str,\n    run_key: str,\n    print_results: Optional[bool] = typer.Option(False, help=\"Print results\"),\n):\n    \"\"\"Get logs from a run on a cluster.\"\"\"\n    c = cluster(name=cluster_name)\n    res = c.get(run_key, stream_logs=True)\n    if print_results:\n        console.print(res)\n\n\ndef load_cluster(cluster_name: str):\n    \"\"\"Load a cluster from RNS into the local environment, e.g. to be able to ssh.\"\"\"\n    c = cluster(name=cluster_name)\n    if not c.address:\n        c.update_from_sky_status(dryrun=True)\n\n\n@app.command()\ndef restart_grpc(\n    cluster_name: str,\n    restart_ray: bool = typer.Option(False, help=\"Restart the Ray runtime\"),\n    resync_rh: bool = typer.Option(False, help=\"Resync the Runhouse package\"),\n):\n    \"\"\"Restart the gRPC server on a cluster.\"\"\"\n    c = cluster(name=cluster_name)\n    c.restart_grpc_server(resync_rh=resync_rh, restart_ray=restart_ray)\n\n\n@app.callback()\ndef main(verbose: bool = False):\n    \"\"\"\n    Runhouse CLI\n    \"\"\"\n    if verbose:\n        name = \"runhouse\"\n        version = pkg_resources.get_distribution(name).version\n        console.print(f\"{name}=={version}\", style=\"bold green\")\n        state[\"verbose\"] = True\n", "path": "runhouse/main.py"}]}