From a6d9332bab6f268b0f872bfc2109494298b8d897 Mon Sep 17 00:00:00 2001
From: Faur Ioan-Aurel <fioan89@gmail.com>
Date: Fri, 13 Jun 2025 00:21:33 +0300
Subject: [PATCH 1/8] chore: rename misleading method

The REST client doesn't do any authentication but rather
retrieves the user and build info - the user is already
authenticated via the API token. So `authenticate` is misleading here.
---
 .../kotlin/com/coder/toolbox/CoderRemoteProvider.kt  |  2 +-
 .../kotlin/com/coder/toolbox/cli/CoderCLIManager.kt  |  2 +-
 .../kotlin/com/coder/toolbox/sdk/CoderRestClient.kt  | 12 ++++++++----
 .../com/coder/toolbox/util/CoderProtocolHandler.kt   |  2 +-
 .../kotlin/com/coder/toolbox/views/ConnectStep.kt    |  2 +-
 5 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt
index d72b130..33d5cec 100644
--- a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt
+++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt
@@ -346,7 +346,7 @@ class CoderRemoteProvider(
         // Show sign in page if we have not configured the client yet.
         if (client == null) {
             val errorBuffer = mutableListOf<Throwable>()
-            // When coming back to the application, authenticate immediately.
+            // When coming back to the application, initializeSession immediately.
             val autologin = shouldDoAutoLogin()
             context.secrets.lastToken.let { lastToken ->
                 context.secrets.lastDeploymentURL.let { lastDeploymentURL ->
diff --git a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt
index 2898179..54b505c 100644
--- a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt
+++ b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt
@@ -203,7 +203,7 @@ class CoderCLIManager(
     }
 
     /**
-     * Use the provided token to authenticate the CLI.
+     * Use the provided token to initializeSession the CLI.
      */
     fun login(token: String): String {
         logger.info("Storing CLI credentials in $coderConfigPath")
diff --git a/src/main/kotlin/com/coder/toolbox/sdk/CoderRestClient.kt b/src/main/kotlin/com/coder/toolbox/sdk/CoderRestClient.kt
index 9f619bc..365e1ed 100644
--- a/src/main/kotlin/com/coder/toolbox/sdk/CoderRestClient.kt
+++ b/src/main/kotlin/com/coder/toolbox/sdk/CoderRestClient.kt
@@ -131,12 +131,11 @@ open class CoderRestClient(
     }
 
     /**
-     * Authenticate and load information about the current user and the build
-     * version.
+     * Load information about the current user and the build version.
      *
      * @throws [APIResponseException].
      */
-    suspend fun authenticate(): User {
+    suspend fun initializeSession(): User {
         me = me()
         buildVersion = buildInfo().version
         return me
@@ -149,7 +148,12 @@ open class CoderRestClient(
     suspend fun me(): User {
         val userResponse = retroRestClient.me()
         if (!userResponse.isSuccessful) {
-            throw APIResponseException("authenticate", url, userResponse.code(), userResponse.parseErrorBody(moshi))
+            throw APIResponseException(
+                "initializeSession",
+                url,
+                userResponse.code(),
+                userResponse.parseErrorBody(moshi)
+            )
         }
 
         return userResponse.body()!!
diff --git a/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt b/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt
index 90f3465..f8359dc 100644
--- a/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt
+++ b/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt
@@ -143,7 +143,7 @@ open class CoderProtocolHandler(
             if (settings.requireTokenAuth) token else null,
             PluginManager.pluginInfo.version
         )
-        client.authenticate()
+        client.initializeSession()
         return client
     }
 
diff --git a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt
index 58e154e..c096052 100644
--- a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt
+++ b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt
@@ -86,7 +86,7 @@ class ConnectStep(
                 )
                 // allows interleaving with the back/cancel action
                 yield()
-                client.authenticate()
+                client.initializeSession()
                 statusField.textState.update { (context.i18n.ptrl("Checking Coder binary...")) }
                 val cli = ensureCLI(context, client.url, client.buildVersion)
                 // We only need to log in if we are using token-based auth.

From 01651f0d01189eb8201c1c89fc0fc17cbf1f30e4 Mon Sep 17 00:00:00 2001
From: Faur Ioan-Aurel <fioan89@gmail.com>
Date: Fri, 13 Jun 2025 00:22:40 +0300
Subject: [PATCH 2/8] chore: remove misleading message

---
 src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt
index c096052..25e9d3b 100644
--- a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt
+++ b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt
@@ -77,7 +77,6 @@ class ConnectStep(
         signInJob?.cancel()
         signInJob = context.cs.launch {
             try {
-                statusField.textState.update { (context.i18n.ptrl("Authenticating to ${AuthContext.url!!.host}...")) }
                 val client = CoderRestClient(
                     context,
                     AuthContext.url!!,

From afdc641795388c424ec0d2c87b7645905782df36 Mon Sep 17 00:00:00 2001
From: Faur Ioan-Aurel <fioan89@gmail.com>
Date: Fri, 13 Jun 2025 01:41:26 +0300
Subject: [PATCH 3/8] impl: visual text progress during Coder CLI downloading

This PR implements a mechanism to provide recurrent stats about
the number of the KB and MB of Coder CLI downloaded.
---
 CHANGELOG.md                                  |  4 ++
 .../com/coder/toolbox/cli/CoderCLIManager.kt  | 58 +++++++++++++++----
 .../toolbox/util/CoderProtocolHandler.kt      |  4 +-
 .../com/coder/toolbox/views/ConnectStep.kt    | 11 +++-
 .../coder/toolbox/cli/CoderCLIManagerTest.kt  | 36 ++++++------
 5 files changed, 80 insertions(+), 33 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a61780a..62352c0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
 
 ## Unreleased
 
+### Added
+
+- visual text progress during Coder CLI downloading
+
 ### Changed
 
 - the plugin will now remember the SSH connection state for each workspace, and it will try to automatically
diff --git a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt
index 54b505c..21b7bea 100644
--- a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt
+++ b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt
@@ -32,7 +32,7 @@ import java.net.HttpURLConnection
 import java.net.URL
 import java.nio.file.Files
 import java.nio.file.Path
-import java.nio.file.StandardCopyOption
+import java.nio.file.StandardOpenOption
 import java.util.zip.GZIPInputStream
 import javax.net.ssl.HttpsURLConnection
 
@@ -44,6 +44,8 @@ internal data class Version(
     @Json(name = "version") val version: String,
 )
 
+private const val DOWNLOADING_CODER_CLI = "Downloading Coder CLI..."
+
 /**
  * Do as much as possible to get a valid, up-to-date CLI.
  *
@@ -60,6 +62,7 @@ fun ensureCLI(
     context: CoderToolboxContext,
     deploymentURL: URL,
     buildVersion: String,
+    showTextProgress: (String) -> Unit
 ): CoderCLIManager {
     val settings = context.settingsStore.readOnly()
     val cli = CoderCLIManager(deploymentURL, context.logger, settings)
@@ -76,9 +79,10 @@ fun ensureCLI(
 
     // If downloads are enabled download the new version.
     if (settings.enableDownloads) {
-        context.logger.info("Downloading Coder CLI...")
+        context.logger.info(DOWNLOADING_CODER_CLI)
+        showTextProgress(DOWNLOADING_CODER_CLI)
         try {
-            cli.download()
+            cli.download(showTextProgress)
             return cli
         } catch (e: java.nio.file.AccessDeniedException) {
             // Might be able to fall back to the data directory.
@@ -98,8 +102,9 @@ fun ensureCLI(
     }
 
     if (settings.enableDownloads) {
-        context.logger.info("Downloading Coder CLI...")
-        dataCLI.download()
+        context.logger.info(DOWNLOADING_CODER_CLI)
+        showTextProgress(DOWNLOADING_CODER_CLI)
+        dataCLI.download(showTextProgress)
         return dataCLI
     }
 
@@ -137,7 +142,7 @@ class CoderCLIManager(
     /**
      * Download the CLI from the deployment if necessary.
      */
-    fun download(): Boolean {
+    fun download(showTextProgress: (String) -> Unit): Boolean {
         val eTag = getBinaryETag()
         val conn = remoteBinaryURL.openConnection() as HttpURLConnection
         if (!settings.headerCommand.isNullOrBlank()) {
@@ -163,12 +168,25 @@ class CoderCLIManager(
                 HttpURLConnection.HTTP_OK -> {
                     logger.info("Downloading binary to $localBinaryPath")
                     Files.createDirectories(localBinaryPath.parent)
-                    conn.inputStream.use {
-                        Files.copy(
-                            if (conn.contentEncoding == "gzip") GZIPInputStream(it) else it,
-                            localBinaryPath,
-                            StandardCopyOption.REPLACE_EXISTING,
-                        )
+                    val outputStream = Files.newOutputStream(
+                        localBinaryPath,
+                        StandardOpenOption.CREATE,
+                        StandardOpenOption.TRUNCATE_EXISTING
+                    )
+                    val sourceStream = if (conn.isGzip()) GZIPInputStream(conn.inputStream) else conn.inputStream
+
+                    val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
+                    var bytesRead: Int
+                    var totalRead = 0L
+
+                    sourceStream.use { source ->
+                        outputStream.use { sink ->
+                            while (source.read(buffer).also { bytesRead = it } != -1) {
+                                sink.write(buffer, 0, bytesRead)
+                                totalRead += bytesRead
+                                showTextProgress("Downloaded ${totalRead.toHumanReadableSize()}...")
+                            }
+                        }
                     }
                     if (getOS() != OS.WINDOWS) {
                         localBinaryPath.toFile().setExecutable(true)
@@ -178,6 +196,7 @@ class CoderCLIManager(
 
                 HttpURLConnection.HTTP_NOT_MODIFIED -> {
                     logger.info("Using cached binary at $localBinaryPath")
+                    showTextProgress("Using cached binary")
                     return false
                 }
             }
@@ -190,6 +209,21 @@ class CoderCLIManager(
         throw ResponseException("Unexpected response from $remoteBinaryURL", conn.responseCode)
     }
 
+    private fun HttpURLConnection.isGzip(): Boolean = this.contentEncoding.equals("gzip", ignoreCase = true)
+
+    fun Long.toHumanReadableSize(): String {
+        if (this < 1024) return "$this B"
+
+        val kb = this / 1024.0
+        if (kb < 1024) return String.format("%.1f KB", kb)
+
+        val mb = kb / 1024.0
+        if (mb < 1024) return String.format("%.1f MB", mb)
+
+        val gb = mb / 1024.0
+        return String.format("%.1f GB", gb)
+    }
+
     /**
      * Return the entity tag for the binary on disk, if any.
      */
diff --git a/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt b/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt
index f8359dc..cd54305 100644
--- a/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt
+++ b/src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt
@@ -24,6 +24,7 @@ import kotlin.time.Duration.Companion.seconds
 import kotlin.time.toJavaDuration
 
 private const val CAN_T_HANDLE_URI_TITLE = "Can't handle URI"
+private val noOpTextProgress: (String) -> Unit = { _ -> }
 
 @Suppress("UnstableApiUsage")
 open class CoderProtocolHandler(
@@ -304,7 +305,8 @@ open class CoderProtocolHandler(
         val cli = ensureCLI(
             context,
             deploymentURL.toURL(),
-            restClient.buildInfo().version
+            restClient.buildInfo().version,
+            noOpTextProgress
         )
 
         // We only need to log in if we are using token-based auth.
diff --git a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt
index 25e9d3b..4c1c81a 100644
--- a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt
+++ b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt
@@ -86,11 +86,16 @@ class ConnectStep(
                 // allows interleaving with the back/cancel action
                 yield()
                 client.initializeSession()
-                statusField.textState.update { (context.i18n.ptrl("Checking Coder binary...")) }
-                val cli = ensureCLI(context, client.url, client.buildVersion)
+                statusField.textState.update { (context.i18n.ptrl("Checking Coder CLI...")) }
+                val cli = ensureCLI(
+                    context, client.url,
+                    client.buildVersion
+                ) { progress ->
+                    statusField.textState.update { (context.i18n.pnotr(progress)) }
+                }
                 // We only need to log in if we are using token-based auth.
                 if (client.token != null) {
-                    statusField.textState.update { (context.i18n.ptrl("Configuring CLI...")) }
+                    statusField.textState.update { (context.i18n.ptrl("Configuring Coder CLI...")) }
                     // allows interleaving with the back/cancel action
                     yield()
                     cli.login(client.token)
diff --git a/src/test/kotlin/com/coder/toolbox/cli/CoderCLIManagerTest.kt b/src/test/kotlin/com/coder/toolbox/cli/CoderCLIManagerTest.kt
index 4603fda..6a4ce12 100644
--- a/src/test/kotlin/com/coder/toolbox/cli/CoderCLIManagerTest.kt
+++ b/src/test/kotlin/com/coder/toolbox/cli/CoderCLIManagerTest.kt
@@ -62,6 +62,8 @@ import kotlin.test.assertFalse
 import kotlin.test.assertNotEquals
 import kotlin.test.assertTrue
 
+private val noOpTextProgress: (String) -> Unit = { _ -> }
+
 internal class CoderCLIManagerTest {
     private val context = CoderToolboxContext(
         mockk<ToolboxUi>(),
@@ -145,7 +147,7 @@ internal class CoderCLIManagerTest {
         val ex =
             assertFailsWith(
                 exceptionClass = ResponseException::class,
-                block = { ccm.download() },
+                block = { ccm.download(noOpTextProgress) },
             )
         assertEquals(HttpURLConnection.HTTP_INTERNAL_ERROR, ex.code)
 
@@ -200,7 +202,7 @@ internal class CoderCLIManagerTest {
 
         assertFailsWith(
             exceptionClass = AccessDeniedException::class,
-            block = { ccm.download() },
+            block = { ccm.download(noOpTextProgress) },
         )
 
         srv.stop(0)
@@ -229,11 +231,11 @@ internal class CoderCLIManagerTest {
             ).readOnly(),
         )
 
-        assertTrue(ccm.download())
+        assertTrue(ccm.download(noOpTextProgress))
         assertDoesNotThrow { ccm.version() }
 
         // It should skip the second attempt.
-        assertFalse(ccm.download())
+        assertFalse(ccm.download(noOpTextProgress))
 
         // Make sure login failures propagate.
         assertFailsWith(
@@ -258,11 +260,11 @@ internal class CoderCLIManagerTest {
             ).readOnly(),
         )
 
-        assertEquals(true, ccm.download())
+        assertEquals(true, ccm.download(noOpTextProgress))
         assertEquals(SemVer(url.port.toLong(), 0, 0), ccm.version())
 
         // It should skip the second attempt.
-        assertEquals(false, ccm.download())
+        assertEquals(false, ccm.download(noOpTextProgress))
 
         // Should use the source override.
         ccm = CoderCLIManager(
@@ -278,7 +280,7 @@ internal class CoderCLIManagerTest {
             ).readOnly(),
         )
 
-        assertEquals(true, ccm.download())
+        assertEquals(true, ccm.download(noOpTextProgress))
         assertContains(ccm.localBinaryPath.toFile().readText(), "0.0.0")
 
         srv.stop(0)
@@ -326,7 +328,7 @@ internal class CoderCLIManagerTest {
         assertEquals("cli", ccm.localBinaryPath.toFile().readText())
         assertEquals(0, ccm.localBinaryPath.toFile().lastModified())
 
-        assertTrue(ccm.download())
+        assertTrue(ccm.download(noOpTextProgress))
 
         assertNotEquals("cli", ccm.localBinaryPath.toFile().readText())
         assertNotEquals(0, ccm.localBinaryPath.toFile().lastModified())
@@ -351,8 +353,8 @@ internal class CoderCLIManagerTest {
         val ccm1 = CoderCLIManager(url1, context.logger, settings)
         val ccm2 = CoderCLIManager(url2, context.logger, settings)
 
-        assertTrue(ccm1.download())
-        assertTrue(ccm2.download())
+        assertTrue(ccm1.download(noOpTextProgress))
+        assertTrue(ccm2.download(noOpTextProgress))
 
         srv1.stop(0)
         srv2.stop(0)
@@ -883,12 +885,12 @@ internal class CoderCLIManagerTest {
                 Result.ERROR -> {
                     assertFailsWith(
                         exceptionClass = AccessDeniedException::class,
-                        block = { ensureCLI(localContext, url, it.buildVersion) },
+                        block = { ensureCLI(localContext, url, it.buildVersion, noOpTextProgress) },
                     )
                 }
 
                 Result.NONE -> {
-                    val ccm = ensureCLI(localContext, url, it.buildVersion)
+                    val ccm = ensureCLI(localContext, url, it.buildVersion, noOpTextProgress)
                     assertEquals(settings.binPath(url), ccm.localBinaryPath)
                     assertFailsWith(
                         exceptionClass = ProcessInitException::class,
@@ -897,25 +899,25 @@ internal class CoderCLIManagerTest {
                 }
 
                 Result.DL_BIN -> {
-                    val ccm = ensureCLI(localContext, url, it.buildVersion)
+                    val ccm = ensureCLI(localContext, url, it.buildVersion, noOpTextProgress)
                     assertEquals(settings.binPath(url), ccm.localBinaryPath)
                     assertEquals(SemVer(url.port.toLong(), 0, 0), ccm.version())
                 }
 
                 Result.DL_DATA -> {
-                    val ccm = ensureCLI(localContext, url, it.buildVersion)
+                    val ccm = ensureCLI(localContext, url, it.buildVersion, noOpTextProgress)
                     assertEquals(settings.binPath(url, true), ccm.localBinaryPath)
                     assertEquals(SemVer(url.port.toLong(), 0, 0), ccm.version())
                 }
 
                 Result.USE_BIN -> {
-                    val ccm = ensureCLI(localContext, url, it.buildVersion)
+                    val ccm = ensureCLI(localContext, url, it.buildVersion, noOpTextProgress)
                     assertEquals(settings.binPath(url), ccm.localBinaryPath)
                     assertEquals(SemVer.parse(it.version ?: ""), ccm.version())
                 }
 
                 Result.USE_DATA -> {
-                    val ccm = ensureCLI(localContext, url, it.buildVersion)
+                    val ccm = ensureCLI(localContext, url, it.buildVersion, noOpTextProgress)
                     assertEquals(settings.binPath(url, true), ccm.localBinaryPath)
                     assertEquals(SemVer.parse(it.fallbackVersion ?: ""), ccm.version())
                 }
@@ -955,7 +957,7 @@ internal class CoderCLIManagerTest {
                     context.logger,
                 ).readOnly(),
             )
-            assertEquals(true, ccm.download())
+            assertEquals(true, ccm.download(noOpTextProgress))
             assertEquals(it.second, ccm.features, "version: ${it.first}")
 
             srv.stop(0)

From c65ac6c31941f98cba7ca43e80a9027f718c000a Mon Sep 17 00:00:00 2001
From: Faur Ioan-Aurel <fioan89@gmail.com>
Date: Tue, 17 Jun 2025 22:42:37 +0300
Subject: [PATCH 4/8] fix: delete previous binaries

Keeps the previous behavior which was removing the binary before download.
---
 src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt
index 21b7bea..401307d 100644
--- a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt
+++ b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt
@@ -167,6 +167,7 @@ class CoderCLIManager(
             when (conn.responseCode) {
                 HttpURLConnection.HTTP_OK -> {
                     logger.info("Downloading binary to $localBinaryPath")
+                    Files.deleteIfExists(localBinaryPath)
                     Files.createDirectories(localBinaryPath.parent)
                     val outputStream = Files.newOutputStream(
                         localBinaryPath,

From b95fc4c1421f3d1ce12bcedf52fe361c3aad82fb Mon Sep 17 00:00:00 2001
From: Faur Ioan-Aurel <fioan89@gmail.com>
Date: Wed, 18 Jun 2025 23:14:21 +0300
Subject: [PATCH 5/8] impl: report cli name and version

Alongside the content size downloaded
---
 .../com/coder/toolbox/cli/CoderCLIManager.kt  |  8 +++----
 .../coder/toolbox/cli/CoderCLIManagerTest.kt  | 23 ++++++++++---------
 2 files changed, 16 insertions(+), 15 deletions(-)

diff --git a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt
index 401307d..e4ef501 100644
--- a/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt
+++ b/src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt
@@ -82,7 +82,7 @@ fun ensureCLI(
         context.logger.info(DOWNLOADING_CODER_CLI)
         showTextProgress(DOWNLOADING_CODER_CLI)
         try {
-            cli.download(showTextProgress)
+            cli.download(buildVersion, showTextProgress)
             return cli
         } catch (e: java.nio.file.AccessDeniedException) {
             // Might be able to fall back to the data directory.
@@ -104,7 +104,7 @@ fun ensureCLI(
     if (settings.enableDownloads) {
         context.logger.info(DOWNLOADING_CODER_CLI)
         showTextProgress(DOWNLOADING_CODER_CLI)
-        dataCLI.download(showTextProgress)
+        dataCLI.download(buildVersion, showTextProgress)
         return dataCLI
     }
 
@@ -142,7 +142,7 @@ class CoderCLIManager(
     /**
      * Download the CLI from the deployment if necessary.
      */
-    fun download(showTextProgress: (String) -> Unit): Boolean {
+    fun download(buildVersion: String, showTextProgress: (String) -> Unit): Boolean {
         val eTag = getBinaryETag()
         val conn = remoteBinaryURL.openConnection() as HttpURLConnection
         if (!settings.headerCommand.isNullOrBlank()) {
@@ -185,7 +185,7 @@ class CoderCLIManager(
                             while (source.read(buffer).also { bytesRead = it } != -1) {
                                 sink.write(buffer, 0, bytesRead)
                                 totalRead += bytesRead
-                                showTextProgress("Downloaded ${totalRead.toHumanReadableSize()}...")
+                                showTextProgress("${settings.defaultCliBinaryNameByOsAndArch} $buildVersion - ${totalRead.toHumanReadableSize()} downloaded")
                             }
                         }
                     }
diff --git a/src/test/kotlin/com/coder/toolbox/cli/CoderCLIManagerTest.kt b/src/test/kotlin/com/coder/toolbox/cli/CoderCLIManagerTest.kt
index 6a4ce12..5c37c9e 100644
--- a/src/test/kotlin/com/coder/toolbox/cli/CoderCLIManagerTest.kt
+++ b/src/test/kotlin/com/coder/toolbox/cli/CoderCLIManagerTest.kt
@@ -62,6 +62,7 @@ import kotlin.test.assertFalse
 import kotlin.test.assertNotEquals
 import kotlin.test.assertTrue
 
+private const val VERSION_FOR_PROGRESS_REPORTING = "v2.23.1-devel+de07351b8"
 private val noOpTextProgress: (String) -> Unit = { _ -> }
 
 internal class CoderCLIManagerTest {
@@ -147,7 +148,7 @@ internal class CoderCLIManagerTest {
         val ex =
             assertFailsWith(
                 exceptionClass = ResponseException::class,
-                block = { ccm.download(noOpTextProgress) },
+                block = { ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress) },
             )
         assertEquals(HttpURLConnection.HTTP_INTERNAL_ERROR, ex.code)
 
@@ -202,7 +203,7 @@ internal class CoderCLIManagerTest {
 
         assertFailsWith(
             exceptionClass = AccessDeniedException::class,
-            block = { ccm.download(noOpTextProgress) },
+            block = { ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress) },
         )
 
         srv.stop(0)
@@ -231,11 +232,11 @@ internal class CoderCLIManagerTest {
             ).readOnly(),
         )
 
-        assertTrue(ccm.download(noOpTextProgress))
+        assertTrue(ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress))
         assertDoesNotThrow { ccm.version() }
 
         // It should skip the second attempt.
-        assertFalse(ccm.download(noOpTextProgress))
+        assertFalse(ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress))
 
         // Make sure login failures propagate.
         assertFailsWith(
@@ -260,11 +261,11 @@ internal class CoderCLIManagerTest {
             ).readOnly(),
         )
 
-        assertEquals(true, ccm.download(noOpTextProgress))
+        assertEquals(true, ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress))
         assertEquals(SemVer(url.port.toLong(), 0, 0), ccm.version())
 
         // It should skip the second attempt.
-        assertEquals(false, ccm.download(noOpTextProgress))
+        assertEquals(false, ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress))
 
         // Should use the source override.
         ccm = CoderCLIManager(
@@ -280,7 +281,7 @@ internal class CoderCLIManagerTest {
             ).readOnly(),
         )
 
-        assertEquals(true, ccm.download(noOpTextProgress))
+        assertEquals(true, ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress))
         assertContains(ccm.localBinaryPath.toFile().readText(), "0.0.0")
 
         srv.stop(0)
@@ -328,7 +329,7 @@ internal class CoderCLIManagerTest {
         assertEquals("cli", ccm.localBinaryPath.toFile().readText())
         assertEquals(0, ccm.localBinaryPath.toFile().lastModified())
 
-        assertTrue(ccm.download(noOpTextProgress))
+        assertTrue(ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress))
 
         assertNotEquals("cli", ccm.localBinaryPath.toFile().readText())
         assertNotEquals(0, ccm.localBinaryPath.toFile().lastModified())
@@ -353,8 +354,8 @@ internal class CoderCLIManagerTest {
         val ccm1 = CoderCLIManager(url1, context.logger, settings)
         val ccm2 = CoderCLIManager(url2, context.logger, settings)
 
-        assertTrue(ccm1.download(noOpTextProgress))
-        assertTrue(ccm2.download(noOpTextProgress))
+        assertTrue(ccm1.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress))
+        assertTrue(ccm2.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress))
 
         srv1.stop(0)
         srv2.stop(0)
@@ -957,7 +958,7 @@ internal class CoderCLIManagerTest {
                     context.logger,
                 ).readOnly(),
             )
-            assertEquals(true, ccm.download(noOpTextProgress))
+            assertEquals(true, ccm.download(VERSION_FOR_PROGRESS_REPORTING, noOpTextProgress))
             assertEquals(it.second, ccm.features, "version: ${it.first}")
 
             srv.stop(0)

From 9ab03a34d657deed8857aaa0d9903f0aa8c71170 Mon Sep 17 00:00:00 2001
From: Faur Ioan-Aurel <fioan89@gmail.com>
Date: Wed, 18 Jun 2025 23:16:22 +0300
Subject: [PATCH 6/8] impl: change page title to `Setting up Coder`

We are not really doing authentication, instead we download the cli,
we initialize the cli with the token, re-generate the ssh config file,
initialize a rest client with the deployment url and the token and
retrieve basic information about the deployment like the version and
the user authenticated by the token.
---
 src/main/kotlin/com/coder/toolbox/views/AuthWizardPage.kt | 2 +-
 src/main/resources/localization/defaultMessages.po        | 3 +++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/main/kotlin/com/coder/toolbox/views/AuthWizardPage.kt b/src/main/kotlin/com/coder/toolbox/views/AuthWizardPage.kt
index affa96e..ed2922e 100644
--- a/src/main/kotlin/com/coder/toolbox/views/AuthWizardPage.kt
+++ b/src/main/kotlin/com/coder/toolbox/views/AuthWizardPage.kt
@@ -25,7 +25,7 @@ class AuthWizardPage(
         client: CoderRestClient,
         cli: CoderCLIManager,
     ) -> Unit,
-) : CoderPage(context.i18n.ptrl("Authenticate to Coder"), false) {
+) : CoderPage(context.i18n.ptrl("Setting up Coder"), false) {
     private val shouldAutoLogin = MutableStateFlow(initialAutoLogin)
     private val settingsAction = Action(context.i18n.ptrl("Settings"), actionBlock = {
         context.ui.showUiPage(settingsPage)
diff --git a/src/main/resources/localization/defaultMessages.po b/src/main/resources/localization/defaultMessages.po
index 1b04695..e1b3f90 100644
--- a/src/main/resources/localization/defaultMessages.po
+++ b/src/main/resources/localization/defaultMessages.po
@@ -143,4 +143,7 @@ msgid "Error encountered while handling Coder URI"
 msgstr ""
 
 msgid "Error encountered during authentication"
+msgstr ""
+
+msgid "Setting up Coder"
 msgstr ""
\ No newline at end of file

From 2015addbb091ab0fe5ab8aee7f08a17e69a8509d Mon Sep 17 00:00:00 2001
From: Faur Ioan-Aurel <fioan89@gmail.com>
Date: Wed, 18 Jun 2025 23:33:14 +0300
Subject: [PATCH 7/8] chore: refactor classes to reflect we are not actually
 doing auth

---
 .../com/coder/toolbox/CoderRemoteProvider.kt  | 27 +++++++-------
 ...zardPage.kt => CoderCliSetupWizardPage.kt} | 36 +++++++++----------
 .../com/coder/toolbox/views/ConnectStep.kt    | 32 ++++++++---------
 .../{SignInStep.kt => DeploymentUrlStep.kt}   | 12 +++----
 .../com/coder/toolbox/views/TokenStep.kt      | 16 ++++-----
 ...AuthContext.kt => CoderCliSetupContext.kt} |  4 +--
 ...rdState.kt => CoderCliSetupWizardState.kt} |  6 ++--
 .../resources/localization/defaultMessages.po |  4 +--
 8 files changed, 69 insertions(+), 68 deletions(-)
 rename src/main/kotlin/com/coder/toolbox/views/{AuthWizardPage.kt => CoderCliSetupWizardPage.kt} (81%)
 rename src/main/kotlin/com/coder/toolbox/views/{SignInStep.kt => DeploymentUrlStep.kt} (89%)
 rename src/main/kotlin/com/coder/toolbox/views/state/{AuthContext.kt => CoderCliSetupContext.kt} (89%)
 rename src/main/kotlin/com/coder/toolbox/views/state/{AuthWizardState.kt => CoderCliSetupWizardState.kt} (82%)

diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt
index 33d5cec..101cf71 100644
--- a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt
+++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt
@@ -9,10 +9,10 @@ import com.coder.toolbox.util.CoderProtocolHandler
 import com.coder.toolbox.util.DialogUi
 import com.coder.toolbox.util.withPath
 import com.coder.toolbox.views.Action
-import com.coder.toolbox.views.AuthWizardPage
+import com.coder.toolbox.views.CoderCliSetupWizardPage
 import com.coder.toolbox.views.CoderSettingsPage
 import com.coder.toolbox.views.NewEnvironmentPage
-import com.coder.toolbox.views.state.AuthWizardState
+import com.coder.toolbox.views.state.CoderCliSetupWizardState
 import com.coder.toolbox.views.state.WizardStep
 import com.jetbrains.toolbox.api.core.ui.icons.SvgIcon
 import com.jetbrains.toolbox.api.core.ui.icons.SvgIcon.IconType
@@ -242,7 +242,7 @@ class CoderRemoteProvider(
         environments.value = LoadableState.Value(emptyList())
         isInitialized.update { false }
         client = null
-        AuthWizardState.resetSteps()
+        CoderCliSetupWizardState.resetSteps()
     }
 
     override val svgIcon: SvgIcon =
@@ -301,7 +301,7 @@ class CoderRemoteProvider(
      */
     override suspend fun handleUri(uri: URI) {
         linkHandler.handle(
-            uri, shouldDoAutoLogin(),
+            uri, shouldDoAutoSetup(),
             {
                 coderHeaderPage.isBusyCreatingNewEnvironment.update {
                     true
@@ -343,17 +343,17 @@ class CoderRemoteProvider(
      * list.
      */
     override fun getOverrideUiPage(): UiPage? {
-        // Show sign in page if we have not configured the client yet.
+        // Show the setup page if we have not configured the client yet.
         if (client == null) {
             val errorBuffer = mutableListOf<Throwable>()
             // When coming back to the application, initializeSession immediately.
-            val autologin = shouldDoAutoLogin()
+            val autoSetup = shouldDoAutoSetup()
             context.secrets.lastToken.let { lastToken ->
                 context.secrets.lastDeploymentURL.let { lastDeploymentURL ->
-                    if (autologin && lastDeploymentURL.isNotBlank() && (lastToken.isNotBlank() || !settings.requireTokenAuth)) {
+                    if (autoSetup && lastDeploymentURL.isNotBlank() && (lastToken.isNotBlank() || !settings.requireTokenAuth)) {
                         try {
-                            AuthWizardState.goToStep(WizardStep.LOGIN)
-                            return AuthWizardPage(context, settingsPage, visibilityState, true, ::onConnect)
+                            CoderCliSetupWizardState.goToStep(WizardStep.CONNECT)
+                            return CoderCliSetupWizardPage(context, settingsPage, visibilityState, true, ::onConnect)
                         } catch (ex: Exception) {
                             errorBuffer.add(ex)
                         }
@@ -363,18 +363,19 @@ class CoderRemoteProvider(
             firstRun = false
 
             // Login flow.
-            val authWizard = AuthWizardPage(context, settingsPage, visibilityState, onConnect = ::onConnect)
+            val setupWizardPage =
+                CoderCliSetupWizardPage(context, settingsPage, visibilityState, onConnect = ::onConnect)
             // We might have navigated here due to a polling error.
             errorBuffer.forEach {
-                authWizard.notify("Error encountered", it)
+                setupWizardPage.notify("Error encountered", it)
             }
             // and now reset the errors, otherwise we show it every time on the screen
-            return authWizard
+            return setupWizardPage
         }
         return null
     }
 
-    private fun shouldDoAutoLogin(): Boolean = firstRun && context.secrets.rememberMe == true
+    private fun shouldDoAutoSetup(): Boolean = firstRun && context.secrets.rememberMe == true
 
     private suspend fun onConnect(client: CoderRestClient, cli: CoderCLIManager) {
         // Store the URL and token for use next time.
diff --git a/src/main/kotlin/com/coder/toolbox/views/AuthWizardPage.kt b/src/main/kotlin/com/coder/toolbox/views/CoderCliSetupWizardPage.kt
similarity index 81%
rename from src/main/kotlin/com/coder/toolbox/views/AuthWizardPage.kt
rename to src/main/kotlin/com/coder/toolbox/views/CoderCliSetupWizardPage.kt
index ed2922e..c6193da 100644
--- a/src/main/kotlin/com/coder/toolbox/views/AuthWizardPage.kt
+++ b/src/main/kotlin/com/coder/toolbox/views/CoderCliSetupWizardPage.kt
@@ -5,8 +5,8 @@ import com.coder.toolbox.cli.CoderCLIManager
 import com.coder.toolbox.sdk.CoderRestClient
 import com.coder.toolbox.sdk.ex.APIResponseException
 import com.coder.toolbox.util.toURL
-import com.coder.toolbox.views.state.AuthContext
-import com.coder.toolbox.views.state.AuthWizardState
+import com.coder.toolbox.views.state.CoderCliSetupContext
+import com.coder.toolbox.views.state.CoderCliSetupWizardState
 import com.coder.toolbox.views.state.WizardStep
 import com.jetbrains.toolbox.api.remoteDev.ProviderVisibilityState
 import com.jetbrains.toolbox.api.ui.actions.RunnableActionDescription
@@ -16,26 +16,26 @@ import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
 import java.util.UUID
 
-class AuthWizardPage(
+class CoderCliSetupWizardPage(
     private val context: CoderToolboxContext,
     private val settingsPage: CoderSettingsPage,
     private val visibilityState: MutableStateFlow<ProviderVisibilityState>,
-    initialAutoLogin: Boolean = false,
+    initialAutoSetup: Boolean = false,
     onConnect: suspend (
         client: CoderRestClient,
         cli: CoderCLIManager,
     ) -> Unit,
 ) : CoderPage(context.i18n.ptrl("Setting up Coder"), false) {
-    private val shouldAutoLogin = MutableStateFlow(initialAutoLogin)
+    private val shouldAutoSetup = MutableStateFlow(initialAutoSetup)
     private val settingsAction = Action(context.i18n.ptrl("Settings"), actionBlock = {
         context.ui.showUiPage(settingsPage)
     })
 
-    private val signInStep = SignInStep(context, this::notify)
+    private val deploymentUrlStep = DeploymentUrlStep(context, this::notify)
     private val tokenStep = TokenStep(context)
     private val connectStep = ConnectStep(
         context,
-        shouldAutoLogin,
+        shouldAutoSetup,
         this::notify,
         this::displaySteps,
         onConnect
@@ -50,9 +50,9 @@ class AuthWizardPage(
     private val errorBuffer = mutableListOf<Throwable>()
 
     init {
-        if (shouldAutoLogin.value) {
-            AuthContext.url = context.secrets.lastDeploymentURL.toURL()
-            AuthContext.token = context.secrets.lastToken
+        if (shouldAutoSetup.value) {
+            CoderCliSetupContext.url = context.secrets.lastDeploymentURL.toURL()
+            CoderCliSetupContext.token = context.secrets.lastToken
         }
     }
 
@@ -67,22 +67,22 @@ class AuthWizardPage(
     }
 
     private fun displaySteps() {
-        when (AuthWizardState.currentStep()) {
+        when (CoderCliSetupWizardState.currentStep()) {
             WizardStep.URL_REQUEST -> {
                 fields.update {
-                    listOf(signInStep.panel)
+                    listOf(deploymentUrlStep.panel)
                 }
                 actionButtons.update {
                     listOf(
-                        Action(context.i18n.ptrl("Sign In"), closesPage = false, actionBlock = {
-                            if (signInStep.onNext()) {
+                        Action(context.i18n.ptrl("Next"), closesPage = false, actionBlock = {
+                            if (deploymentUrlStep.onNext()) {
                                 displaySteps()
                             }
                         }),
                         settingsAction
                     )
                 }
-                signInStep.onVisible()
+                deploymentUrlStep.onVisible()
             }
 
             WizardStep.TOKEN_REQUEST -> {
@@ -106,7 +106,7 @@ class AuthWizardPage(
                 tokenStep.onVisible()
             }
 
-            WizardStep.LOGIN -> {
+            WizardStep.CONNECT -> {
                 fields.update {
                     listOf(connectStep.panel)
                 }
@@ -115,7 +115,7 @@ class AuthWizardPage(
                         settingsAction,
                         Action(context.i18n.ptrl("Back"), closesPage = false, actionBlock = {
                             connectStep.onBack()
-                            shouldAutoLogin.update {
+                            shouldAutoSetup.update {
                                 false
                             }
                             displaySteps()
@@ -150,7 +150,7 @@ class AuthWizardPage(
         context.cs.launch {
             context.ui.showSnackbar(
                 UUID.randomUUID().toString(),
-                context.i18n.ptrl("Error encountered during authentication"),
+                context.i18n.ptrl("Error encountered while setting up Coder"),
                 context.i18n.pnotr(textError ?: ""),
                 context.i18n.ptrl("Dismiss")
             )
diff --git a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt
index 4c1c81a..c4973d7 100644
--- a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt
+++ b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt
@@ -5,8 +5,8 @@ import com.coder.toolbox.cli.CoderCLIManager
 import com.coder.toolbox.cli.ensureCLI
 import com.coder.toolbox.plugin.PluginManager
 import com.coder.toolbox.sdk.CoderRestClient
-import com.coder.toolbox.views.state.AuthContext
-import com.coder.toolbox.views.state.AuthWizardState
+import com.coder.toolbox.views.state.CoderCliSetupContext
+import com.coder.toolbox.views.state.CoderCliSetupWizardState
 import com.jetbrains.toolbox.api.localization.LocalizableString
 import com.jetbrains.toolbox.api.ui.components.LabelField
 import com.jetbrains.toolbox.api.ui.components.RowGroup
@@ -50,14 +50,14 @@ class ConnectStep(
             context.i18n.pnotr("")
         }
 
-        if (AuthContext.isNotReadyForAuth()) {
+        if (CoderCliSetupContext.isNotReadyForAuth()) {
             errorField.textState.update {
                 context.i18n.pnotr("URL and token were not properly configured. Please go back and provide a proper URL and token!")
             }
             return
         }
 
-        statusField.textState.update { context.i18n.pnotr("Connecting to ${AuthContext.url!!.host}...") }
+        statusField.textState.update { context.i18n.pnotr("Connecting to ${CoderCliSetupContext.url!!.host}...") }
         connect()
     }
 
@@ -65,12 +65,12 @@ class ConnectStep(
      * Try connecting to Coder with the provided URL and token.
      */
     private fun connect() {
-        if (!AuthContext.hasUrl()) {
+        if (!CoderCliSetupContext.hasUrl()) {
             errorField.textState.update { context.i18n.ptrl("URL is required") }
             return
         }
 
-        if (!AuthContext.hasToken()) {
+        if (!CoderCliSetupContext.hasToken()) {
             errorField.textState.update { context.i18n.ptrl("Token is required") }
             return
         }
@@ -79,8 +79,8 @@ class ConnectStep(
             try {
                 val client = CoderRestClient(
                     context,
-                    AuthContext.url!!,
-                    AuthContext.token!!,
+                    CoderCliSetupContext.url!!,
+                    CoderCliSetupContext.token!!,
                     PluginManager.pluginInfo.version,
                 )
                 // allows interleaving with the back/cancel action
@@ -100,20 +100,20 @@ class ConnectStep(
                     yield()
                     cli.login(client.token)
                 }
-                statusField.textState.update { (context.i18n.ptrl("Successfully configured ${AuthContext.url!!.host}...")) }
+                statusField.textState.update { (context.i18n.ptrl("Successfully configured ${CoderCliSetupContext.url!!.host}...")) }
                 // allows interleaving with the back/cancel action
                 yield()
-                AuthContext.reset()
-                AuthWizardState.resetSteps()
+                CoderCliSetupContext.reset()
+                CoderCliSetupWizardState.resetSteps()
                 onConnect(client, cli)
             } catch (ex: CancellationException) {
                 if (ex.message != USER_HIT_THE_BACK_BUTTON) {
-                    notify("Connection to ${AuthContext.url!!.host} was configured", ex)
+                    notify("Connection to ${CoderCliSetupContext.url!!.host} was configured", ex)
                     onBack()
                     refreshWizard()
                 }
             } catch (ex: Exception) {
-                notify("Failed to configure ${AuthContext.url!!.host}", ex)
+                notify("Failed to configure ${CoderCliSetupContext.url!!.host}", ex)
                 onBack()
                 refreshWizard()
             }
@@ -129,11 +129,11 @@ class ConnectStep(
             signInJob?.cancel(CancellationException(USER_HIT_THE_BACK_BUTTON))
         } finally {
             if (shouldAutoLogin.value) {
-                AuthContext.reset()
-                AuthWizardState.resetSteps()
+                CoderCliSetupContext.reset()
+                CoderCliSetupWizardState.resetSteps()
                 context.secrets.rememberMe = false
             } else {
-                AuthWizardState.goToPreviousStep()
+                CoderCliSetupWizardState.goToPreviousStep()
             }
         }
     }
diff --git a/src/main/kotlin/com/coder/toolbox/views/SignInStep.kt b/src/main/kotlin/com/coder/toolbox/views/DeploymentUrlStep.kt
similarity index 89%
rename from src/main/kotlin/com/coder/toolbox/views/SignInStep.kt
rename to src/main/kotlin/com/coder/toolbox/views/DeploymentUrlStep.kt
index 34cdf2d..2b8d5f2 100644
--- a/src/main/kotlin/com/coder/toolbox/views/SignInStep.kt
+++ b/src/main/kotlin/com/coder/toolbox/views/DeploymentUrlStep.kt
@@ -2,8 +2,8 @@ package com.coder.toolbox.views
 
 import com.coder.toolbox.CoderToolboxContext
 import com.coder.toolbox.util.toURL
-import com.coder.toolbox.views.state.AuthContext
-import com.coder.toolbox.views.state.AuthWizardState
+import com.coder.toolbox.views.state.CoderCliSetupContext
+import com.coder.toolbox.views.state.CoderCliSetupWizardState
 import com.jetbrains.toolbox.api.localization.LocalizableString
 import com.jetbrains.toolbox.api.ui.components.RowGroup
 import com.jetbrains.toolbox.api.ui.components.TextField
@@ -19,7 +19,7 @@ import java.net.URL
  * Populates with the provided URL, at which point the user can accept or
  * enter their own.
  */
-class SignInStep(
+class DeploymentUrlStep(
     private val context: CoderToolboxContext,
     private val notify: (String, Throwable) -> Unit
 ) :
@@ -32,7 +32,7 @@ class SignInStep(
         RowGroup.RowField(errorField)
     )
 
-    override val nextButtonTitle: LocalizableString? = context.i18n.ptrl("Sign In")
+    override val nextButtonTitle: LocalizableString? = context.i18n.ptrl("Next")
 
     override fun onVisible() {
         errorField.textState.update {
@@ -55,12 +55,12 @@ class SignInStep(
             url
         }
         try {
-            AuthContext.url = validateRawUrl(url)
+            CoderCliSetupContext.url = validateRawUrl(url)
         } catch (e: MalformedURLException) {
             notify("URL is invalid", e)
             return false
         }
-        AuthWizardState.goToNextStep()
+        CoderCliSetupWizardState.goToNextStep()
         return true
     }
 
diff --git a/src/main/kotlin/com/coder/toolbox/views/TokenStep.kt b/src/main/kotlin/com/coder/toolbox/views/TokenStep.kt
index b02e9ed..da1f8b0 100644
--- a/src/main/kotlin/com/coder/toolbox/views/TokenStep.kt
+++ b/src/main/kotlin/com/coder/toolbox/views/TokenStep.kt
@@ -2,8 +2,8 @@ package com.coder.toolbox.views
 
 import com.coder.toolbox.CoderToolboxContext
 import com.coder.toolbox.util.withPath
-import com.coder.toolbox.views.state.AuthContext
-import com.coder.toolbox.views.state.AuthWizardState
+import com.coder.toolbox.views.state.CoderCliSetupContext
+import com.coder.toolbox.views.state.CoderCliSetupWizardState
 import com.jetbrains.toolbox.api.localization.LocalizableString
 import com.jetbrains.toolbox.api.ui.components.LinkField
 import com.jetbrains.toolbox.api.ui.components.RowGroup
@@ -37,9 +37,9 @@ class TokenStep(
         errorField.textState.update {
             context.i18n.pnotr("")
         }
-        if (AuthContext.hasUrl()) {
+        if (CoderCliSetupContext.hasUrl()) {
             tokenField.textState.update {
-                context.secrets.tokenFor(AuthContext.url!!) ?: ""
+                context.secrets.tokenFor(CoderCliSetupContext.url!!) ?: ""
             }
         } else {
             errorField.textState.update {
@@ -48,7 +48,7 @@ class TokenStep(
             }
         }
         (linkField.urlState as MutableStateFlow).update {
-            AuthContext.url!!.withPath("/login?redirect=%2Fcli-auth")?.toString() ?: ""
+            CoderCliSetupContext.url!!.withPath("/login?redirect=%2Fcli-auth")?.toString() ?: ""
         }
     }
 
@@ -59,12 +59,12 @@ class TokenStep(
             return false
         }
 
-        AuthContext.token = token
-        AuthWizardState.goToNextStep()
+        CoderCliSetupContext.token = token
+        CoderCliSetupWizardState.goToNextStep()
         return true
     }
 
     override fun onBack() {
-        AuthWizardState.goToPreviousStep()
+        CoderCliSetupWizardState.goToPreviousStep()
     }
 }
diff --git a/src/main/kotlin/com/coder/toolbox/views/state/AuthContext.kt b/src/main/kotlin/com/coder/toolbox/views/state/CoderCliSetupContext.kt
similarity index 89%
rename from src/main/kotlin/com/coder/toolbox/views/state/AuthContext.kt
rename to src/main/kotlin/com/coder/toolbox/views/state/CoderCliSetupContext.kt
index 320bd63..8d503b9 100644
--- a/src/main/kotlin/com/coder/toolbox/views/state/AuthContext.kt
+++ b/src/main/kotlin/com/coder/toolbox/views/state/CoderCliSetupContext.kt
@@ -3,13 +3,13 @@ package com.coder.toolbox.views.state
 import java.net.URL
 
 /**
- * Singleton that holds authentication context (URL and token) across multiple
+ * Singleton that holds Coder CLI setup context (URL and token) across multiple
  * Toolbox window lifecycle events.
  *
  * This ensures that user input (URL and token) is not lost when the Toolbox
  * window is temporarily closed or recreated.
  */
-object AuthContext {
+object CoderCliSetupContext {
     /**
      * The currently entered URL.
      */
diff --git a/src/main/kotlin/com/coder/toolbox/views/state/AuthWizardState.kt b/src/main/kotlin/com/coder/toolbox/views/state/CoderCliSetupWizardState.kt
similarity index 82%
rename from src/main/kotlin/com/coder/toolbox/views/state/AuthWizardState.kt
rename to src/main/kotlin/com/coder/toolbox/views/state/CoderCliSetupWizardState.kt
index c29fbc9..f1efca4 100644
--- a/src/main/kotlin/com/coder/toolbox/views/state/AuthWizardState.kt
+++ b/src/main/kotlin/com/coder/toolbox/views/state/CoderCliSetupWizardState.kt
@@ -2,13 +2,13 @@ package com.coder.toolbox.views.state
 
 
 /**
- * A singleton that maintains the state of the authorization wizard across Toolbox window lifecycle events.
+ * A singleton that maintains the state of the coder setup wizard across Toolbox window lifecycle events.
  *
  * This is used to persist the wizard's progress (i.e., current step) between visibility changes
  * of the Toolbox window. Without this object, closing and reopening the window would reset the wizard
  * to its initial state by creating a new instance.
  */
-object AuthWizardState {
+object CoderCliSetupWizardState {
     private var currentStep = WizardStep.URL_REQUEST
 
     fun currentStep(): WizardStep = currentStep
@@ -31,5 +31,5 @@ object AuthWizardState {
 }
 
 enum class WizardStep {
-    URL_REQUEST, TOKEN_REQUEST, LOGIN;
+    URL_REQUEST, TOKEN_REQUEST, CONNECT;
 }
\ No newline at end of file
diff --git a/src/main/resources/localization/defaultMessages.po b/src/main/resources/localization/defaultMessages.po
index e1b3f90..fe1f90c 100644
--- a/src/main/resources/localization/defaultMessages.po
+++ b/src/main/resources/localization/defaultMessages.po
@@ -106,7 +106,7 @@ msgstr ""
 msgid "Configuring CLI..."
 msgstr ""
 
-msgid "Sign In"
+msgid "Next"
 msgstr ""
 
 msgid "Token"
@@ -142,7 +142,7 @@ msgstr ""
 msgid "Error encountered while handling Coder URI"
 msgstr ""
 
-msgid "Error encountered during authentication"
+msgid "Error encountered while setting up Coder"
 msgstr ""
 
 msgid "Setting up Coder"

From 2797652f47679aca98bc30cd4314e78a8ea38cfc Mon Sep 17 00:00:00 2001
From: Faur Ioan-Aurel <fioan89@gmail.com>
Date: Wed, 18 Jun 2025 23:39:45 +0300
Subject: [PATCH 8/8] chore: remove unused field

---
 src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt       | 3 ---
 src/main/kotlin/com/coder/toolbox/views/DeploymentUrlStep.kt | 3 ---
 src/main/kotlin/com/coder/toolbox/views/TokenStep.kt         | 2 --
 src/main/kotlin/com/coder/toolbox/views/WizardStep.kt        | 2 --
 4 files changed, 10 deletions(-)

diff --git a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt
index c4973d7..9964d0c 100644
--- a/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt
+++ b/src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt
@@ -7,7 +7,6 @@ import com.coder.toolbox.plugin.PluginManager
 import com.coder.toolbox.sdk.CoderRestClient
 import com.coder.toolbox.views.state.CoderCliSetupContext
 import com.coder.toolbox.views.state.CoderCliSetupWizardState
-import com.jetbrains.toolbox.api.localization.LocalizableString
 import com.jetbrains.toolbox.api.ui.components.LabelField
 import com.jetbrains.toolbox.api.ui.components.RowGroup
 import com.jetbrains.toolbox.api.ui.components.ValidationErrorField
@@ -43,8 +42,6 @@ class ConnectStep(
         RowGroup.RowField(errorField)
     )
 
-    override val nextButtonTitle: LocalizableString? = null
-
     override fun onVisible() {
         errorField.textState.update {
             context.i18n.pnotr("")
diff --git a/src/main/kotlin/com/coder/toolbox/views/DeploymentUrlStep.kt b/src/main/kotlin/com/coder/toolbox/views/DeploymentUrlStep.kt
index 2b8d5f2..aa87b57 100644
--- a/src/main/kotlin/com/coder/toolbox/views/DeploymentUrlStep.kt
+++ b/src/main/kotlin/com/coder/toolbox/views/DeploymentUrlStep.kt
@@ -4,7 +4,6 @@ import com.coder.toolbox.CoderToolboxContext
 import com.coder.toolbox.util.toURL
 import com.coder.toolbox.views.state.CoderCliSetupContext
 import com.coder.toolbox.views.state.CoderCliSetupWizardState
-import com.jetbrains.toolbox.api.localization.LocalizableString
 import com.jetbrains.toolbox.api.ui.components.RowGroup
 import com.jetbrains.toolbox.api.ui.components.TextField
 import com.jetbrains.toolbox.api.ui.components.TextType
@@ -32,8 +31,6 @@ class DeploymentUrlStep(
         RowGroup.RowField(errorField)
     )
 
-    override val nextButtonTitle: LocalizableString? = context.i18n.ptrl("Next")
-
     override fun onVisible() {
         errorField.textState.update {
             context.i18n.pnotr("")
diff --git a/src/main/kotlin/com/coder/toolbox/views/TokenStep.kt b/src/main/kotlin/com/coder/toolbox/views/TokenStep.kt
index da1f8b0..b449f40 100644
--- a/src/main/kotlin/com/coder/toolbox/views/TokenStep.kt
+++ b/src/main/kotlin/com/coder/toolbox/views/TokenStep.kt
@@ -4,7 +4,6 @@ import com.coder.toolbox.CoderToolboxContext
 import com.coder.toolbox.util.withPath
 import com.coder.toolbox.views.state.CoderCliSetupContext
 import com.coder.toolbox.views.state.CoderCliSetupWizardState
-import com.jetbrains.toolbox.api.localization.LocalizableString
 import com.jetbrains.toolbox.api.ui.components.LinkField
 import com.jetbrains.toolbox.api.ui.components.RowGroup
 import com.jetbrains.toolbox.api.ui.components.TextField
@@ -31,7 +30,6 @@ class TokenStep(
         RowGroup.RowField(linkField),
         RowGroup.RowField(errorField)
     )
-    override val nextButtonTitle: LocalizableString? = context.i18n.ptrl("Connect")
 
     override fun onVisible() {
         errorField.textState.update {
diff --git a/src/main/kotlin/com/coder/toolbox/views/WizardStep.kt b/src/main/kotlin/com/coder/toolbox/views/WizardStep.kt
index 6ba3d52..bb19281 100644
--- a/src/main/kotlin/com/coder/toolbox/views/WizardStep.kt
+++ b/src/main/kotlin/com/coder/toolbox/views/WizardStep.kt
@@ -1,11 +1,9 @@
 package com.coder.toolbox.views
 
-import com.jetbrains.toolbox.api.localization.LocalizableString
 import com.jetbrains.toolbox.api.ui.components.RowGroup
 
 interface WizardStep {
     val panel: RowGroup
-    val nextButtonTitle: LocalizableString?
 
     /**
      * Callback when step is visible