diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt
index 8f265fe..62fe4bb 100644
--- a/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt
+++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt
@@ -204,4 +204,8 @@ class CoderRemoteEnvironment(
      * Companion to equals, for sets.
      */
     override fun hashCode(): Int = id.hashCode()
+
+    override fun toString(): String {
+        return "CoderRemoteEnvironment(name='$name')"
+    }
 }
diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt
index fd0ad57..c9acc57 100644
--- a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt
+++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt
@@ -20,13 +20,16 @@ import com.jetbrains.toolbox.api.remoteDev.RemoteProvider
 import com.jetbrains.toolbox.api.remoteDev.RemoteProviderEnvironment
 import com.jetbrains.toolbox.api.ui.actions.ActionDescription
 import com.jetbrains.toolbox.api.ui.components.UiPage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
-import kotlinx.coroutines.delay
+import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.selects.onTimeout
+import kotlinx.coroutines.selects.select
 import okhttp3.OkHttpClient
 import java.net.URI
 import java.net.URL
@@ -35,18 +38,20 @@ import kotlin.time.Duration.Companion.seconds
 import com.jetbrains.toolbox.api.ui.components.AccountDropdownField as DropDownMenu
 import com.jetbrains.toolbox.api.ui.components.AccountDropdownField as dropDownFactory
 
+@OptIn(ExperimentalCoroutinesApi::class)
 class CoderRemoteProvider(
     private val context: CoderToolboxContext,
     private val httpClient: OkHttpClient,
 ) : RemoteProvider("Coder") {
     // Current polling job.
     private var pollJob: Job? = null
-    private var lastEnvironments: Set<CoderRemoteEnvironment>? = null
+    private val lastEnvironments = mutableSetOf<CoderRemoteEnvironment>()
 
-    private val cSettings = context.settingsStore.readOnly()
+    private val settings = context.settingsStore.readOnly()
 
     // Create our services from the Toolbox ones.
-    private val settingsPage: CoderSettingsPage = CoderSettingsPage(context)
+    private val triggerSshConfig = Channel<Boolean>(Channel.CONFLATED)
+    private val settingsPage: CoderSettingsPage = CoderSettingsPage(context, triggerSshConfig)
     private val dialogUi = DialogUi(context)
 
     // The REST client, if we are signed in
@@ -92,7 +97,7 @@ class CoderRemoteProvider(
                         }?.map { agent ->
                             // If we have an environment already, update that.
                             val env = CoderRemoteEnvironment(context, client, ws, agent)
-                            lastEnvironments?.firstOrNull { it == env }?.let {
+                            lastEnvironments.firstOrNull { it == env }?.let {
                                 it.update(ws, agent)
                                 it
                             } ?: env
@@ -107,9 +112,7 @@ class CoderRemoteProvider(
 
                 // Reconfigure if a new environment is found.
                 // TODO@JB: Should we use the add/remove listeners instead?
-                val newEnvironments = lastEnvironments
-                    ?.let { resolvedEnvironments.subtract(it) }
-                    ?: resolvedEnvironments
+                val newEnvironments = resolvedEnvironments.subtract(lastEnvironments)
                 if (newEnvironments.isNotEmpty()) {
                     context.logger.info("Found new environment(s), reconfiguring CLI: $newEnvironments")
                     cli.configSsh(newEnvironments.map { it.name }.toSet())
@@ -124,8 +127,10 @@ class CoderRemoteProvider(
                         true
                     }
                 }
-
-                lastEnvironments = resolvedEnvironments
+                lastEnvironments.apply {
+                    clear()
+                    addAll(resolvedEnvironments)
+                }
             } catch (_: CancellationException) {
                 context.logger.debug("${client.url} polling loop canceled")
                 break
@@ -136,7 +141,17 @@ class CoderRemoteProvider(
                 break
             }
             // TODO: Listening on a web socket might be better?
-            delay(5.seconds)
+            select<Unit> {
+                onTimeout(5.seconds) {
+                    context.logger.trace("workspace poller waked up by the 5 seconds timeout")
+                }
+                triggerSshConfig.onReceive { shouldTrigger ->
+                    if (shouldTrigger) {
+                        context.logger.trace("workspace poller waked up because it should reconfigure the ssh configurations")
+                        cli.configSsh(lastEnvironments.map { it.name }.toSet())
+                    }
+                }
+            }
         }
     }
 
@@ -178,7 +193,7 @@ class CoderRemoteProvider(
     override fun close() {
         pollJob?.cancel()
         client?.close()
-        lastEnvironments = null
+        lastEnvironments.clear()
         environments.value = LoadableState.Value(emptyList())
         isInitialized.update { false }
     }
@@ -270,7 +285,7 @@ class CoderRemoteProvider(
             var autologinEx: Exception? = null
             context.secrets.lastToken.let { lastToken ->
                 context.secrets.lastDeploymentURL.let { lastDeploymentURL ->
-                    if (autologin && lastDeploymentURL.isNotBlank() && (lastToken.isNotBlank() || !cSettings.requireTokenAuth)) {
+                    if (autologin && lastDeploymentURL.isNotBlank() && (lastToken.isNotBlank() || !settings.requireTokenAuth)) {
                         try {
                             return createConnectPage(URL(lastDeploymentURL), lastToken)
                         } catch (ex: Exception) {
@@ -342,7 +357,7 @@ class CoderRemoteProvider(
         if (it.isNotBlank() && context.secrets.lastDeploymentURL == deploymentURL.toString()) {
             it to SettingSource.LAST_USED
         } else {
-            cSettings.token(deploymentURL)
+            settings.token(deploymentURL)
         }
     }
 
diff --git a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt
index d112c18..d97caf8 100644
--- a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt
+++ b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt
@@ -6,7 +6,7 @@ import com.coder.toolbox.cli.ex.ResponseException
 import com.coder.toolbox.cli.ex.SSHConfigFormatException
 import com.coder.toolbox.sdk.v2.models.Workspace
 import com.coder.toolbox.sdk.v2.models.WorkspaceAgent
-import com.coder.toolbox.settings.CoderSettings
+import com.coder.toolbox.settings.ReadOnlyCoderSettings
 import com.coder.toolbox.util.CoderHostnameVerifier
 import com.coder.toolbox.util.InvalidVersionException
 import com.coder.toolbox.util.OS
@@ -125,7 +125,7 @@ class CoderCLIManager(
     private val deploymentURL: URL,
     private val logger: Logger,
     // Plugin configuration.
-    private val settings: CoderSettings,
+    private val settings: ReadOnlyCoderSettings,
     // If the binary directory is not writable, this can be used to force the
     // manager to download to the data directory instead.
     forceDownloadToData: Boolean = false,
@@ -267,21 +267,21 @@ class CoderCLIManager(
                 "--url",
                 escape(deploymentURL.toString()),
                 if (!settings.headerCommand.isNullOrBlank()) "--header-command" else null,
-                if (!settings.headerCommand.isNullOrBlank()) escapeSubcommand(settings.headerCommand) else null,
+                if (!settings.headerCommand.isNullOrBlank()) escapeSubcommand(settings.headerCommand!!) else null,
                 "ssh",
                 "--stdio",
                 if (settings.disableAutostart && feats.disableAutostart) "--disable-autostart" else null,
             )
         val proxyArgs = baseArgs + listOfNotNull(
             if (!settings.sshLogDirectory.isNullOrBlank()) "--log-dir" else null,
-            if (!settings.sshLogDirectory.isNullOrBlank()) escape(settings.sshLogDirectory) else null,
+            if (!settings.sshLogDirectory.isNullOrBlank()) escape(settings.sshLogDirectory!!) else null,
             if (feats.reportWorkspaceUsage) "--usage-app=jetbrains" else null,
         )
         val backgroundProxyArgs =
             baseArgs + listOfNotNull(if (feats.reportWorkspaceUsage) "--usage-app=disable" else null)
         val extraConfig =
             if (!settings.sshConfigOptions.isNullOrBlank()) {
-                "\n" + settings.sshConfigOptions.prependIndent("  ")
+                "\n" + settings.sshConfigOptions!!.prependIndent("  ")
             } else {
                 ""
             }
diff --git a/src/main/kotlin/com/coder/toolbox/settings/CoderSettings.kt b/src/main/kotlin/com/coder/toolbox/settings/CoderSettings.kt
deleted file mode 100644
index 867159c..0000000
--- a/src/main/kotlin/com/coder/toolbox/settings/CoderSettings.kt
+++ /dev/null
@@ -1,244 +0,0 @@
-package com.coder.toolbox.settings
-
-import com.coder.toolbox.util.expand
-import com.coder.toolbox.util.safeHost
-import com.coder.toolbox.util.toURL
-import com.coder.toolbox.util.withPath
-import java.net.URL
-import java.nio.file.Files
-import java.nio.file.Path
-
-data class CoderSettings(
-    val defaultURL: String?,
-
-    /**
-     * Used to download the Coder CLI which is necessary to proxy SSH
-     * connections.  The If-None-Match header will be set to the SHA1 of the CLI
-     * and can be used for caching.  Absolute URLs will be used as-is; otherwise
-     * this value will be resolved against the deployment domain.  Defaults to
-     * the plugin's data directory.
-     */
-    val binarySource: String?,
-
-    /**
-     * Directories are created here that store the CLI for each domain to which
-     * the plugin connects.   Defaults to the data directory.
-     */
-    val binaryDirectory: String?,
-
-    val defaultCliBinaryNameByOsAndArch: String,
-
-    /**
-     * Configurable CLI binary name with extension, dependent on OS and arch
-     */
-    val binaryName: String,
-
-    /**
-     * Where to save plugin data like the Coder binary (if not configured with
-     * binaryDirectory) and the deployment URL and session token.
-     */
-    val dataDirectory: String?,
-
-    /**
-     * Coder plugin's global data directory.
-     */
-    val globalDataDirectory: String,
-
-    /**
-     * Coder plugin's global config dir
-     */
-    val globalConfigDir: String,
-
-    /**
-     * Whether to allow the plugin to download the CLI if the current one is out
-     * of date or does not exist.
-     */
-    val enableDownloads: Boolean,
-
-    /**
-     * Whether to allow the plugin to fall back to the data directory when the
-     * CLI directory is not writable.
-     */
-    val enableBinaryDirectoryFallback: Boolean,
-
-    /**
-     * An external command that outputs additional HTTP headers added to all
-     * requests. The command must output each header as `key=value` on its own
-     * line. The following environment variables will be available to the
-     * process: CODER_URL.
-     */
-    val headerCommand: String?,
-
-    /**
-     * Optional TLS settings
-     */
-    val tls: CTLSSettings,
-
-    /**
-     * Whether login should be done with a token
-     */
-    val requireTokenAuth: Boolean = tls.certPath.isNullOrBlank() || tls.keyPath.isNullOrBlank(),
-
-    /**
-     * Whether to add --disable-autostart to the proxy command.  This works
-     * around issues on macOS where it periodically wakes and Gateway
-     * reconnects, keeping the workspace constantly up.
-     */
-    val disableAutostart: Boolean,
-
-    val isSshWildcardConfigEnabled: Boolean,
-
-    /**
-     * The location of the SSH config.  Defaults to ~/.ssh/config.
-     */
-    val sshConfigPath: String,
-
-    /**
-     * Value for --log-dir.
-     */
-    val sshLogDirectory: String?,
-
-    /**
-     * Extra SSH config options
-     */
-    val sshConfigOptions: String?,
-) {
-
-    /**
-     * Given a deployment URL, try to find a token for it if required.
-     */
-    fun token(deploymentURL: URL): Pair<String, SettingSource>? {
-        // No need to bother if we do not need token auth anyway.
-        if (!requireTokenAuth) {
-            return null
-        }
-        // Try the deployment's config directory.  This could exist if someone
-        // has entered a URL that they are not currently connected to, but have
-        // connected to in the past.
-        val (_, deploymentToken) = readConfig(dataDir(deploymentURL).resolve("config"))
-        if (!deploymentToken.isNullOrBlank()) {
-            return deploymentToken to SettingSource.DEPLOYMENT_CONFIG
-        }
-        // Try the global config directory, in case they previously set up the
-        // CLI with this URL.
-        val (configUrl, configToken) = readConfig(Path.of(globalConfigDir))
-        if (configUrl == deploymentURL.toString() && !configToken.isNullOrBlank()) {
-            return configToken to SettingSource.CONFIG
-        }
-        return null
-    }
-
-    /**
-     * Where the specified deployment should put its data.
-     */
-    fun dataDir(url: URL): Path {
-        dataDirectory.let {
-            val dir =
-                if (it.isNullOrBlank()) {
-                    Path.of(globalDataDirectory)
-                } else {
-                    Path.of(expand(it))
-                }
-            return withHost(dir, url).toAbsolutePath()
-        }
-    }
-
-    /**
-     * From where the specified deployment should download the binary.
-     */
-    fun binSource(url: URL): URL {
-        binarySource.let {
-            return if (it.isNullOrBlank()) {
-                url.withPath("/bin/$defaultCliBinaryNameByOsAndArch")
-            } else {
-                try {
-                    it.toURL()
-                } catch (_: Exception) {
-                    url.withPath(it) // Assume a relative path.
-                }
-            }
-        }
-    }
-
-    /**
-     * To where the specified deployment should download the binary.
-     */
-    fun binPath(
-        url: URL,
-        forceDownloadToData: Boolean = false,
-    ): Path {
-        binaryDirectory.let {
-            val dir =
-                if (forceDownloadToData || it.isNullOrBlank()) {
-                    dataDir(url)
-                } else {
-                    withHost(Path.of(expand(it)), url)
-                }
-            return dir.resolve(binaryName).toAbsolutePath()
-        }
-    }
-
-    /**
-     * Return the URL and token from the config, if they exist.
-     */
-    fun readConfig(dir: Path): Pair<String?, String?> {
-//        logger.info("Reading config from $dir")
-        return try {
-            Files.readString(dir.resolve("url"))
-        } catch (e: Exception) {
-            // SSH has not been configured yet, or using some other authorization mechanism.
-            null
-        } to
-                try {
-                    Files.readString(dir.resolve("session"))
-                } catch (e: Exception) {
-                    // SSH has not been configured yet, or using some other authorization mechanism.
-                    null
-                }
-    }
-
-    /**
-     * Append the host to the path.  For example, foo/bar could become
-     * foo/bar/dev.coder.com-8080.
-     */
-    private fun withHost(
-        path: Path,
-        url: URL,
-    ): Path {
-        val host = if (url.port > 0) "${url.safeHost()}-${url.port}" else url.safeHost()
-        return path.resolve(host)
-    }
-
-}
-
-/**
- * Consolidated TLS settings.
- */
-data class CTLSSettings(
-    /**
-     * Optionally set this to the path of a certificate to use for TLS
-     * connections. The certificate should be in X.509 PEM format.
-     */
-    val certPath: String?,
-
-    /**
-     * Optionally set this to the path of the private key that corresponds to
-     * the above cert path to use for TLS connections. The key should be in
-     * X.509 PEM format.
-     */
-    val keyPath: String?,
-
-    /**
-     * Optionally set this to the path of a file containing certificates for an
-     * alternate certificate authority used to verify TLS certs returned by the
-     * Coder service. The file should be in X.509 PEM format.
-     */
-    val caPath: String?,
-
-    /**
-     * Optionally set this to an alternate hostname used for verifying TLS
-     * connections. This is useful when the hostname used to connect to the
-     * Coder service does not match the hostname in the TLS certificate.
-     */
-    val altHostname: String?,
-)
\ No newline at end of file
diff --git a/src/main/kotlin/com/coder/toolbox/settings/ReadOnlyCoderSettings.kt b/src/main/kotlin/com/coder/toolbox/settings/ReadOnlyCoderSettings.kt
new file mode 100644
index 0000000..25568d3
--- /dev/null
+++ b/src/main/kotlin/com/coder/toolbox/settings/ReadOnlyCoderSettings.kt
@@ -0,0 +1,174 @@
+package com.coder.toolbox.settings
+
+import java.net.URL
+import java.nio.file.Path
+
+/**
+ * Read-only interface for accessing Coder settings
+ */
+interface ReadOnlyCoderSettings {
+    /**
+     * The default URL to show in the connection window.
+     */
+    val defaultURL: String?
+
+    /**
+     * Used to download the Coder CLI which is necessary to proxy SSH
+     * connections.  The If-None-Match header will be set to the SHA1 of the CLI
+     * and can be used for caching.  Absolute URLs will be used as-is; otherwise
+     * this value will be resolved against the deployment domain.  Defaults to
+     * the plugin's data directory.
+     */
+    val binarySource: String?
+
+    /**
+     * Directories are created here that store the CLI for each domain to which
+     * the plugin connects.   Defaults to the data directory.
+     */
+    val binaryDirectory: String?
+
+    /**
+     * Default CLI binary name based on OS and architecture
+     */
+    val defaultCliBinaryNameByOsAndArch: String
+
+    /**
+     * Configurable CLI binary name with extension, dependent on OS and arch
+     */
+    val binaryName: String
+
+    /**
+     * Where to save plugin data like the Coder binary (if not configured with
+     * binaryDirectory) and the deployment URL and session token.
+     */
+    val dataDirectory: String?
+
+    /**
+     * Coder plugin's global data directory.
+     */
+    val globalDataDirectory: String
+
+    /**
+     * Coder plugin's global config dir
+     */
+    val globalConfigDir: String
+
+    /**
+     * Whether to allow the plugin to download the CLI if the current one is out
+     * of date or does not exist.
+     */
+    val enableDownloads: Boolean
+
+    /**
+     * Whether to allow the plugin to fall back to the data directory when the
+     * CLI directory is not writable.
+     */
+    val enableBinaryDirectoryFallback: Boolean
+
+    /**
+     * An external command that outputs additional HTTP headers added to all
+     * requests. The command must output each header as `key=value` on its own
+     * line. The following environment variables will be available to the
+     * process: CODER_URL.
+     */
+    val headerCommand: String?
+
+    /**
+     * Optional TLS settings
+     */
+    val tls: ReadOnlyTLSSettings
+
+    /**
+     * Whether login should be done with a token
+     */
+    val requireTokenAuth: Boolean
+
+    /**
+     * Whether to add --disable-autostart to the proxy command.  This works
+     * around issues on macOS where it periodically wakes and Gateway
+     * reconnects, keeping the workspace constantly up.
+     */
+    val disableAutostart: Boolean
+
+    /**
+     * Whether SSH wildcard config is enabled
+     */
+    val isSshWildcardConfigEnabled: Boolean
+
+    /**
+     * The location of the SSH config.  Defaults to ~/.ssh/config.
+     */
+    val sshConfigPath: String
+
+    /**
+     * Value for --log-dir.
+     */
+    val sshLogDirectory: String?
+
+    /**
+     * Extra SSH config options
+     */
+    val sshConfigOptions: String?
+
+    /**
+     * The default URL to show in the connection window.
+     */
+    fun defaultURL(): Pair<String, SettingSource>?
+
+    /**
+     * Given a deployment URL, try to find a token for it if required.
+     */
+    fun token(deploymentURL: URL): Pair<String, SettingSource>?
+
+    /**
+     * Where the specified deployment should put its data.
+     */
+    fun dataDir(url: URL): Path
+
+    /**
+     * From where the specified deployment should download the binary.
+     */
+    fun binSource(url: URL): URL
+
+    /**
+     * To where the specified deployment should download the binary.
+     */
+    fun binPath(url: URL, forceDownloadToData: Boolean = false): Path
+
+    /**
+     * Return the URL and token from the config, if they exist.
+     */
+    fun readConfig(dir: Path): Pair<String?, String?>
+}
+
+/**
+ * Read-only interface for TLS settings
+ */
+interface ReadOnlyTLSSettings {
+    /**
+     * Optionally set this to the path of a certificate to use for TLS
+     * connections. The certificate should be in X.509 PEM format.
+     */
+    val certPath: String?
+
+    /**
+     * Optionally set this to the path of the private key that corresponds to
+     * the above cert path to use for TLS connections. The key should be in
+     * X.509 PEM format.
+     */
+    val keyPath: String?
+
+    /**
+     * Optionally set this to the path of a file containing certificates for an
+     * alternate certificate authority used to verify TLS certs returned by the
+     * Coder service. The file should be in X.509 PEM format.
+     */
+    val caPath: String?
+
+    /**
+     * Optionally set this to an alternate hostname used for verifying TLS
+     * connections. This is useful when the hostname used to connect to the
+     * Coder service does not match the hostname in the TLS certificate.
+     */
+    val altHostname: String?
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/coder/toolbox/store/CoderSettingsStore.kt b/src/main/kotlin/com/coder/toolbox/store/CoderSettingsStore.kt
index e5b96a1..92c08d0 100644
--- a/src/main/kotlin/com/coder/toolbox/store/CoderSettingsStore.kt
+++ b/src/main/kotlin/com/coder/toolbox/store/CoderSettingsStore.kt
@@ -1,62 +1,82 @@
 package com.coder.toolbox.store
 
-import com.coder.toolbox.settings.CTLSSettings
-import com.coder.toolbox.settings.CoderSettings
 import com.coder.toolbox.settings.Environment
+import com.coder.toolbox.settings.ReadOnlyCoderSettings
+import com.coder.toolbox.settings.ReadOnlyTLSSettings
 import com.coder.toolbox.settings.SettingSource
 import com.coder.toolbox.util.Arch
 import com.coder.toolbox.util.OS
+import com.coder.toolbox.util.expand
 import com.coder.toolbox.util.getArch
 import com.coder.toolbox.util.getOS
+import com.coder.toolbox.util.safeHost
+import com.coder.toolbox.util.toURL
+import com.coder.toolbox.util.withPath
 import com.jetbrains.toolbox.api.core.PluginSettingsStore
 import com.jetbrains.toolbox.api.core.diagnostics.Logger
+import java.net.URL
+import java.nio.file.Files
 import java.nio.file.Path
 import java.nio.file.Paths
 
+
 class CoderSettingsStore(
     private val store: PluginSettingsStore,
     private val env: Environment = Environment(),
     private val logger: Logger
-) {
-    private var backingSettings = CoderSettings(
-        defaultURL = store[DEFAULT_URL],
-        binarySource = store[BINARY_SOURCE],
-        binaryDirectory = store[BINARY_DIRECTORY],
-        defaultCliBinaryNameByOsAndArch = getCoderCLIForOS(getOS(), getArch()),
-        binaryName = store[BINARY_NAME] ?: getCoderCLIForOS(getOS(), getArch()),
-        dataDirectory = store[DATA_DIRECTORY],
-        globalDataDirectory = getDefaultGlobalDataDir().normalize().toString(),
-        globalConfigDir = getDefaultGlobalConfigDir().normalize().toString(),
-        enableDownloads = store[ENABLE_DOWNLOADS]?.toBooleanStrictOrNull() ?: true,
-        enableBinaryDirectoryFallback = store[ENABLE_BINARY_DIR_FALLBACK]?.toBooleanStrictOrNull() ?: false,
-        headerCommand = store[HEADER_COMMAND],
-        tls = CTLSSettings(
+) : ReadOnlyCoderSettings {
+
+    // Internal TLS settings implementation
+    private class TLSSettings(
+        override val certPath: String?,
+        override val keyPath: String?,
+        override val caPath: String?,
+        override val altHostname: String?
+    ) : ReadOnlyTLSSettings
+
+    // Properties implementation
+    override val defaultURL: String? get() = store[DEFAULT_URL]
+    override val binarySource: String? get() = store[BINARY_SOURCE]
+    override val binaryDirectory: String? get() = store[BINARY_DIRECTORY]
+    override val defaultCliBinaryNameByOsAndArch: String get() = getCoderCLIForOS(getOS(), getArch())
+    override val binaryName: String get() = store[BINARY_NAME] ?: getCoderCLIForOS(getOS(), getArch())
+    override val dataDirectory: String? get() = store[DATA_DIRECTORY]
+    override val globalDataDirectory: String get() = getDefaultGlobalDataDir().normalize().toString()
+    override val globalConfigDir: String get() = getDefaultGlobalConfigDir().normalize().toString()
+    override val enableDownloads: Boolean get() = store[ENABLE_DOWNLOADS]?.toBooleanStrictOrNull() ?: true
+    override val enableBinaryDirectoryFallback: Boolean
+        get() = store[ENABLE_BINARY_DIR_FALLBACK]?.toBooleanStrictOrNull() ?: false
+    override val headerCommand: String? get() = store[HEADER_COMMAND]
+    override val tls: ReadOnlyTLSSettings
+        get() = TLSSettings(
             certPath = store[TLS_CERT_PATH],
             keyPath = store[TLS_KEY_PATH],
             caPath = store[TLS_CA_PATH],
             altHostname = store[TLS_ALTERNATE_HOSTNAME]
-        ),
-        disableAutostart = store[DISABLE_AUTOSTART]?.toBooleanStrictOrNull() ?: (getOS() == OS.MAC),
-        isSshWildcardConfigEnabled = store[ENABLE_SSH_WILDCARD_CONFIG]?.toBooleanStrictOrNull() ?: true,
-        sshConfigPath = store[SSH_CONFIG_PATH].takeUnless { it.isNullOrEmpty() }
-            ?: Path.of(System.getProperty("user.home")).resolve(".ssh/config").normalize().toString(),
-        sshLogDirectory = store[SSH_LOG_DIR],
-        sshConfigOptions = store[SSH_CONFIG_OPTIONS].takeUnless { it.isNullOrEmpty() } ?: env.get(
-            CODER_SSH_CONFIG_OPTIONS
         )
-    )
+    override val requireTokenAuth: Boolean get() = tls.certPath.isNullOrBlank() || tls.keyPath.isNullOrBlank()
+    override val disableAutostart: Boolean
+        get() = store[DISABLE_AUTOSTART]?.toBooleanStrictOrNull() ?: (getOS() == OS.MAC)
+    override val isSshWildcardConfigEnabled: Boolean
+        get() = store[ENABLE_SSH_WILDCARD_CONFIG]?.toBooleanStrictOrNull() ?: true
+    override val sshConfigPath: String
+        get() = store[SSH_CONFIG_PATH].takeUnless { it.isNullOrEmpty() }
+            ?: Path.of(System.getProperty("user.home")).resolve(".ssh/config").normalize().toString()
+    override val sshLogDirectory: String? get() = store[SSH_LOG_DIR]
+    override val sshConfigOptions: String?
+        get() = store[SSH_CONFIG_OPTIONS].takeUnless { it.isNullOrEmpty() } ?: env.get(CODER_SSH_CONFIG_OPTIONS)
 
     /**
      * The default URL to show in the connection window.
      */
-    fun defaultURL(): Pair<String, SettingSource>? {
+    override fun defaultURL(): Pair<String, SettingSource>? {
         val envURL = env.get(CODER_URL)
-        if (!backingSettings.defaultURL.isNullOrEmpty()) {
-            return backingSettings.defaultURL!! to SettingSource.SETTINGS
+        if (!defaultURL.isNullOrEmpty()) {
+            return defaultURL!! to SettingSource.SETTINGS
         } else if (envURL.isNotBlank()) {
             return envURL to SettingSource.ENVIRONMENT
         } else {
-            val (configUrl, _) = backingSettings.readConfig(Path.of(backingSettings.globalConfigDir))
+            val (configUrl, _) = readConfig(Path.of(globalConfigDir))
             if (!configUrl.isNullOrBlank()) {
                 return configUrl to SettingSource.CONFIG
             }
@@ -65,77 +85,154 @@ class CoderSettingsStore(
     }
 
     /**
-     * Read-only access to the settings
+     * Given a deployment URL, try to find a token for it if required.
      */
-    fun readOnly(): CoderSettings = backingSettings
+    override fun token(deploymentURL: URL): Pair<String, SettingSource>? {
+        // No need to bother if we do not need token auth anyway.
+        if (!requireTokenAuth) {
+            return null
+        }
+        // Try the deployment's config directory.  This could exist if someone
+        // has entered a URL that they are not currently connected to, but have
+        // connected to in the past.
+        val (_, deploymentToken) = readConfig(dataDir(deploymentURL).resolve("config"))
+        if (!deploymentToken.isNullOrBlank()) {
+            return deploymentToken to SettingSource.DEPLOYMENT_CONFIG
+        }
+        // Try the global config directory, in case they previously set up the
+        // CLI with this URL.
+        val (configUrl, configToken) = readConfig(Path.of(globalConfigDir))
+        if (configUrl == deploymentURL.toString() && !configToken.isNullOrBlank()) {
+            return configToken to SettingSource.CONFIG
+        }
+        return null
+    }
 
+    /**
+     * Where the specified deployment should put its data.
+     */
+    override fun dataDir(url: URL): Path {
+        dataDirectory.let {
+            val dir =
+                if (it.isNullOrBlank()) {
+                    Path.of(globalDataDirectory)
+                } else {
+                    Path.of(expand(it))
+                }
+            return withHost(dir, url).toAbsolutePath()
+        }
+    }
+
+    /**
+     * From where the specified deployment should download the binary.
+     */
+    override fun binSource(url: URL): URL {
+        binarySource.let {
+            return if (it.isNullOrBlank()) {
+                url.withPath("/bin/$defaultCliBinaryNameByOsAndArch")
+            } else {
+                try {
+                    it.toURL()
+                } catch (_: Exception) {
+                    url.withPath(it) // Assume a relative path.
+                }
+            }
+        }
+    }
+
+    /**
+     * To where the specified deployment should download the binary.
+     */
+    override fun binPath(
+        url: URL,
+        forceDownloadToData: Boolean,
+    ): Path {
+        binaryDirectory.let {
+            val dir =
+                if (forceDownloadToData || it.isNullOrBlank()) {
+                    dataDir(url)
+                } else {
+                    withHost(Path.of(expand(it)), url)
+                }
+            return dir.resolve(binaryName).toAbsolutePath()
+        }
+    }
+
+    /**
+     * Return the URL and token from the config, if they exist.
+     */
+    override fun readConfig(dir: Path): Pair<String?, String?> {
+        return try {
+            Files.readString(dir.resolve("url"))
+        } catch (e: Exception) {
+            // SSH has not been configured yet, or using some other authorization mechanism.
+            null
+        } to
+                try {
+                    Files.readString(dir.resolve("session"))
+                } catch (e: Exception) {
+                    // SSH has not been configured yet, or using some other authorization mechanism.
+                    null
+                }
+    }
+
+    // a readonly cast
+    fun readOnly(): ReadOnlyCoderSettings = this
+
+    // Write operations
     fun updateBinarySource(source: String) {
-        backingSettings = backingSettings.copy(binarySource = source)
         store[BINARY_SOURCE] = source
     }
 
     fun updateBinaryDirectory(dir: String) {
-        backingSettings = backingSettings.copy(binaryDirectory = dir)
         store[BINARY_DIRECTORY] = dir
     }
 
     fun updateDataDirectory(dir: String) {
-        backingSettings = backingSettings.copy(dataDirectory = dir)
         store[DATA_DIRECTORY] = dir
     }
 
     fun updateEnableDownloads(shouldEnableDownloads: Boolean) {
-        backingSettings = backingSettings.copy(enableDownloads = shouldEnableDownloads)
         store[ENABLE_DOWNLOADS] = shouldEnableDownloads.toString()
     }
 
     fun updateBinaryDirectoryFallback(shouldEnableBinDirFallback: Boolean) {
-        backingSettings = backingSettings.copy(enableBinaryDirectoryFallback = shouldEnableBinDirFallback)
         store[ENABLE_BINARY_DIR_FALLBACK] = shouldEnableBinDirFallback.toString()
     }
 
     fun updateHeaderCommand(cmd: String) {
-        backingSettings = backingSettings.copy(headerCommand = cmd)
         store[HEADER_COMMAND] = cmd
     }
 
     fun updateCertPath(path: String) {
-        backingSettings = backingSettings.copy(tls = backingSettings.tls.copy(certPath = path))
         store[TLS_CERT_PATH] = path
     }
 
     fun updateKeyPath(path: String) {
-        backingSettings = backingSettings.copy(tls = backingSettings.tls.copy(keyPath = path))
         store[TLS_KEY_PATH] = path
     }
 
     fun updateCAPath(path: String) {
-        backingSettings = backingSettings.copy(tls = backingSettings.tls.copy(caPath = path))
         store[TLS_CA_PATH] = path
     }
 
     fun updateAltHostname(hostname: String) {
-        backingSettings = backingSettings.copy(tls = backingSettings.tls.copy(altHostname = hostname))
         store[TLS_ALTERNATE_HOSTNAME] = hostname
     }
 
     fun updateDisableAutostart(shouldDisableAutostart: Boolean) {
-        backingSettings = backingSettings.copy(disableAutostart = shouldDisableAutostart)
         store[DISABLE_AUTOSTART] = shouldDisableAutostart.toString()
     }
 
     fun updateEnableSshWildcardConfig(enable: Boolean) {
-        backingSettings = backingSettings.copy(isSshWildcardConfigEnabled = enable)
         store[ENABLE_SSH_WILDCARD_CONFIG] = enable.toString()
     }
 
     fun updateSshLogDir(path: String) {
-        backingSettings = backingSettings.copy(sshLogDirectory = path)
         store[SSH_LOG_DIR] = path
     }
 
     fun updateSshConfigOptions(options: String) {
-        backingSettings = backingSettings.copy(sshConfigOptions = options)
         store[SSH_CONFIG_OPTIONS] = options
     }
 
@@ -210,4 +307,16 @@ class CoderSettingsStore(
                 }
         }
     }
-}
+
+    /**
+     * Append the host to the path.  For example, foo/bar could become
+     * foo/bar/dev.coder.com-8080.
+     */
+    private fun withHost(
+        path: Path,
+        url: URL,
+    ): Path {
+        val host = if (url.port > 0) "${url.safeHost()}-${url.port}" else url.safeHost()
+        return path.resolve(host)
+    }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/coder/toolbox/util/TLS.kt b/src/main/kotlin/com/coder/toolbox/util/TLS.kt
index 17952df..dac816e 100644
--- a/src/main/kotlin/com/coder/toolbox/util/TLS.kt
+++ b/src/main/kotlin/com/coder/toolbox/util/TLS.kt
@@ -1,6 +1,6 @@
 package com.coder.toolbox.util
 
-import com.coder.toolbox.settings.CTLSSettings
+import com.coder.toolbox.settings.ReadOnlyTLSSettings
 import okhttp3.internal.tls.OkHostnameVerifier
 import java.io.File
 import java.io.FileInputStream
@@ -81,7 +81,7 @@ fun sslContextFromPEMs(
     return sslContext
 }
 
-fun coderSocketFactory(settings: CTLSSettings): SSLSocketFactory {
+fun coderSocketFactory(settings: ReadOnlyTLSSettings): SSLSocketFactory {
     val sslContext = sslContextFromPEMs(settings.certPath, settings.keyPath, settings.caPath)
     if (settings.altHostname.isNullOrBlank()) {
         return sslContext.socketFactory
diff --git a/src/main/kotlin/com/coder/toolbox/views/CoderSettingsPage.kt b/src/main/kotlin/com/coder/toolbox/views/CoderSettingsPage.kt
index d4ac2c8..ff86c42 100644
--- a/src/main/kotlin/com/coder/toolbox/views/CoderSettingsPage.kt
+++ b/src/main/kotlin/com/coder/toolbox/views/CoderSettingsPage.kt
@@ -6,8 +6,11 @@ import com.jetbrains.toolbox.api.ui.components.CheckboxField
 import com.jetbrains.toolbox.api.ui.components.TextField
 import com.jetbrains.toolbox.api.ui.components.TextType
 import com.jetbrains.toolbox.api.ui.components.UiField
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.ClosedSendChannelException
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
 
 /**
  * A page for modifying Coder settings.
@@ -16,7 +19,8 @@ import kotlinx.coroutines.flow.StateFlow
  * TODO@JB: There is no scroll, and our settings do not fit.  As a consequence,
  *          I have not been able to test this page.
  */
-class CoderSettingsPage(context: CoderToolboxContext) : CoderPage(context, context.i18n.ptrl("Coder Settings"), false) {
+class CoderSettingsPage(context: CoderToolboxContext, triggerSshConfig: Channel<Boolean>) :
+    CoderPage(context, context.i18n.ptrl("Coder Settings"), false) {
     private val settings = context.settingsStore.readOnly()
 
     // TODO: Copy over the descriptions, holding until I can test this page.
@@ -87,7 +91,18 @@ class CoderSettingsPage(context: CoderToolboxContext) : CoderPage(context, conte
                 context.settingsStore.updateCAPath(tlsCAPathField.textState.value)
                 context.settingsStore.updateAltHostname(tlsAlternateHostnameField.textState.value)
                 context.settingsStore.updateDisableAutostart(disableAutostartField.checkedState.value)
+                val oldIsSshWildcardConfigEnabled = settings.isSshWildcardConfigEnabled
                 context.settingsStore.updateEnableSshWildcardConfig(enableSshWildCardConfig.checkedState.value)
+
+                if (enableSshWildCardConfig.checkedState.value != oldIsSshWildcardConfigEnabled) {
+                    context.cs.launch {
+                        try {
+                            triggerSshConfig.send(true)
+                            context.logger.info("Wildcard settings have been modified from $oldIsSshWildcardConfigEnabled to ${!oldIsSshWildcardConfigEnabled}, ssh config is going to be regenerated...")
+                        } catch (_: ClosedSendChannelException) {
+                        }
+                    }
+                }
                 context.settingsStore.updateSshLogDir(sshLogDirField.textState.value)
                 context.settingsStore.updateSshConfigOptions(sshExtraArgs.textState.value)
             }
diff --git a/src/main/kotlin/com/coder/toolbox/views/EnvironmentView.kt b/src/main/kotlin/com/coder/toolbox/views/EnvironmentView.kt
index 4f41eee..89ef3dd 100644
--- a/src/main/kotlin/com/coder/toolbox/views/EnvironmentView.kt
+++ b/src/main/kotlin/com/coder/toolbox/views/EnvironmentView.kt
@@ -3,7 +3,7 @@ package com.coder.toolbox.views
 import com.coder.toolbox.cli.CoderCLIManager
 import com.coder.toolbox.sdk.v2.models.Workspace
 import com.coder.toolbox.sdk.v2.models.WorkspaceAgent
-import com.coder.toolbox.settings.CoderSettings
+import com.coder.toolbox.settings.ReadOnlyCoderSettings
 import com.jetbrains.toolbox.api.remoteDev.environments.SshEnvironmentContentsView
 import com.jetbrains.toolbox.api.remoteDev.ssh.SshConnectionInfo
 import java.net.URL
@@ -17,7 +17,7 @@ import java.net.URL
  * SSH must be configured before this will work.
  */
 class EnvironmentView(
-    private val settings: CoderSettings,
+    private val settings: ReadOnlyCoderSettings,
     private val url: URL,
     private val workspace: Workspace,
     private val agent: WorkspaceAgent,