diff --git a/.github/workflows/check-go-task.yml b/.github/workflows/check-go-task.yml
index 6e347253980..7388cc095e1 100644
--- a/.github/workflows/check-go-task.yml
+++ b/.github/workflows/check-go-task.yml
@@ -237,7 +237,7 @@ jobs:
         module:
           - path: internal/arduino/discovery/discovery_client
           - path: rpc/internal/client_example
-          - path: commands/daemon/term_example
+          - path: commands/term_example
 
     steps:
       - name: Checkout repository
diff --git a/.golangci.yml b/.golangci.yml
index a4057e492c6..a80fe164d7e 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -134,5 +134,7 @@ issues:
     - path-except: internal/cli/
       linters:
         - forbidigo
+    - path: internal/cli/.*_test.go
+      linters: [forbidigo]
     - path: internal/cli/feedback/
       linters: [forbidigo]
diff --git a/Taskfile.yml b/Taskfile.yml
index 2a9b20fd9ca..d5cbf0131a0 100755
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -95,7 +95,7 @@ tasks:
     dir: '{{default "./" .GO_MODULE_PATH}}'
     cmds:
       - |
-        go test \
+        LANG=en go test \
           -v \
           -short \
           -run '{{default ".*" .GO_TEST_REGEX}}' \
@@ -115,7 +115,7 @@ tasks:
       - |
         rm -fr coverage_data
         mkdir coverage_data
-        INTEGRATION_GOCOVERDIR={{ .ROOT_DIR }}/coverage_data go test \
+        LANG=en INTEGRATION_GOCOVERDIR={{ .ROOT_DIR }}/coverage_data go test \
           -v \
           -short \
           {{ .GO_TEST_PACKAGE }} \
diff --git a/commands/board/testdata/package_index.json b/commands/board/testdata/package_index.json
deleted file mode 100644
index e36f45cb5b0..00000000000
--- a/commands/board/testdata/package_index.json
+++ /dev/null
@@ -1,2523 +0,0 @@
-{
-  "packages": [
-    {
-      "name": "arduino",
-      "maintainer": "Arduino",
-      "websiteURL": "http://www.arduino.cc/",
-      "email": "packages@arduino.cc",
-      "help": {
-        "online": "http://www.arduino.cc/en/Reference/HomePage"
-      },
-      "platforms": [
-        {
-          "name": "Arduino AVR Boards",
-          "architecture": "avr",
-          "version": "1.8.3",
-          "category": "Arduino",
-          "help": {
-            "online": "http://www.arduino.cc/en/Reference/HomePage"
-          },
-          "url": "http://downloads.arduino.cc/cores/avr-1.8.3.tar.bz2",
-          "archiveFileName": "avr-1.8.3.tar.bz2",
-          "checksum": "SHA-256:de8a9b982477762d3d3e52fc2b682cdd8ff194dc3f1d46f4debdea6a01b33c14",
-          "size": "4941548",
-          "boards": [
-            { "name": "Arduino Yún" },
-            { "name": "Arduino Uno" },
-            { "name": "Arduino Uno WiFi" },
-            { "name": "Arduino Diecimila" },
-            { "name": "Arduino Nano" },
-            { "name": "Arduino Mega" },
-            { "name": "Arduino MegaADK" },
-            { "name": "Arduino Leonardo" },
-            { "name": "Arduino Leonardo Ethernet" },
-            { "name": "Arduino Micro" },
-            { "name": "Arduino Esplora" },
-            { "name": "Arduino Mini" },
-            { "name": "Arduino Ethernet" },
-            { "name": "Arduino Fio" },
-            { "name": "Arduino BT" },
-            { "name": "Arduino LilyPadUSB" },
-            { "name": "Arduino Lilypad" },
-            { "name": "Arduino Pro" },
-            { "name": "Arduino ATMegaNG" },
-            { "name": "Arduino Robot Control" },
-            { "name": "Arduino Robot Motor" },
-            { "name": "Arduino Gemma" },
-            { "name": "Adafruit Circuit Playground" },
-            { "name": "Arduino Yún Mini" },
-            { "name": "Arduino Industrial 101" },
-            { "name": "Linino One" }
-          ],
-          "toolsDependencies": [
-            {
-              "packager": "arduino",
-              "name": "avr-gcc",
-              "version": "7.3.0-atmel3.6.1-arduino7"
-            },
-            {
-              "packager": "arduino",
-              "name": "avrdude",
-              "version": "6.3.0-arduino17"
-            },
-            {
-              "packager": "arduino",
-              "name": "arduinoOTA",
-              "version": "1.3.0"
-            }
-          ]
-        }
-      ],
-      "tools": [
-        {
-          "name": "arm-none-eabi-gcc",
-          "version": "4.8.3-2014q1",
-          "systems": [
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/gcc-arm-none-eabi-4.8.3-2014q1-arm.tar.bz2",
-              "archiveFileName": "gcc-arm-none-eabi-4.8.3-2014q1-arm.tar.bz2",
-              "checksum": "SHA-256:ebe96b34c4f434667cab0187b881ed585e7c7eb990fe6b69be3c81ec7e11e845",
-              "size": "44423906"
-            },
-            {
-              "host": "i686-mingw32",
-              "archiveFileName": "gcc-arm-none-eabi-4.8.3-2014q1-windows.tar.gz",
-              "url": "http://downloads.arduino.cc/gcc-arm-none-eabi-4.8.3-2014q1-windows.tar.gz",
-              "checksum": "SHA-256:fd8c111c861144f932728e00abd3f7d1107e186eb9cd6083a54c7236ea78b7c2",
-              "size": "84537449"
-            },
-            {
-              "host": "x86_64-apple-darwin",
-              "url": "http://downloads.arduino.cc/gcc-arm-none-eabi-4.8.3-2014q1-mac.tar.gz",
-              "archiveFileName": "gcc-arm-none-eabi-4.8.3-2014q1-mac.tar.gz",
-              "checksum": "SHA-256:3598acf21600f17a8e4a4e8e193dc422b894dc09384759b270b2ece5facb59c2",
-              "size": "52518522"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/gcc-arm-none-eabi-4.8.3-2014q1-linux64.tar.gz",
-              "archiveFileName": "gcc-arm-none-eabi-4.8.3-2014q1-linux64.tar.gz",
-              "checksum": "SHA-256:d23f6626148396d6ec42a5b4d928955a703e0757829195fa71a939e5b86eecf6",
-              "size": "51395093"
-            },
-            {
-              "host": "i686-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/gcc-arm-none-eabi-4.8.3-2014q1-linux32.tar.gz",
-              "archiveFileName": "gcc-arm-none-eabi-4.8.3-2014q1-linux32.tar.gz",
-              "checksum": "SHA-256:ba1994235f69c526c564f65343f22ddbc9822b2ea8c5ee07dd79d89f6ace2498",
-              "size": "51029223"
-            }
-          ]
-        },
-        {
-          "name": "arm-none-eabi-gcc",
-          "version": "7-2017q4",
-          "systems": [
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/tools/gcc-arm-none-eabi-7-2019-q4-major-linuxarm.tar.bz2",
-              "archiveFileName": "gcc-arm-none-eabi-7-2019-q4-major-linuxarm.tar.bz2",
-              "checksum": "SHA-256:34180943d95f759c66444a40b032f7dd9159a562670fc334f049567de140c51b",
-              "size": "96613739"
-            },
-            {
-              "host": "aarch64-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/gcc-arm-none-eabi-7-2018-q2-update-linuxarm64.tar.bz2",
-              "archiveFileName": "gcc-arm-none-eabi-7-2018-q2-update-linuxarm64.tar.bz2",
-              "checksum": "SHA-256:6fb5752fb4d11012bd0a1ceb93a19d0641ff7cf29d289b3e6b86b99768e66f76",
-              "size": "99558726"
-            },
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/tools/gcc-arm-none-eabi-7-2017-q4-major-win32-arduino1.zip",
-              "archiveFileName": "gcc-arm-none-eabi-7-2017-q4-major-win32-arduino1.zip",
-              "checksum": "SHA-256:96dd0091856f4d2eb21046eba571321feecf7d50b9c156f708b2a8b683903382",
-              "size": "131761924"
-            },
-            {
-              "host": "x86_64-apple-darwin",
-              "url": "http://downloads.arduino.cc/tools/gcc-arm-none-eabi-7-2017-q4-major-mac.tar.bz2",
-              "archiveFileName": "gcc-arm-none-eabi-7-2017-q4-major-mac.tar.bz2",
-              "checksum": "SHA-256:89b776c7cf0591c810b5b60067e4dc113b5b71bc50084a536e71b894a97fdccb",
-              "size": "104550003"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/gcc-arm-none-eabi-7-2017-q4-major-linux64.tar.bz2",
-              "archiveFileName": "gcc-arm-none-eabi-7-2017-q4-major-linux64.tar.bz2",
-              "checksum": "SHA-256:96a029e2ae130a1210eaa69e309ea40463028eab18ba19c1086e4c2dafe69a6a",
-              "size": "99857645"
-            },
-            {
-              "host": "i686-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/gcc-arm-none-eabi-7-2018-q2-update-linux32.tar.bz2",
-              "archiveFileName": "gcc-arm-none-eabi-7-2018-q2-update-linux32.tar.bz2",
-              "checksum": "SHA-256:090a0bc2b1956bc49392dff924a6c30fa57c88130097b1972204d67a45ce3cf3",
-              "size": "97427309"
-            }
-          ]
-        },
-        {
-          "name": "bossac",
-          "version": "1.3-arduino",
-          "systems": [
-            {
-              "host": "i686-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.3a-arduino-i686-linux-gnu.tar.bz2",
-              "archiveFileName": "bossac-1.3a-arduino-i686-linux-gnu.tar.bz2",
-              "checksum": "SHA-256:d6d10362f40729a7877e43474fcf02ad82cf83321cc64ca931f5c82b2d25d24f",
-              "size": "147359"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.3a-arduino-x86_64-pc-linux-gnu.tar.bz2",
-              "archiveFileName": "bossac-1.3a-arduino-x86_64-pc-linux-gnu.tar.bz2",
-              "checksum": "SHA-256:c1daed033251296768fa8b63ad283e053da93427c0f3cd476a71a9188e18442c",
-              "size": "26179"
-            },
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.3a-arduino-i686-mingw32.tar.bz2",
-              "archiveFileName": "bossac-1.3a-arduino-i686-mingw32.tar.bz2",
-              "checksum": "SHA-256:a37727622e0f86cb4f2856ad0209568a5d804234dba3dc0778829730d61a5ec7",
-              "size": "265647"
-            },
-            {
-              "host": "i386-apple-darwin11",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.3a-arduino-i386-apple-darwin11.tar.bz2",
-              "archiveFileName": "bossac-1.3a-arduino-i386-apple-darwin11.tar.bz2",
-              "checksum": "SHA-256:40770b225753e7a52bb165e8f37e6b760364f5c5e96048168d0178945bd96ad6",
-              "size": "39475"
-            }
-          ]
-        },
-        {
-          "name": "avr-gcc",
-          "version": "4.8.1-arduino2",
-          "systems": [
-            {
-              "size": "24443285",
-              "checksum": "SHA-256:c19a7526235c364d7f62ec1a993d9b495973ba1813869ccf0241c65905896852",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "avr-gcc-4.8.1-arduino2-i386-apple-darwin11.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.8.1-arduino2-i386-apple-darwin11.tar.bz2"
-            },
-            {
-              "size": "27152002",
-              "checksum": "SHA-256:24a931877bee5f36dc00a88877219a6d2f6a1fb7abb989fd04556b8432d2e14e",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "avr-gcc-4.8.1-arduino2-x86_64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.8.1-arduino2-x86_64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "25876628",
-              "checksum": "SHA-256:2d701b4efbc8cec62dc299cde01730c5eebcf23d7e4393db8cf7744a9bf1d3de",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "avr-gcc-4.8.1-arduino2-i686-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.8.1-arduino2-i686-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "46046691",
-              "checksum": "SHA-256:2eafb49fb803fa4d2c32d35e24c0b372fcd520ca0a790fa537a847179e382000",
-              "host": "i686-mingw32",
-              "archiveFileName": "avr-gcc-4.8.1-arduino2-i686-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.8.1-arduino2-i686-mingw32.zip"
-            }
-          ]
-        },
-        {
-          "name": "avrdude",
-          "version": "6.0.1-arduino2",
-          "systems": [
-            {
-              "size": "264965",
-              "checksum": "SHA-256:71117cce0096dad6c091e2c34eb0b9a3386d3aec7d863d2da733d9e5eac3a6b1",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "avrdude-6.0.1-arduino2-i386-apple-darwin11.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.0.1-arduino2-i386-apple-darwin11.tar.bz2"
-            },
-            {
-              "size": "292541",
-              "checksum": "SHA-256:2489004d1d98177eaf69796760451f89224007c98b39ebb5577a9a34f51425f1",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "avrdude-6.0.1-arduino2-x86_64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.0.1-arduino2-x86_64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "283209",
-              "checksum": "SHA-256:6f633dd6270ad0d9ef19507bcbf8697b414a15208e4c0f71deec25ef89cdef3f",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "avrdude-6.0.1-arduino2-i686-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.0.1-arduino2-i686-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "241618",
-              "checksum": "SHA-256:6c5483800ba753c80893607e30cade8ab77b182808fcc5ea15fa3019c63d76ae",
-              "host": "i686-mingw32",
-              "archiveFileName": "avrdude-6.0.1-arduino2-i686-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.0.1-arduino2-i686-mingw32.zip"
-            }
-          ]
-        },
-        {
-          "name": "avr-gcc",
-          "version": "4.8.1-arduino3",
-          "systems": [
-            {
-              "size": "24447175",
-              "checksum": "SHA-256:28e207c66b3dc405367d0c5e68ce3c278e5ec3abb0e4974e7927fe0f9a532c40",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "avr-gcc-4.8.1-arduino3-i386-apple-darwin11.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.8.1-arduino3-i386-apple-darwin11.tar.bz2"
-            },
-            {
-              "size": "30556996",
-              "checksum": "SHA-256:028340abec6eb3085b82404dfc7ed143e1bb05b2da961b539ddcdba4a6f65533",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "avr-gcc-4.8.1-arduino3-x86_64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.8.1-arduino3-x86_64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "28768022",
-              "checksum": "SHA-256:37796548ba9653267568f959cd8c7ebfe5b4bce4599898cf9f876d64e616cb87",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "avr-gcc-4.8.1-arduino3-i686-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.8.1-arduino3-i686-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "46046917",
-              "checksum": "SHA-256:d6f0527793f9800f060408392a99eb290ed205730edbae43a1a25cbf6b6b588f",
-              "host": "i686-mingw32",
-              "archiveFileName": "avr-gcc-4.8.1-arduino3-i686-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.8.1-arduino3-i686-mingw32.zip"
-            }
-          ]
-        },
-        {
-          "name": "avrdude",
-          "version": "6.0.1-arduino3",
-          "systems": [
-            {
-              "size": "264682",
-              "checksum": "SHA-256:df7cd4a76e45ab3767eb964f845f4d5e9d643df950ec32812923da1e9843d072",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "avrdude-6.0.1-arduino3-i386-apple-darwin11.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.0.1-arduino3-i386-apple-darwin11.tar.bz2"
-            },
-            {
-              "size": "748634",
-              "checksum": "SHA-256:bb7bff48f20a68e1fe559c3f3f644574df12ab5c98eb6a1491079f3c760434ad",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "avrdude-6.0.1-arduino3-x86_64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.0.1-arduino3-x86_64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "495482",
-              "checksum": "SHA-256:96a0cfb83fe0452366159e3bf4e19ff10906a8957d1feafd3d98b49ab4b14405",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "avrdude-6.0.1-arduino3-i686-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.0.1-arduino3-i686-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "241619",
-              "checksum": "SHA-256:ea59bfc2ee85039c85318b2ba52c47ef0573513444a785b72f59b22586a950f9",
-              "host": "i686-mingw32",
-              "archiveFileName": "avrdude-6.0.1-arduino3-i686-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.0.1-arduino3-i686-mingw32.zip"
-            }
-          ]
-        },
-        {
-          "name": "avr-gcc",
-          "version": "4.8.1-arduino5",
-          "systems": [
-            {
-              "size": "24403768",
-              "checksum": "SHA-256:c8ffcd2db7a651b48ab4ea19db4b34fbae3e7f0210a0f294592af2bdabf2154b",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "avr-gcc-4.8.1-arduino5-armhf-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.8.1-arduino5-armhf-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "24437400",
-              "checksum": "SHA-256:111b3ef00d737d069eb237a8933406cbb928e4698689e24663cffef07688a901",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "avr-gcc-4.8.1-arduino5-i386-apple-darwin11.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.8.1-arduino5-i386-apple-darwin11.tar.bz2"
-            },
-            {
-              "size": "27093036",
-              "checksum": "SHA-256:9054fcc174397a419ba56c4ce1bfcbcad275a6a080cc144905acc9b0351ee9cc",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "avr-gcc-4.8.1-arduino5-x86_64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.8.1-arduino5-x86_64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "25882375",
-              "checksum": "SHA-256:7648b7f549b37191da0b0be53bae791b652f82ac3cb4e7877f85075aaf32141f",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "avr-gcc-4.8.1-arduino5-i686-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.8.1-arduino5-i686-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "46044779",
-              "checksum": "SHA-256:d4303226a7b41d3c445d901b5aa5903458def3fc7b7ff4ffef37cabeb37d424d",
-              "host": "i686-mingw32",
-              "archiveFileName": "avr-gcc-4.8.1-arduino5-i686-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.8.1-arduino5-i686-mingw32.zip"
-            }
-          ]
-        },
-        {
-          "name": "avrdude",
-          "version": "6.0.1-arduino5",
-          "systems": [
-            {
-              "size": "267095",
-              "checksum": "SHA-256:23ea1341dbc117ec067f2eb1a498ad2bdd7d11fff0143c00b2e018c39804f6b4",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "avrdude-6.0.1-arduino5-armhf-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.0.1-arduino5-armhf-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "264894",
-              "checksum": "SHA-256:41af8d3b0a586853c8317b4fb5163ca0db594a1870ddf680fd988c42166fc3e5",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "avrdude-6.0.1-arduino5-i386-apple-darwin11.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.0.1-arduino5-i386-apple-darwin11.tar.bz2"
-            },
-            {
-              "size": "292629",
-              "checksum": "SHA-256:d826cca7383461f7e8adde686372cf900e9cb3afd639555cf2d6c645b283a476",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "avrdude-6.0.1-arduino5-x86_64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.0.1-arduino5-x86_64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "283121",
-              "checksum": "SHA-256:5933d66927bce46ababa9b68a8b7f1d53f68c4f3ff7a5ce4b85d7cf4e6c6bfee",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "avrdude-6.0.1-arduino5-i686-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.0.1-arduino5-i686-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "241634",
-              "checksum": "SHA-256:41f667f1f6a0ab8df46b4ffacd023176dcdef331d6db3b74bddd37d18cca0a44",
-              "host": "i686-mingw32",
-              "archiveFileName": "avrdude-6.0.1-arduino5-i686-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.0.1-arduino5-i686-mingw32.zip"
-            }
-          ]
-        },
-        {
-          "name": "avr-gcc",
-          "version": "4.9.2-atmel3.5.3-arduino",
-          "systems": [
-            {
-              "size": "27046965",
-              "checksum": "SHA-256:adeee70be27cc3ee0e4b9e844610d9c534c7b21dae24ec3fa49808c2f04958de",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "avr-gcc-4.9.2-atmel3.5.3-arduino-i386-apple-darwin11.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.9.2-atmel3.5.3-arduino-i386-apple-darwin11.tar.bz2"
-            },
-            {
-              "size": "27400001",
-              "checksum": "SHA-256:02dba9ee77694c23a4c304416a3808949c8faedf07f25a225a4189d850615ec6",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "avr-gcc-4.9.2-atmel3.5.3-arduino-armhf-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.9.2-atmel3.5.3-arduino-armhf-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "29904544",
-              "checksum": "SHA-256:0711e885c0430859e7fea3831af8c69a0c25f92a90ecfda9281799a0acec7455",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "avr-gcc-4.9.2-atmel3.5.3-arduino-x86_64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.9.2-atmel3.5.3-arduino-x86_64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "29077606",
-              "checksum": "SHA-256:fe0bb1d6369694779ceb671d457ccadbeafe855a11f6746b7db20055cea4df33",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "avr-gcc-4.9.2-atmel3.5.3-arduino-i686-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.9.2-atmel3.5.3-arduino-i686-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "43847566",
-              "checksum": "SHA-256:445ce3117e87be7e196809fbbea373976160689b6d4b43dbf185eb4c914d1469",
-              "host": "i686-mingw32",
-              "archiveFileName": "avr-gcc-4.9.2-atmel3.5.3-arduino-i686-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.9.2-atmel3.5.3-arduino-i686-mingw32.zip"
-            }
-          ]
-        },
-        {
-          "name": "avr-gcc",
-          "version": "4.9.2-atmel3.5.3-arduino2",
-          "systems": [
-            {
-              "size": "27400889",
-              "checksum": "SHA-256:77f300d519bc6b9a25df17b36cb303218e9a258c059b2f6bff8f71a0d8f96821",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "avr-gcc-4.9.2-atmel3.5.3-arduino2-armhf-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.9.2-atmel3.5.3-arduino2-armhf-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "27048070",
-              "checksum": "SHA-256:311258af188defe24a4b341e4e1f4dc93ca6c80516d3e3b55a2fc07a7050248b",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "avr-gcc-4.9.2-atmel3.5.3-arduino2-i386-apple-darwin11.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.9.2-atmel3.5.3-arduino2-i386-apple-darwin11.tar.bz2"
-            },
-            {
-              "size": "43847945",
-              "checksum": "SHA-256:f8e6ede8746c70be01ec79a30803277cd94360cc5b2e104762da0fbcf536fcc6",
-              "host": "i686-mingw32",
-              "archiveFileName": "avr-gcc-4.9.2-atmel3.5.3-arduino2-i686-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.9.2-atmel3.5.3-arduino2-i686-mingw32.zip"
-            },
-            {
-              "size": "29292729",
-              "checksum": "SHA-256:f108951e7c4dc90926d1fc76cc27549f6ea63c702a2bb7ff39647a19ae86ec68",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "avr-gcc-4.9.2-atmel3.5.3-arduino2-i686-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.9.2-atmel3.5.3-arduino2-i686-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "29882960",
-              "checksum": "SHA-256:3903a6d1bb9fdd91727e504b5993d5501f119bcb7f99f7aee98a2101e5629188",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "avr-gcc-4.9.2-atmel3.5.3-arduino2-x86_64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.9.2-atmel3.5.3-arduino2-x86_64-pc-linux-gnu.tar.bz2"
-            }
-          ]
-        },
-        {
-          "name": "avr-gcc",
-          "version": "4.9.2-atmel3.5.4-arduino2",
-          "systems": [
-            {
-              "size": "27764772",
-              "checksum": "SHA-256:ee36009e19bd238d1f6351cbc9aa5db69714761f67dec4c1d69d5d5d7758720c",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "avr-gcc-4.9.2-atmel3.5.4-arduino2-armhf-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.9.2-atmel3.5.4-arduino2-armhf-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "28574644",
-              "checksum": "SHA-256:67b3ed3555eacf0b4fc6f62240773b9f0220171fe4de26bb8d711547fc884730",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "avr-gcc-4.9.2-atmel3.5.4-arduino2-i386-apple-darwin11.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.9.2-atmel3.5.4-arduino2-i386-apple-darwin11.tar.bz2"
-            },
-            {
-              "size": "44386446",
-              "checksum": "SHA-256:6044551cd729d88ea6ffcccf10aad1934c5b164d61f4f5890b0e78524ffff853",
-              "host": "i686-mingw32",
-              "archiveFileName": "avr-gcc-4.9.2-atmel3.5.4-arduino2-i686-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.9.2-atmel3.5.4-arduino2-i686-mingw32.zip"
-            },
-            {
-              "size": "29723974",
-              "checksum": "SHA-256:63a9d4cebbac06fd5fa8f48a2e2ba7d513837dcddc97f560129b4e466af901b5",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "avr-gcc-4.9.2-atmel3.5.4-arduino2-i686-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.9.2-atmel3.5.4-arduino2-i686-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "30374404",
-              "checksum": "SHA-256:19480217f1524d78467b83cd742f503182bbcc76b5440093261f146828aa588c",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "avr-gcc-4.9.2-atmel3.5.4-arduino2-x86_64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-4.9.2-atmel3.5.4-arduino2-x86_64-pc-linux-gnu.tar.bz2"
-            }
-          ]
-        },
-        {
-          "name": "avr-gcc",
-          "version": "5.4.0-atmel3.6.1-arduino2",
-          "systems": [
-            {
-              "size": "31449123",
-              "checksum": "SHA-256:6741f95cc3182a8729cf9670eb13d8dc5a19e881639ca61e53a2d78346a4e99f",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "avr-gcc-5.4.0-atmel3.6.1-arduino2-armhf-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-5.4.0-atmel3.6.1-arduino2-armhf-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "33141295",
-              "checksum": "SHA-256:0fa9e4f2d6d09782dbc84dd91a302849cde2f192163cb9f29484c5f32785269a",
-              "host": "aarch64-linux-gnu",
-              "archiveFileName": "avr-gcc-5.4.0-atmel3.6.1-arduino2-aarch64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-5.4.0-atmel3.6.1-arduino2-aarch64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "31894498",
-              "checksum": "SHA-256:abc50137543ba73e227b4d1b8510fff50a474bacd24f2c794f852904963849f8",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "avr-gcc-5.4.0-atmel3.6.1-arduino2-i386-apple-darwin11.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-5.4.0-atmel3.6.1-arduino2-i386-apple-darwin11.tar.bz2"
-            },
-            {
-              "size": "45923772",
-              "checksum": "SHA-256:7eb5691a379b547798fae535b05d68bc02d3969f12d051b8a5a5f2f350ab0a7f",
-              "host": "i686-mingw32",
-              "archiveFileName": "avr-gcc-5.4.0-atmel3.6.1-arduino2-i686-w64-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-5.4.0-atmel3.6.1-arduino2-i686-w64-mingw32.zip"
-            },
-            {
-              "size": "33022916",
-              "checksum": "SHA-256:51f87e04f3cdaa73565c751051ac118e02904ad8478f1475b300e1bffcd5538f",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "avr-gcc-5.4.0-atmel3.6.1-arduino2-i686-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-5.4.0-atmel3.6.1-arduino2-i686-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "33522375",
-              "checksum": "SHA-256:05422b0d73b10357c12ea938f02cf50529422b89a4722756e70024aed3e69185",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "avr-gcc-5.4.0-atmel3.6.1-arduino2-x86_64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-5.4.0-atmel3.6.1-arduino2-x86_64-pc-linux-gnu.tar.bz2"
-            }
-          ]
-        },
-        {
-          "name": "avr-gcc",
-          "version": "7.3.0-atmel3.6.1-arduino5",
-          "systems": [
-            {
-              "size": "34462042",
-              "checksum": "SHA-256:f4acd5531c6b82c715e2edfa0aadb13fb718b4095b3ea1aa1f7fbde680069639",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "avr-gcc-7.3.0-atmel3.6.1-arduino5-arm-linux-gnueabihf.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-7.3.0-atmel3.6.1-arduino5-arm-linux-gnueabihf.tar.bz2"
-            },
-            {
-              "size": "39381972",
-              "checksum": "SHA-256:dd9c70190be370a44fb47dab1514de6d8852b861dfa527964b65c740d8d50c10",
-              "host": "aarch64-linux-gnu",
-              "archiveFileName": "avr-gcc-7.3.0-atmel3.6.1-arduino5-aarch64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-7.3.0-atmel3.6.1-arduino5-aarch64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "38492678",
-              "checksum": "SHA-256:f48706317f04452544ab90e75bd1bb193f8af2cb1002f53aa702f27202c1b38f",
-              "host": "x86_64-apple-darwin14",
-              "archiveFileName": "avr-gcc-7.3.0-atmel3.6.1-arduino5-x86_64-apple-darwin14.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-7.3.0-atmel3.6.1-arduino5-x86_64-apple-darwin14.tar.bz2"
-            },
-            {
-              "size": "53727984",
-              "checksum": "SHA-256:6d4a5d089a36e5b5252befc73da204555b49e376ce7577ee19ca7f028b295830",
-              "host": "i686-mingw32",
-              "archiveFileName": "avr-gcc-7.3.0-atmel3.6.1-arduino5-i686-w64-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-7.3.0-atmel3.6.1-arduino5-i686-w64-mingw32.zip"
-            },
-            {
-              "size": "38710087",
-              "checksum": "SHA-256:2ff12739d7ed09688d6b3c2c126e8df69b5bda1a07ab558799f0e576571e0e1d",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "avr-gcc-7.3.0-atmel3.6.1-arduino5-i686-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-7.3.0-atmel3.6.1-arduino5-i686-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "39114120",
-              "checksum": "SHA-256:3effed8ffa1978b6e4a46f1aa2acc629e440b4d77244f71f9b79a916025206fb",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "avr-gcc-7.3.0-atmel3.6.1-arduino5-x86_64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-7.3.0-atmel3.6.1-arduino5-x86_64-pc-linux-gnu.tar.bz2"
-            }
-          ]
-        },
-        {
-          "name": "avr-gcc",
-          "version": "7.3.0-atmel3.6.1-arduino7",
-          "systems": [
-            {
-              "size": "34683056",
-              "checksum": "SHA-256:3903553d035da59e33cff9941b857c3cb379cb0638105dfdf69c97f0acc8e7b5",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "avr-gcc-7.3.0-atmel3.6.1-arduino7-arm-linux-gnueabihf.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-7.3.0-atmel3.6.1-arduino7-arm-linux-gnueabihf.tar.bz2"
-            },
-            {
-              "size": "38045723",
-              "checksum": "SHA-256:03d322b9df6da17289e9e7c6233c34a8535d9c645c19efc772ba19e56914f339",
-              "host": "aarch64-linux-gnu",
-              "archiveFileName": "avr-gcc-7.3.0-atmel3.6.1-arduino7-aarch64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-7.3.0-atmel3.6.1-arduino7-aarch64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "36684546",
-              "checksum": "SHA-256:f6ed2346953fcf88df223469088633eb86de997fa27ece117fd1ef170d69c1f8",
-              "host": "x86_64-apple-darwin14",
-              "archiveFileName": "avr-gcc-7.3.0-atmel3.6.1-arduino7-x86_64-apple-darwin14.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-7.3.0-atmel3.6.1-arduino7-x86_64-apple-darwin14.tar.bz2"
-            },
-            {
-              "size": "52519412",
-              "checksum": "SHA-256:a54f64755fff4cb792a1495e5defdd789902a2a3503982e81b898299cf39800e",
-              "host": "i686-mingw32",
-              "archiveFileName": "avr-gcc-7.3.0-atmel3.6.1-arduino7-i686-w64-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-7.3.0-atmel3.6.1-arduino7-i686-w64-mingw32.zip"
-            },
-            {
-              "size": "37176991",
-              "checksum": "SHA-256:954bbffb33545bcdcd473af993da2980bf32e8461ff55a18e0eebc7b2ef69a4c",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "avr-gcc-7.3.0-atmel3.6.1-arduino7-i686-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-7.3.0-atmel3.6.1-arduino7-i686-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "37630618",
-              "checksum": "SHA-256:bd8c37f6952a2130ac9ee32c53f6a660feb79bee8353c8e289eb60fdcefed91e",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "avr-gcc-7.3.0-atmel3.6.1-arduino7-x86_64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avr-gcc-7.3.0-atmel3.6.1-arduino7-x86_64-pc-linux-gnu.tar.bz2"
-            }
-          ]
-        },
-        {
-          "name": "avrdude",
-          "version": "6.3.0-arduino2",
-          "systems": [
-            {
-              "size": "643484",
-              "checksum": "SHA-256:26af86137d8a872f64d217cb262734860b36fe26d6d34faf72e951042f187885",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "avrdude-6.3.0-arduino2-armhf-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino2-armhf-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "653968",
-              "checksum": "SHA-256:32525ea3696c861030e1a6006a5f11971d1dad331e45bfa68dac35126476b04f",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "avrdude-6.3.0-arduino2-i386-apple-darwin11.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino2-i386-apple-darwin11.tar.bz2"
-            },
-            {
-              "size": "745081",
-              "checksum": "SHA-256:9635af5a35bdca11804c07582d7beec458140fb6e3308168c3deda18dc6790fa",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "avrdude-6.3.0-arduino2-x86_64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino2-x86_64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "731802",
-              "checksum": "SHA-256:790b6cb610c48e73a2a0f65dcee9903d2fd7f1b0a1f75008a9a21f50a60c7251",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "avrdude-6.3.0-arduino2-i686-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino2-i686-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "608496",
-              "checksum": "SHA-256:8eaf98ea41fbd4450483488ef31710cbcc43c0412dbc8e1e1b582feaab6eca30",
-              "host": "i686-mingw32",
-              "archiveFileName": "avrdude-6.3.0-arduino2-i686-w64-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino2-i686-w64-mingw32.zip"
-            }
-          ]
-        },
-        {
-          "name": "avrdude",
-          "version": "6.3.0-arduino6",
-          "systems": [
-            {
-              "size": "644600",
-              "checksum": "SHA-256:2426207423d58eb0e5fc4df9493418f1cb54ba3f328fdc7c3bb582f920b9cbe7",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "avrdude-6.3.0-arduino6-armhf-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino6-armhf-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "696273",
-              "checksum": "SHA-256:d9a039c9e92d3dbb2011e75e6c044a1a4a2789e2fbf8386b1d580994811be084",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "avrdude-6.3.0-arduino6-i386-apple-darwin11.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino6-i386-apple-darwin11.tar.bz2"
-            },
-            {
-              "size": "746653",
-              "checksum": "SHA-256:97b4875cad6110c70101bb776f3ac37b64a2e73f036cd0b10afb6f4be96a6621",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "avrdude-6.3.0-arduino6-x86_64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino6-x86_64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "733127",
-              "checksum": "SHA-256:5f4bc4b0957b1d34cec9908b7f84a7c297b894b39fe16a4992c284b24c00d6fb",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "avrdude-6.3.0-arduino6-i686-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino6-i686-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "645859",
-              "checksum": "SHA-256:7468a1bcdfa459d175a095b102c0de28efc466accfb104305fbcad7832659ddc",
-              "host": "i686-mingw32",
-              "archiveFileName": "avrdude-6.3.0-arduino6-i686-w64-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino6-i686-w64-mingw32.zip"
-            }
-          ]
-        },
-        {
-          "name": "avrdude",
-          "version": "6.3.0-arduino8",
-          "systems": [
-            {
-              "size": "644550",
-              "checksum": "SHA-256:25a6834ae48019fccf37024236a1f79fe21760414292a4f3fa058d937ceee1ce",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "avrdude-6.3.0-arduino8-armhf-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino8-armhf-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "697268",
-              "checksum": "SHA-256:be8a33a7ec01bb7123279466ffa31371e0aa4fccefffcc23ce71810b59531947",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "avrdude-6.3.0-arduino8-i386-apple-darwin11.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino8-i386-apple-darwin11.tar.bz2"
-            },
-            {
-              "size": "711544",
-              "checksum": "SHA-256:85f38d02e2398d3b7f93da2ca8b830ee65bb73f66cc7a7b30c466d3cebf2da6e",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "avrdude-6.3.0-arduino8-x86_64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino8-x86_64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "701718",
-              "checksum": "SHA-256:8e2e4bc71d22e9d11ed143763b97f3aa2d164cdeee678a9deaf5b36e245b2d20",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "avrdude-6.3.0-arduino8-i686-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino8-i686-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "645996",
-              "checksum": "SHA-256:3a7592f6c33efd658b820c73d1058d3c868a297cbddb37da5644973c3b516d5e",
-              "host": "i686-mingw32",
-              "archiveFileName": "avrdude-6.3.0-arduino8-i686-w64-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino8-i686-w64-mingw32.zip"
-            }
-          ]
-        },
-        {
-          "name": "avrdude",
-          "version": "6.3.0-arduino9",
-          "systems": [
-            {
-              "size": "644550",
-              "checksum": "SHA-256:25a6834ae48019fccf37024236a1f79fe21760414292a4f3fa058d937ceee1ce",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "avrdude-6.3.0-arduino9-armhf-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino9-armhf-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "697309",
-              "checksum": "SHA-256:bfa06bc042dff252d3a8eded98da159484e75b46d2697da4d9446dcd2aea8465",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "avrdude-6.3.0-arduino9-i386-apple-darwin11.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino9-i386-apple-darwin11.tar.bz2"
-            },
-            {
-              "size": "711229",
-              "checksum": "SHA-256:c8cccb84e2fe49ee837b24f0a60a99e9c371dae26e84c5b0b22b6b6aab2f1f6a",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "avrdude-6.3.0-arduino9-x86_64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino9-x86_64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "701590",
-              "checksum": "SHA-256:4235a2d58e3c3224c603d6c5f0610507ed6c48ebf4051fdcce9f77a7646e218b",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "avrdude-6.3.0-arduino9-i686-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino9-i686-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "645974",
-              "checksum": "SHA-256:f3c5cfa8d0b3b0caee81c5b35fb6acff89c342ef609bf4266734c6266a256d4f",
-              "host": "i686-mingw32",
-              "archiveFileName": "avrdude-6.3.0-arduino9-i686-w64-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino9-i686-w64-mingw32.zip"
-            }
-          ]
-        },
-        {
-          "name": "avrdude",
-          "version": "6.3.0-arduino14",
-          "systems": [
-            {
-              "size": "219616",
-              "checksum": "SHA-256:d1a06275490d59a431c419788bbc53ffd5a79510dac1a35e63cf488621ba5589",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "avrdude-6.3.0-arduino14-armhf-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino14-armhf-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "229688",
-              "checksum": "SHA-256:439f5de150695e3732dd598bb182dae6ec1e3a5cdb580f855d9b58e485e84e66",
-              "host": "aarch64-linux-gnu",
-              "archiveFileName": "avrdude-6.3.0-arduino14-aarch64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino14-aarch64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "256917",
-              "checksum": "SHA-256:47d03991522722ce92120c60c4118685b7861909d895f34575001137961e4a63",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "avrdude-6.3.0-arduino14-i386-apple-darwin12.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino14-i386-apple-darwin11.tar.bz2"
-            },
-            {
-              "size": "253366",
-              "checksum": "SHA-256:7986e8f3059353dc08f9234f7dbc98d9b2fa2242f046f02a8243a060f7358bfc",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "avrdude-6.3.0-arduino14-x86_64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino14-x86_64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "244293",
-              "checksum": "SHA-256:4f100e3843c635064997df91d2a079ab15cd30d1d7fa227280abe6a7c3bc74ca",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "avrdude-6.3.0-arduino14-i686-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino14-i686-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "328363",
-              "checksum": "SHA-256:69293e0de2eff8de89f553477795c25005f674a320bbba4b0222beb0194aa297",
-              "host": "i686-mingw32",
-              "archiveFileName": "avrdude-6.3.0-arduino14-i686-w64-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino14-i686-w64-mingw32.zip"
-            }
-          ]
-        },
-        {
-          "name": "avrdude",
-          "version": "6.3.0-arduino16",
-          "systems": [
-            {
-              "size": "219642",
-              "checksum": "SHA-256:6fc443445440f0e2d95d70013ed075bceffc2a1babc1e7d4f1ae69c3fe268c57",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "avrdude-6.3.0-arduino16-armhf-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino16-armhf-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "229997",
-              "checksum": "SHA-256:7a2726ab2fd18b910abc3d9dd33c4b40d18c34cf12c46f3367932e7fd87c0197",
-              "host": "aarch64-linux-gnu",
-              "archiveFileName": "avrdude-6.3.0-arduino16-aarch64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino16-aarch64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "279172",
-              "checksum": "SHA-256:f93dc12a4b30a335ab24b3c628e6cad0ebf2f8adfb7ef50f87c0fc17165b2156",
-              "host": "x86_64-apple-darwin15",
-              "archiveFileName": "avrdude-6.3.0-arduino16-i386-apple-darwin11.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino16-i386-apple-darwin11.tar.bz2"
-            },
-            {
-              "size": "254085",
-              "checksum": "SHA-256:57856d6e388d333d924afa3e2d5525161dbe0dc670e7caae2720e249606175a7",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "avrdude-6.3.0-arduino16-x86_64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino16-x86_64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "244393",
-              "checksum": "SHA-256:bdf73358991243a9a8de11a42c80c4ec4b14c82f2222cb0c3c181f62656c41fb",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "avrdude-6.3.0-arduino16-i686-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino16-i686-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "328456",
-              "checksum": "SHA-256:781c16a8bf813fa68fc0f47d427279053c9e098c3aed7165449ac4f0137304dd",
-              "host": "i686-mingw32",
-              "archiveFileName": "avrdude-6.3.0-arduino16-i686-w64-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino16-i686-w64-mingw32.zip"
-            }
-          ]
-        },
-        {
-          "name": "avrdude",
-          "version": "6.3.0-arduino17",
-          "systems": [
-            {
-              "size": "219631",
-              "checksum": "SHA-256:2a8e68c5d803aa6f902ef219f177ec3a4c28275d85cbe272962ad2cd374f50d1",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "avrdude-6.3.0-arduino17-armhf-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino17-armhf-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "229852",
-              "checksum": "SHA-256:6cf948f751acfe7b96684537f2291c766ec8b54b4f7dc95539864821456fa9fc",
-              "host": "aarch64-linux-gnu",
-              "archiveFileName": "avrdude-6.3.0-arduino17-aarch64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino17-aarch64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "279045",
-              "checksum": "SHA-256:120cc9edaae699e7e9ac50b1b8eb0e7d51fdfa555bac54233c2511e6ee5418c9",
-              "host": "x86_64-apple-darwin12",
-              "archiveFileName": "avrdude-6.3.0-arduino17-x86_64-apple-darwin12.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino17-x86_64-apple-darwin12.tar.bz2"
-            },
-            {
-              "size": "254271",
-              "checksum": "SHA-256:accdfb920af2aabf4f7461d2ac73c0751760f525216dc4e7657427a78c60d13d",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "avrdude-6.3.0-arduino17-x86_64-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino17-x86_64-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "244550",
-              "checksum": "SHA-256:5c8cc6c17db9300e1451fe41cd7178b0442b4490ee6fdbc0aed9811aef96c05f",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "avrdude-6.3.0-arduino17-i686-pc-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino17-i686-pc-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "328460",
-              "checksum": "SHA-256:e99188873c7c5ad8f8f906f068c33600e758b2e36cce3adbd518a21bd266749d",
-              "host": "i686-mingw32",
-              "archiveFileName": "avrdude-6.3.0-arduino17-i686-w64-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/avrdude-6.3.0-arduino17-i686-w64-mingw32.zip"
-            }
-          ]
-        },
-        {
-          "name": "arduinoOTA",
-          "version": "1.0.0",
-          "systems": [
-            {
-              "size": "2044124",
-              "checksum": "SHA-256:850a86876403cb45c944590a8cc7f9d8ef6d53ed853f7a9593ec395c4c1c6b2d",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "arduinoOTA-1.0.0-linux32.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.0.0-linux32.tar.bz2"
-            },
-            {
-              "size": "2178772",
-              "checksum": "SHA-256:f01f25e02787492a8a30414230635adae76ed85228045437433892d185991f9e",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "arduinoOTA-1.0.0-linux64.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.0.0-linux64.tar.bz2"
-            },
-            {
-              "size": "1961623",
-              "checksum": "SHA-256:0ca6c0a93bfad50be0b6e62dc51ba6c3267b809bab4ec91ef9606ab7d838e46b",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "arduinoOTA-1.0.0-linuxarm.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.0.0-linuxarm.tar.bz2"
-            },
-            {
-              "size": "2180617",
-              "checksum": "SHA-256:e63c6034da2c1a7fe453eaa29c22df88627cc0aa3c5cbab7635c19367b74ee59",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "arduinoOTA-1.0.0-osx.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.0.0-osx.tar.bz2"
-            },
-            {
-              "size": "2247970",
-              "checksum": "SHA-256:7bced1489217e07661ea1e75702a10a874b54f6146e2414ee47684c7eac014d1",
-              "host": "i686-mingw32",
-              "archiveFileName": "arduinoOTA-1.0.0-windows.zip",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.0.0-windows.zip"
-            }
-          ]
-        },
-        {
-          "name": "arduinoOTA",
-          "version": "1.1.1",
-          "systems": [
-            {
-              "size": "2045036",
-              "checksum": "SHA-256:7ac91ef1d5b357c0ceb790be02ef54986db598ba5a42fffbd6c8ecbdd6a271ef",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "arduinoOTA-1.1.1-linux_386.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.1.1-linux_386.tar.bz2"
-            },
-            {
-              "size": "2178288",
-              "checksum": "SHA-256:eb5ad0a457dd7f610f7f9b85454399c36755673d61a16f9d07cdfcbbb32ec277",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "arduinoOTA-1.1.1-linux_amd64.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.1.1-linux_amd64.tar.bz2"
-            },
-            {
-              "size": "1962115",
-              "checksum": "SHA-256:e4880d83df3d3f6f4b7b7bcde161e80a0556877468803a3c6066ee4ad18a374c",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "arduinoOTA-1.1.1-linux_arm.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.1.1-linux_arm.tar.bz2"
-            },
-            {
-              "size": "2181376",
-              "checksum": "SHA-256:a1ce7cf578982f3af5e4fab6b5839e44830d7a41cb093faba5c4b45952a6fa55",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "arduinoOTA-1.1.1-darwin_amd64.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.1.1-darwin_amd64.tar.bz2"
-            },
-            {
-              "size": "2248431",
-              "checksum": "SHA-256:b2d3610c77f969a68cd75b6ea66bf63ec10c263937009d99147fbcd975c90006",
-              "host": "i686-mingw32",
-              "archiveFileName": "arduinoOTA-1.1.1-windows_386.zip",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.1.1-windows_386.zip"
-            }
-          ]
-        },
-        {
-          "name": "arduinoOTA",
-          "version": "1.2.0",
-          "systems": [
-            {
-              "size": "1839854",
-              "checksum": "SHA-256:7157a0b56620fb43b8dfb4afd958f8b294476a5ce4322c212167ca5d4092f2d9",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "arduinoOTA-1.2.0-linux_386.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.2.0-linux_386.tar.bz2"
-            },
-            {
-              "size": "1974030",
-              "checksum": "SHA-256:f672c1c407c4cb10729a1d891bdb8b010e2043e5415e1c2559bf39cdeaede78c",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "arduinoOTA-1.2.0-linux_amd64.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.2.0-linux_amd64.tar.bz2"
-            },
-            {
-              "size": "1787138",
-              "checksum": "SHA-256:ac49ffcd3239a6a52215f89dbda012d28f1296e6d79fc0efc3df06f919105744",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "arduinoOTA-1.2.0-linux_arm.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.2.0-linux_arm.tar.bz2"
-            },
-            {
-              "size": "1992476",
-              "checksum": "SHA-256:160e83e77d7a60514ca40fedf34f539124aac4b9ae0e2bfdf8fda11d958de38f",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "arduinoOTA-1.2.0-darwin_amd64.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.2.0-darwin_amd64.tar.bz2"
-            },
-            {
-              "size": "2003964",
-              "checksum": "SHA-256:9d26747093ab7966bfeffced9dbd7def0e164bba0db89f5efb3f7f8011496c8f",
-              "host": "i686-mingw32",
-              "archiveFileName": "arduinoOTA-1.2.0-windows_386.zip",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.2.0-windows_386.zip"
-            }
-          ]
-        },
-        {
-          "name": "arduinoOTA",
-          "version": "1.2.1",
-          "systems": [
-            {
-              "size": "2133779",
-              "checksum": "SHA-256:2ffdf64b78486c1d0bf28dc23d0ca36ab75ca92e84b9487246da01888abea6d4",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "arduinoOTA-1.2.1-linux_386.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.2.1-linux_386.tar.bz2"
-            },
-            {
-              "size": "2257689",
-              "checksum": "SHA-256:5b82310d53688480f34a916aac31cd8f2dd2be65dd8fa6c2445262262e1948f9",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "arduinoOTA-1.2.1-linux_amd64.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.2.1-linux_amd64.tar.bz2"
-            },
-            {
-              "size": "2093132",
-              "checksum": "SHA-256:ad54b3dcd586212941fd992bab573b53d13207a419a3f2981c970a085ae0e9e0",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "arduinoOTA-1.2.1-linux_arm.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.2.1-linux_arm.tar.bz2"
-            },
-            {
-              "size": "2093132",
-              "checksum": "SHA-256:ad54b3dcd586212941fd992bab573b53d13207a419a3f2981c970a085ae0e9e0",
-              "host": "aarch64-linux-gnu",
-              "archiveFileName": "arduinoOTA-1.2.1-linux_arm.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.2.1-linux_arm.tar.bz2"
-            },
-            {
-              "size": "2244088",
-              "checksum": "SHA-256:93a6d9f9c0c765d237be1665bf7a0a8e2b0b6d2a8531eae92db807f5515088a7",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "arduinoOTA-1.2.1-darwin_amd64.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.2.1-darwin_amd64.tar.bz2"
-            },
-            {
-              "size": "2237511",
-              "checksum": "SHA-256:e1ebf21f2c073fce25c09548c656da90d4ef6c078401ec6f323e0c58335115e5",
-              "host": "i686-mingw32",
-              "archiveFileName": "arduinoOTA-1.2.1-windows_386.zip",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.2.1-windows_386.zip"
-            }
-          ]
-        },
-        {
-          "name": "arduinoOTA",
-          "version": "1.3.0",
-          "systems": [
-            {
-              "size": "2633516",
-              "checksum": "SHA-256:3e7f59d6fbc7a724598303f0d3289d0c4fd137a8973437980658379a024887b2",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "arduinoOTA-1.3.0-linux_386.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.3.0-linux_386.tar.bz2"
-            },
-            {
-              "size": "2716248",
-              "checksum": "SHA-256:aa45ee2441ffc3a122daec5802941d1fa2ac47adf5c5c481b5e0daa4dc259ffa",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "arduinoOTA-1.3.0-linux_amd64.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.3.0-linux_amd64.tar.bz2"
-            },
-            {
-              "size": "2567435",
-              "checksum": "SHA-256:1888587409b56aef4ba0ab0e6703b3dccba7cc3a022756ba9b908247e5d5a656",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "arduinoOTA-1.3.0-linux_arm.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.3.0-linux_arm.tar.bz2"
-            },
-            {
-              "size": "2472427",
-              "checksum": "SHA-256:835ed8f37cffac37e979d1b0f6041559592d3d98be52f0e8611b76c4858e4113",
-              "host": "aarch64-linux-gnu",
-              "archiveFileName": "arduinoOTA-1.3.0-linux_arm64.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.3.0-linux_arm64.tar.bz2"
-            },
-            {
-              "size": "2766116",
-              "checksum": "SHA-256:d5d0f82ff829c0e434d12a2ee640a6fbd78f893ab37782edbb8b5bf2359d119e",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "arduinoOTA-1.3.0-darwin_amd64.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.3.0-darwin_amd64.tar.bz2"
-            },
-            {
-              "size": "2768948",
-              "checksum": "SHA-256:051943844eee442460d2c709edefadca184287fffd2b6c100dd53aa742aa05f6",
-              "host": "i686-mingw32",
-              "archiveFileName": "arduinoOTA-1.3.0-windows_386.zip",
-              "url": "http://downloads.arduino.cc/tools/arduinoOTA-1.3.0-windows_386.zip"
-            }
-          ]
-        },
-        {
-          "name": "bossac",
-          "version": "1.5-arduino",
-          "systems": [
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/bossac-1.5-arduino2-arm-linux-gnueabihf.tar.bz2",
-              "archiveFileName": "bossac-1.5-arduino2-arm-linux-gnueabihf.tar.bz2",
-              "checksum": "SHA-256:7b61b7814e5b57bcbd853439fc9cd3e98af4abfdd369bf039c6917f9599e44b9",
-              "size": "199550"
-            },
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/bossac-1.5-arduino2-mingw32.tar.gz",
-              "archiveFileName": "bossac-1.5-arduino2-mingw32.tar.gz",
-              "checksum": "SHA-256:9d849a34f0b26c25c6a8c4d741cd749dea238cade73b57a3048f248c431d9cc9",
-              "size": "222283"
-            },
-            {
-              "host": "x86_64-apple-darwin",
-              "url": "http://downloads.arduino.cc/bossac-1.5-arduino2-i386-apple-darwin14.3.0.tar.gz",
-              "archiveFileName": "bossac-1.5-arduino2-i386-apple-darwin14.3.0.tar.gz",
-              "checksum": "SHA-256:8f07e50a1f887cb254092034c6a4482d73209568cd83cb624d6625d66794f607",
-              "size": "64120"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/bossac-1.5-arduino2-x86_64-linux-gnu.tar.gz",
-              "archiveFileName": "bossac-1.5-arduino2-x86_64-linux-gnu.tar.gz",
-              "checksum": "SHA-256:42785329155dcb39872d4d30a2a9d31e0f0ce3ae7e34a3ed3d840cbc909c4657",
-              "size": "30431"
-            },
-            {
-              "host": "i686-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/bossac-1.5-arduino2-i486-linux-gnu.tar.gz",
-              "archiveFileName": "bossac-1.5-arduino2-i486-linux-gnu.tar.gz",
-              "checksum": "SHA-256:ac56e553bbd6d992fa5592ace90996806230ab582f2bf9f8590836fec9dabef6",
-              "size": "29783"
-            }
-          ]
-        },
-        {
-          "name": "bossac",
-          "version": "1.6-arduino",
-          "systems": [
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.6-arduino-mingw32.tar.gz",
-              "archiveFileName": "bossac-1.6-arduino-mingw32.tar.gz",
-              "checksum": "SHA-256:b59d64d3f7a43c894d0fba2dd1241bbaeefedf8c902130a24d8ec63b08f9ff6a",
-              "size": "222517"
-            },
-            {
-              "host": "x86_64-apple-darwin",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.6-arduino-i386-apple-darwin14.4.0.tar.gz",
-              "archiveFileName": "bossac-1.6-arduino-i386-apple-darwin14.4.0.tar.gz",
-              "checksum": "SHA-256:6b3b686a782b6587c64c85db80085c9089c5ea1b051e49e5af17b3c6109c8efa",
-              "size": "64538"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.6-arduino-x86_64-linux-gnu.tar.gz",
-              "archiveFileName": "bossac-1.6-arduino-x86_64-linux-gnu.tar.gz",
-              "checksum": "SHA-256:2ce7a54d609b4ce3b678147202b2556dd1ce5b318de48a018c676521b994c7a7",
-              "size": "30649"
-            },
-            {
-              "host": "i686-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.6-arduino-i486-linux-gnu.tar.gz",
-              "archiveFileName": "bossac-1.6-arduino-i486-linux-gnu.tar.gz",
-              "checksum": "SHA-256:5c320bf5cfdbf03e3f648642e6de325e459a061fcf96b2215cb955263f7467b2",
-              "size": "30072"
-            }
-          ]
-        },
-        {
-          "name": "bossac",
-          "version": "1.6.1-arduino",
-          "systems": [
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/bossac-1.6.1-arduino-arm-linux-gnueabihf.tar.bz2",
-              "archiveFileName": "bossac-1.6.1-arduino-arm-linux-gnueabihf.tar.bz2",
-              "checksum": "SHA-256:8c4e63db982178919c824e7a35580dffc95c3426afa7285de3eb583982d4d391",
-              "size": "201341"
-            },
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/bossac-1.6.1-arduino-mingw32.tar.gz",
-              "archiveFileName": "bossac-1.6.1-arduino-mingw32.tar.gz",
-              "checksum": "SHA-256:d59f43e2e83a337d04c4ae88b195a4ee175b8d87fff4c43144d23412a4a9513b",
-              "size": "222918"
-            },
-            {
-              "host": "x86_64-apple-darwin",
-              "url": "http://downloads.arduino.cc/bossac-1.6.1-arduino-i386-apple-darwin14.5.0.tar.gz",
-              "archiveFileName": "bossac-1.6.1-arduino-i386-apple-darwin14.5.0.tar.gz",
-              "checksum": "SHA-256:2f80ef569a3fb19da60ab3489e49d8fe7d4699876acf30ff4938c632230a09aa",
-              "size": "64587"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/bossac-1.6.1-arduino-x86_64-linux-gnu.tar.gz",
-              "archiveFileName": "bossac-1.6.1-arduino-x86_64-linux-gnu.tar.gz",
-              "checksum": "SHA-256:b78afc66c00ccfdd69a08bd3959c260a0c64ccce78a71d5a1135ae4437ff40db",
-              "size": "30869"
-            },
-            {
-              "host": "i686-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/bossac-1.6.1-arduino-i486-linux-gnu.tar.gz",
-              "archiveFileName": "bossac-1.6.1-arduino-i486-linux-gnu.tar.gz",
-              "checksum": "SHA-256:1e211347569d75193b337296a10dd25b0ce04419e3d7dc644355178b6b514f92",
-              "size": "30320"
-            }
-          ]
-        },
-        {
-          "name": "bossac",
-          "version": "1.7.0",
-          "systems": [
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.7.0-mingw32.tar.gz",
-              "archiveFileName": "bossac-1.7.0-mingw32.tar.gz",
-              "checksum": "SHA-256:9ef7d11b4fabca0adc17102a0290957d5cc26ce46b422c3a5344722c80acc7b2",
-              "size": "243066"
-            },
-            {
-              "host": "x86_64-apple-darwin",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.7.0-i386-apple-darwin15.6.0.tar.gz",
-              "archiveFileName": "bossac-1.7.0-i386-apple-darwin15.6.0.tar.gz",
-              "checksum": "SHA-256:feac36ab38876c163dcf51bdbcfbed01554eede3d41c59a0e152e170fe5164d2",
-              "size": "63822"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.7.0-x86_64-linux-gnu.tar.gz",
-              "archiveFileName": "bossac-1.7.0-x86_64-linux-gnu.tar.gz",
-              "checksum": "SHA-256:9475c0c8596c1ba12dcbce60e48fef7559087fa8eccbea7bab732113f3c181ee",
-              "size": "31373"
-            },
-            {
-              "host": "i686-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.7.0-i686-linux-gnu.tar.gz",
-              "archiveFileName": "bossac-1.7.0-i686-linux-gnu.tar.gz",
-              "checksum": "SHA-256:17003b0bdc698d52eeb91b09c34aec501c6e0285b4aa88659ab7cc407a451a4d",
-              "size": "31086"
-            },
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.7.0-arm-linux-gnueabihf.tar.gz",
-              "archiveFileName": "bossac-1.7.0-arm-linux-gnueabihf.tar.gz",
-              "checksum": "SHA-256:09e46d0af61b2189caaac0bc6d4dd15cb22c167fdedc56ec98602dd5f10e68e0",
-              "size": "27382"
-            }
-          ]
-        },
-        {
-          "name": "bossac",
-          "version": "1.7.0-arduino3",
-          "systems": [
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.7.0-arduino3-windows.tar.gz",
-              "archiveFileName": "bossac-1.7.0-arduino3-windows.tar.gz",
-              "checksum": "SHA-256:62745cc5a98c26949ec9041ef20420643c561ec43e99dae659debf44e6836526",
-              "size": "3607421"
-            },
-            {
-              "host": "x86_64-apple-darwin",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.7.0-arduino3-osx.tar.gz",
-              "archiveFileName": "bossac-1.7.0-arduino3-osx.tar.gz",
-              "checksum": "SHA-256:adb3c14debd397d8135e9e970215c6972f0e592c7af7532fa15f9ce5e64b991f",
-              "size": "75510"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.7.0-arduino3-linux64.tar.gz",
-              "archiveFileName": "bossac-1.7.0-arduino3-linux64.tar.gz",
-              "checksum": "SHA-256:1ae54999c1f97234a5c603eb99ad39313b11746a4ca517269a9285afa05f9100",
-              "size": "207271"
-            },
-            {
-              "host": "i686-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.7.0-arduino3-linux32.tar.gz",
-              "archiveFileName": "bossac-1.7.0-arduino3-linux32.tar.gz",
-              "checksum": "SHA-256:4ac4354746d1a09258f49a43ef4d1baf030d81c022f8434774268b00f55d3ec3",
-              "size": "193577"
-            },
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.7.0-arduino3-linuxarm.tar.gz",
-              "archiveFileName": "bossac-1.7.0-arduino3-linuxarm.tar.gz",
-              "checksum": "SHA-256:626c6cc548046901143037b782bf019af1663bae0d78cf19181a876fb9abbb90",
-              "size": "193941"
-            },
-            {
-              "host": "aarch64-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.7.0-arduino3-linuxaarch64.tar.gz",
-              "archiveFileName": "bossac-1.7.0-arduino3-linuxaarch64.tar.gz",
-              "checksum": "SHA-256:a098b2cc23e29f0dc468416210d097c4a808752cd5da1a7b9b8b7b931a04180b",
-              "size": "268365"
-            }
-          ]
-        },
-        {
-          "name": "bossac",
-          "version": "1.8.0-48-gb176eee",
-          "systems": [
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.8-48-gb176eee-i686-w64-mingw32.tar.gz",
-              "archiveFileName": "bossac-1.8-48-gb176eee-i686-w64-mingw32.tar.gz",
-              "checksum": "SHA-256:4523a6897f3dfd673fe821c5cfbac8d6a12782e7a36b312b9ee7d41deec2a10a",
-              "size": "91219"
-            },
-            {
-              "host": "x86_64-apple-darwin",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.8-48-gb176eee-i386-apple-darwin16.1.0.tar.gz",
-              "archiveFileName": "bossac-1.8-48-gb176eee-i386-apple-darwin16.1.0.tar.gz",
-              "checksum": "SHA-256:581ecc16021de36638ae14e9e064ffb4a1d532a11502f4252da8bcdf5ce1d649",
-              "size": "39150"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.8-48-gb176eee-x86_64-linux-gnu.tar.gz",
-              "archiveFileName": "bossac-1.8-48-gb176eee-x86_64-linux-gnu.tar.gz",
-              "checksum": "SHA-256:1347eec67f5b90b785abdf6c8a8aa59129d0c016de7ff9b5ac1690378eacca3c",
-              "size": "37798"
-            },
-            {
-              "host": "i686-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.8-48-gb176eee-i486-linux-gnu.tar.gz",
-              "archiveFileName": "bossac-1.8-48-gb176eee-i486-linux-gnu.tar.gz",
-              "checksum": "SHA-256:4c7492f876b8269aa9d8bcaad3aeda31acf1a0292383093b6d9f5f1d23fdafc3",
-              "size": "37374"
-            },
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.8-48-gb176eee-arm-linux-gnueabihf.tar.gz",
-              "archiveFileName": "bossac-1.8-48-gb176eee-arm-linux-gnueabihf.tar.gz",
-              "checksum": "SHA-256:2001e4a592f3aefd22f213b1ddd6f5d8d5e74bd04080cf1b97c24cbaa81b10ed",
-              "size": "34825"
-            }
-          ]
-        },
-        {
-          "name": "bossac",
-          "version": "1.9.1-arduino1",
-          "systems": [
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.9.1-arduino1-windows.tar.gz",
-              "archiveFileName": "bossac-1.9.1-arduino1-windows.tar.gz",
-              "checksum": "SHA-256:fe2d6ef78ca711c78e104e258357ed06b09e95e9356dc72d8d2c9f6670af4b7a",
-              "size": "1260118"
-            },
-            {
-              "host": "x86_64-apple-darwin",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.9.1-arduino1-osx.tar.gz",
-              "archiveFileName": "bossac-1.9.1-arduino1-osx.tar.gz",
-              "checksum": "SHA-256:c356632f98d5bae9b4f5d3ad823a5ee89b23078c2b835e8ac39a208f4855b0e6",
-              "size": "47835"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.9.1-arduino1-linux64.tar.gz",
-              "archiveFileName": "bossac-1.9.1-arduino1-linux64.tar.gz",
-              "checksum": "SHA-256:d3d324a3503a8db825c01f3b38519e4d4824c4d0e42cb399a16c1e074f9a9a86",
-              "size": "399453"
-            },
-            {
-              "host": "i686-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.9.1-arduino1-linux32.tar.gz",
-              "archiveFileName": "bossac-1.9.1-arduino1-linux32.tar.gz",
-              "checksum": "SHA-256:eec622b8b5a8642af94ec23febfe14c928edd734f144db73b146bf6708d2057f",
-              "size": "384792"
-            },
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.9.1-arduino1-linuxarm.tar.gz",
-              "archiveFileName": "bossac-1.9.1-arduino1-linuxarm.tar.gz",
-              "checksum": "SHA-256:b42061d2fa2dbd6910d0d295e024f2cff7bb44f3e2ecc0bc2fe71a1f31b0ecba",
-              "size": "361799"
-            },
-            {
-              "host": "aarch64-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.9.1-arduino1-linuxaarch64.tar.gz",
-              "archiveFileName": "bossac-1.9.1-arduino1-linuxaarch64.tar.gz",
-              "checksum": "SHA-256:b271013841e1e25ee55f241e8c50a56ed775d3b322844e1e3099231ba17f3868",
-              "size": "442657"
-            }
-          ]
-        },
-        {
-          "name": "bossac",
-          "version": "1.9.1-arduino2",
-          "systems": [
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.9.1-arduino2-windows.tar.gz",
-              "archiveFileName": "bossac-1.9.1-arduino2-windows.tar.gz",
-              "checksum": "SHA-256:5c994d04354f0db8e4bea136f49866d2ba537f0af74b2e78026f2d4fc75e3e39",
-              "size": "1260628"
-            },
-            {
-              "host": "x86_64-apple-darwin",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.9.1-arduino2-osx.tar.gz",
-              "archiveFileName": "bossac-1.9.1-arduino2-osx.tar.gz",
-              "checksum": "SHA-256:b7732129364a378676604db6579c9b8dab50dd965fb50d7a3afff1839c97ff80",
-              "size": "47870"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.9.1-arduino2-linux64.tar.gz",
-              "archiveFileName": "bossac-1.9.1-arduino2-linux64.tar.gz",
-              "checksum": "SHA-256:9eb549874391521999cee13dc823a2cfc8866b8246945339a281808d99c72d2c",
-              "size": "399532"
-            },
-            {
-              "host": "i686-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.9.1-arduino2-linux32.tar.gz",
-              "archiveFileName": "bossac-1.9.1-arduino2-linux32.tar.gz",
-              "checksum": "SHA-256:10d69f53f169f25afee2dd583dfd9dc803c10543e6c5260d106725cb0d174900",
-              "size": "384951"
-            },
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.9.1-arduino2-linuxarm.tar.gz",
-              "archiveFileName": "bossac-1.9.1-arduino2-linuxarm.tar.gz",
-              "checksum": "SHA-256:c9539d161d23231b5beb1d09a71829744216c7f5bc2857a491999c3e567f5b19",
-              "size": "361915"
-            },
-            {
-              "host": "aarch64-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/bossac-1.9.1-arduino2-linuxaarch64.tar.gz",
-              "archiveFileName": "bossac-1.9.1-arduino2-linuxaarch64.tar.gz",
-              "checksum": "SHA-256:c167fa0ea223966f4d21f5592da3888bcbfbae385be6c5c4e41f8abff35f5cb1",
-              "size": "442853"
-            }
-          ]
-        },
-        {
-          "name": "openocd",
-          "version": "0.9.0-arduino",
-          "systems": [
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/tools/OpenOCD-0.9.0-arduino-arm-linux-gnueabihf.tar.bz2",
-              "archiveFileName": "OpenOCD-0.9.0-dev-arduino-arm-linux-gnueabihf.tar.bz2",
-              "checksum": "SHA-256:a84e7c4cba853f2c937d77286f8a0ca317447d3873e51cbd2a2d41424e044a18",
-              "size": "1402283"
-            },
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/tools/OpenOCD-0.9.0-arduino-i686-pc-cygwin.tar.bz2",
-              "archiveFileName": "OpenOCD-0.9.0-arduino-i686-pc-cygwin.tar.bz2",
-              "checksum": "SHA-256:5310bdd3730168a33b09b68558e908ca8b2fec25620c488f50a5fb35d0d1effd",
-              "size": "2360705"
-            },
-            {
-              "host": "x86_64-apple-darwin",
-              "url": "http://downloads.arduino.cc/tools/OpenOCD-0.9.0-arduino-x86_64-apple-darwin14.3.0.tar.bz2",
-              "archiveFileName": "OpenOCD-0.9.0-arduino-x86_64-apple-darwin14.3.0.tar.bz2",
-              "checksum": "SHA-256:ef90769c07b8018cec3a5054e690ac6c196e03720e102ac5038c3f9da4e44782",
-              "size": "2275101"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/OpenOCD-0.9.0-arduino-x86_64-linux-gnu.tar.bz2",
-              "archiveFileName": "OpenOCD-0.9.0-arduino-x86_64-linux-gnu.tar.bz2",
-              "checksum": "SHA-256:c350409f7badf213dfcc516ea34289461ad92d87806e8e33945508a2c6b2c0b3",
-              "size": "1210796"
-            },
-            {
-              "host": "i686-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/OpenOCD-0.9.0-arduino-i486-linux-gnu.tar.bz2",
-              "archiveFileName": "OpenOCD-0.9.0-arduino-i486-linux-gnu.tar.bz2",
-              "checksum": "SHA-256:4c9793dfd7822b0fc959d039e5ecabfa89092ee2911abfdc7b5905deb171499a",
-              "size": "1129654"
-            }
-          ]
-        },
-        {
-          "name": "openocd",
-          "version": "0.9.0-arduino5-static",
-          "systems": [
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.9.0-arduino5-static-arm-linux-gnueabihf.tar.bz2",
-              "archiveFileName": "openocd-0.9.0-arduino5-static-arm-linux-gnueabihf.tar.bz2",
-              "checksum": "SHA-256:cef48c1448664612dd25168f0a56962aec4ce2f1d7c06dafd86a1b606dc8ae20",
-              "size": "1319000"
-            },
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.9.0-arduino5-static-i686-w64-mingw32.zip",
-              "archiveFileName": "openocd-0.9.0-arduino5-static-i686-w64-mingw32.zip",
-              "checksum": "SHA-256:54c70a0bfa1b0a3a592d6ee9ab532f9715e1dede2e7d46a3232abd72de274c5a",
-              "size": "1641209"
-            },
-            {
-              "host": "x86_64-apple-darwin",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.9.0-arduino5-static-x86_64-apple-darwin15.6.0.tar.bz2",
-              "archiveFileName": "openocd-0.9.0-arduino5-static-x86_64-apple-darwin15.6.0.tar.bz2",
-              "checksum": "SHA-256:14be5c5400e1a32c3d6a15f9c8d2f438634974ab263ff437b91b527e5b5d53a4",
-              "size": "1235752"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.9.0-arduino5-static-x86_64-linux-gnu.tar.bz2",
-              "archiveFileName": "openocd-0.9.0-arduino5-static-x86_64-linux-gnu.tar.bz2",
-              "checksum": "SHA-256:8e378bdcd71c93a39818c16b49b91128c8028e3d9675551ba7eff39462391ba2",
-              "size": "1393855"
-            },
-            {
-              "host": "i686-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.9.0-arduino5-static-i686-linux-gnu.tar.bz2",
-              "archiveFileName": "openocd-0.9.0-arduino5-static-i686-linux-gnu.tar.bz2",
-              "checksum": "SHA-256:8e0787f54e204fe6e9071b2b7edf8a5e695492696f1182d447647fe5c0bd55bd",
-              "size": "1341739"
-            }
-          ]
-        },
-        {
-          "name": "openocd",
-          "version": "0.9.0-arduino6-static",
-          "systems": [
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.9.0-arduino6-static-arm-linux-gnueabihf.tar.bz2",
-              "archiveFileName": "openocd-0.9.0-arduino6-static-arm-linux-gnueabihf.tar.bz2",
-              "checksum": "SHA-256:5d596c90510f80d66f64a3615d74063a6a61f07b79be475592a3c76bf0deb3ca",
-              "size": "1319020"
-            },
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.9.0-arduino6-static-i686-w64-mingw32.zip",
-              "archiveFileName": "openocd-0.9.0-arduino6-static-i686-w64-mingw32.zip",
-              "checksum": "SHA-256:dde6c8cd42c179e819eeebee1d09829b0768ecb89b75fb10e1f053c1c65f9cf1",
-              "size": "1641514"
-            },
-            {
-              "host": "x86_64-apple-darwin",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.9.0-arduino6-static-x86_64-apple-darwin15.6.0.tar.bz2",
-              "archiveFileName": "openocd-0.9.0-arduino6-static-x86_64-apple-darwin15.6.0.tar.bz2",
-              "checksum": "SHA-256:00cd65339bc981ff0d4ab4876df8f89b1e60e476441fabca31d5fc2968bad9be",
-              "size": "1222523"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.9.0-arduino6-static-x86_64-linux-gnu.tar.bz2",
-              "archiveFileName": "openocd-0.9.0-arduino6-static-x86_64-linux-gnu.tar.bz2",
-              "checksum": "SHA-256:d2f58bbd0661b755fdb8a307d197f119d838b066f5510b25ee766e47d1774543",
-              "size": "1394293"
-            },
-            {
-              "host": "i686-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.9.0-arduino6-static-i686-linux-gnu.tar.bz2",
-              "archiveFileName": "openocd-0.9.0-arduino6-static-i686-linux-gnu.tar.bz2",
-              "checksum": "SHA-256:88d948c2062c73c0c93e649e099aaac4b009018cff365f44cfc5b47907043dc9",
-              "size": "1340444"
-            }
-          ]
-        },
-        {
-          "name": "openocd",
-          "version": "0.10.0-arduino7",
-          "systems": [
-            {
-              "size": "1638575",
-              "checksum": "SHA-256:f8e0d783e80a3d5f75ee82e9542315871d46e1e283a97447735f1cbcd8986b06",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "openocd-0.10.0-arduino7-static-arm-linux-gnueabihf.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino7-static-arm-linux-gnueabihf.tar.bz2"
-            },
-            {
-              "size": "1580739",
-              "checksum": "SHA-256:d47d728a9a8d98f28dc22e31d7127ced9de0d5e268292bf935e050ef1d2bdfd0",
-              "host": "aarch64-linux-gnu",
-              "archiveFileName": "openocd-0.10.0-arduino7-static-aarch64-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino7-static-aarch64-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "1498970",
-              "checksum": "SHA-256:1e539a587a0c54a551ce0dc542af10a2520b1c93bbfe2ca4ebaef4c83411df1a",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "openocd-0.10.0-arduino7-static-x86_64-apple-darwin13.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino7-static-x86_64-apple-darwin13.tar.bz2"
-            },
-            {
-              "size": "1701581",
-              "checksum": "SHA-256:91d418bd309ec1e98795c622cd25c936aa537c0b3828fa5bcb191389378a1b27",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "openocd-0.10.0-arduino7-static-x86_64-ubuntu12.04-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino7-static-x86_64-ubuntu12.04-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "1626347",
-              "checksum": "SHA-256:08a18f39d72a5626383503053a30a5da89eed7fdccb6f514b20b77403eb1b2b4",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "openocd-0.10.0-arduino7-static-i686-ubuntu12.04-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino7-static-i686-ubuntu12.04-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "2016965",
-              "checksum": "SHA-256:f251aec5471296e18aa540c3078d66475357a76a77c16c06a2d9345f4e12b3d5",
-              "host": "i686-mingw32",
-              "archiveFileName": "openocd-0.10.0-arduino7-static-i686-w64-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino7-static-i686-w64-mingw32.zip"
-            }
-          ]
-        },
-        {
-          "name": "openocd",
-          "version": "0.10.0-arduino8",
-          "systems": [
-            {
-              "size": "1714346",
-              "checksum": "SHA-256:86c4ea3086b4a1475fd8a1e1daf4585748be093dad4160e816b1bf2502501fb2",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "openocd-0.10.0-arduino8-static-arm-linux-gnueabihf.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino8-static-arm-linux-gnueabihf.tar.bz2"
-            },
-            {
-              "size": "1778371",
-              "checksum": "SHA-256:500cb112ee92092bbfce69649b90d0284752c5766f5aaf5c24dc754100788ef9",
-              "host": "aarch64-linux-gnu",
-              "archiveFileName": "openocd-0.10.0-arduino8-static-aarch64-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino8-static-aarch64-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "1653652",
-              "checksum": "SHA-256:584b513ebbc4a645a68234d964ba56f042aaf7668d84ba47398a07a294516cc4",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "openocd-0.10.0-arduino8-static-x86_64-apple-darwin13.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino8-static-x86_64-apple-darwin13.tar.bz2"
-            },
-            {
-              "size": "1845547",
-              "checksum": "SHA-256:455d4123146bf6b4b095de86d3340fd01e39bba9c70b2f0bb8e979ac4dddac39",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "openocd-0.10.0-arduino8-static-x86_64-ubuntu12.04-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino8-static-x86_64-ubuntu12.04-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "1781892",
-              "checksum": "SHA-256:5b44889984daefa966b8251edb98445169107ca32f974ca598d4c59e7d2c8901",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "openocd-0.10.0-arduino8-static-i686-ubuntu12.04-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino8-static-i686-ubuntu12.04-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "2190397",
-              "checksum": "SHA-256:35a92f32f2762ef9405d2c684ec7bea2e70c01068f380251aecd9290f5bd5b24",
-              "host": "i686-mingw32",
-              "archiveFileName": "openocd-0.10.0-arduino8-static-i686-w64-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino8-static-i686-w64-mingw32.zip"
-            }
-          ]
-        },
-        {
-          "name": "openocd",
-          "version": "0.10.0-arduino9",
-          "systems": [
-            {
-              "size": "1714657",
-              "checksum": "SHA-256:b814b16b52cef21eacf456cc7a89d7b5d4cb1385bfb8fb82963b7d8151824d93",
-              "host": "aarch64-linux-gnu",
-              "archiveFileName": "openocd-0.10.0-arduino9-static-aarch64-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino9-static-aarch64-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "1778177",
-              "checksum": "SHA-256:f0443e771f5e3a779949498d3c9bfffd1dd27cdf0ad7136a2db5e80447a7175a",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "openocd-0.10.0-arduino9-static-arm-linux-gnueabihf.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino9-static-arm-linux-gnueabihf.tar.bz2"
-            },
-            {
-              "size": "1782958",
-              "checksum": "SHA-256:a22872918df899cb808f9286141d42438ae5611156c143cfb692069f52a2bddd",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "openocd-0.10.0-arduino9-static-i686-ubuntu12.04-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino9-static-i686-ubuntu12.04-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "2190484",
-              "checksum": "SHA-256:f53f9a2b7f48a2ebc00ea9196bf559d15987d3779bcea4118ebec8925da5a1f6",
-              "host": "i686-mingw32",
-              "archiveFileName": "openocd-0.10.0-arduino9-static-i686-w64-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino9-static-i686-w64-mingw32.zip"
-            },
-            {
-              "size": "1655311",
-              "checksum": "SHA-256:6d47f97919f317bb6e5f1f903127604271d66d149f4625f29b8e0eb5f6c94c64",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "openocd-0.10.0-arduino9-static-x86_64-apple-darwin13.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino9-static-x86_64-apple-darwin13.tar.bz2"
-            },
-            {
-              "size": "1844365",
-              "checksum": "SHA-256:f624552b5ba56aa78d0c1a0e5d18cf2b5694db2ed44080968e22aa1af8f23c1b",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "openocd-0.10.0-arduino9-static-x86_64-ubuntu12.04-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino9-static-x86_64-ubuntu12.04-linux-gnu.tar.bz2"
-            }
-          ]
-        },
-        {
-          "name": "openocd",
-          "version": "0.10.0-arduino12",
-          "systems": [
-            {
-              "size": "1778706",
-              "checksum": "SHA-256:86e15186a44b87c00f5ddd9c05849d2ec107783dd18a5ac984ce2a71e34084ed",
-              "host": "aarch64-linux-gnu",
-              "archiveFileName": "openocd-0.10.0-arduino12-static-aarch64-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino12-static-aarch64-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "1855234",
-              "checksum": "SHA-256:5c6ca6189f61894ee77b29bc342f96cd14e4d7627fabeb2a8d2e2c534316cc38",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "openocd-0.10.0-arduino12-static-arm-linux-gnueabihf.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino12-static-arm-linux-gnueabihf.tar.bz2"
-            },
-            {
-              "size": "1844359",
-              "checksum": "SHA-256:4938742d3fec62941187666b8ded44d8f6c7a404920ff49d97fca484b9fd08af",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "openocd-0.10.0-arduino12-static-i686-ubuntu12.04-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino12-static-i686-ubuntu12.04-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "2276602",
-              "checksum": "SHA-256:1e23c0f1f809725db3e1f8d1e1f460a37fb7b2cf95e93c6e328e527501ab084c",
-              "host": "i686-mingw32",
-              "archiveFileName": "openocd-0.10.0-arduino12-static-i686-w64-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino12-static-i686-w64-mingw32.zip"
-            },
-            {
-              "size": "1723600",
-              "checksum": "SHA-256:b40d135449401870302bec073326d6f1df79da38d9dd21326314a5a90189a06e",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "openocd-0.10.0-arduino12-static-x86_64-apple-darwin13.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino12-static-x86_64-apple-darwin13.tar.bz2"
-            },
-            {
-              "size": "1918845",
-              "checksum": "SHA-256:bc48be10916f69f3a4b050f04babc14ee99dad1fc5da55ce606077991edab1d0",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "openocd-0.10.0-arduino12-static-x86_64-ubuntu12.04-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino12-static-x86_64-ubuntu12.04-linux-gnu.tar.bz2"
-            }
-          ]
-        },
-        {
-          "name": "openocd",
-          "version": "0.10.0-arduino13",
-          "systems": [
-            {
-              "size": "1820630",
-              "checksum": "SHA-256:47ae7a1a7961ac9daef001b011505b38777baac3c02dd7e673f62601df841427",
-              "host": "aarch64-linux-gnu",
-              "archiveFileName": "openocd-0.10.0-arduino13-static-aarch64-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino13-static-aarch64-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "1896478",
-              "checksum": "SHA-256:4dd38b701019ad2fbb58173a3bc6c58effd39501a4a8266256dfe169e7516655",
-              "host": "arm-linux-gnueabihf",
-              "archiveFileName": "openocd-0.10.0-arduino13-static-arm-linux-gnueabihf.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino13-static-arm-linux-gnueabihf.tar.bz2"
-            },
-            {
-              "size": "1883854",
-              "checksum": "SHA-256:788093504b25d2b9b772657215254ba178ed37773364ce240de68281efe40bd5",
-              "host": "i686-linux-gnu",
-              "archiveFileName": "openocd-0.10.0-arduino13-static-i686-ubuntu12.04-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino13-static-i686-ubuntu12.04-linux-gnu.tar.bz2"
-            },
-            {
-              "size": "2334654",
-              "checksum": "SHA-256:2f3b87c644569f47780b16b071cd0929a64a8899ec769f4ca7480d20d5503365",
-              "host": "i686-mingw32",
-              "archiveFileName": "openocd-0.10.0-arduino13-static-i686-w64-mingw32.zip",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino13-static-i686-w64-mingw32.zip"
-            },
-            {
-              "size": "1767137",
-              "checksum": "SHA-256:0f3f6e5e03355ffbbc84c4b4750e63c9315b7638c56d63df1b7795968208e6ba",
-              "host": "i386-apple-darwin11",
-              "archiveFileName": "openocd-0.10.0-arduino13-static-x86_64-apple-darwin13.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino13-static-x86_64-apple-darwin13.tar.bz2"
-            },
-            {
-              "size": "1955043",
-              "checksum": "SHA-256:e4b2ffbc9a29be21c32c6921c2e7c0ee39c664c4faca28a5f839c1df32d3bd24",
-              "host": "x86_64-linux-gnu",
-              "archiveFileName": "openocd-0.10.0-arduino13-static-x86_64-ubuntu12.04-linux-gnu.tar.bz2",
-              "url": "http://downloads.arduino.cc/tools/openocd-0.10.0-arduino13-static-x86_64-ubuntu12.04-linux-gnu.tar.bz2"
-            }
-          ]
-        },
-        {
-          "name": "CMSIS",
-          "version": "4.0.0-atmel",
-          "systems": [
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/CMSIS-4.0.0.tar.bz2",
-              "archiveFileName": "CMSIS-4.0.0.tar.bz2",
-              "checksum": "SHA-256:7d637d2d7a0c6bacc22065848a201db2fff124268e4a56868260d0f472b4bbb7",
-              "size": "17642623"
-            },
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/CMSIS-4.0.0.tar.bz2",
-              "archiveFileName": "CMSIS-4.0.0.tar.bz2",
-              "checksum": "SHA-256:7d637d2d7a0c6bacc22065848a201db2fff124268e4a56868260d0f472b4bbb7",
-              "size": "17642623"
-            },
-            {
-              "host": "x86_64-apple-darwin",
-              "url": "http://downloads.arduino.cc/CMSIS-4.0.0.tar.bz2",
-              "archiveFileName": "CMSIS-4.0.0.tar.bz2",
-              "checksum": "SHA-256:7d637d2d7a0c6bacc22065848a201db2fff124268e4a56868260d0f472b4bbb7",
-              "size": "17642623"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/CMSIS-4.0.0.tar.bz2",
-              "archiveFileName": "CMSIS-4.0.0.tar.bz2",
-              "checksum": "SHA-256:7d637d2d7a0c6bacc22065848a201db2fff124268e4a56868260d0f472b4bbb7",
-              "size": "17642623"
-            },
-            {
-              "host": "i686-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/CMSIS-4.0.0.tar.bz2",
-              "archiveFileName": "CMSIS-4.0.0.tar.bz2",
-              "checksum": "SHA-256:7d637d2d7a0c6bacc22065848a201db2fff124268e4a56868260d0f472b4bbb7",
-              "size": "17642623"
-            }
-          ]
-        },
-        {
-          "name": "CMSIS",
-          "version": "4.5.0",
-          "systems": [
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/CMSIS-4.5.0.tar.bz2",
-              "archiveFileName": "CMSIS-4.5.0.tar.bz2",
-              "checksum": "SHA-256:cd8f7eae9fc7c8b4a1b5e40b89b9666d33953b47d3d2eb81844f5af729fa224d",
-              "size": "31525196"
-            },
-            {
-              "host": "x86_64-apple-darwin",
-              "url": "http://downloads.arduino.cc/CMSIS-4.5.0.tar.bz2",
-              "archiveFileName": "CMSIS-4.5.0.tar.bz2",
-              "checksum": "SHA-256:cd8f7eae9fc7c8b4a1b5e40b89b9666d33953b47d3d2eb81844f5af729fa224d",
-              "size": "31525196"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/CMSIS-4.5.0.tar.bz2",
-              "archiveFileName": "CMSIS-4.5.0.tar.bz2",
-              "checksum": "SHA-256:cd8f7eae9fc7c8b4a1b5e40b89b9666d33953b47d3d2eb81844f5af729fa224d",
-              "size": "31525196"
-            },
-            {
-              "host": "i686-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/CMSIS-4.5.0.tar.bz2",
-              "archiveFileName": "CMSIS-4.5.0.tar.bz2",
-              "checksum": "SHA-256:cd8f7eae9fc7c8b4a1b5e40b89b9666d33953b47d3d2eb81844f5af729fa224d",
-              "size": "31525196"
-            },
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/CMSIS-4.5.0.tar.bz2",
-              "archiveFileName": "CMSIS-4.5.0.tar.bz2",
-              "checksum": "SHA-256:cd8f7eae9fc7c8b4a1b5e40b89b9666d33953b47d3d2eb81844f5af729fa224d",
-              "size": "31525196"
-            },
-            {
-              "host": "aarch64-linux-gnu",
-              "url": "http://downloads.arduino.cc/CMSIS-4.5.0.tar.bz2",
-              "archiveFileName": "CMSIS-4.5.0.tar.bz2",
-              "checksum": "SHA-256:cd8f7eae9fc7c8b4a1b5e40b89b9666d33953b47d3d2eb81844f5af729fa224d",
-              "size": "31525196"
-            },
-            {
-              "host": "all",
-              "url": "http://downloads.arduino.cc/CMSIS-4.5.0.tar.bz2",
-              "archiveFileName": "CMSIS-4.5.0.tar.bz2",
-              "checksum": "SHA-256:cd8f7eae9fc7c8b4a1b5e40b89b9666d33953b47d3d2eb81844f5af729fa224d",
-              "size": "31525196"
-            }
-          ]
-        },
-        {
-          "name": "CMSIS-Atmel",
-          "version": "1.0.0",
-          "systems": [
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/CMSIS-Atmel-1.0.0.tar.bz2",
-              "archiveFileName": "CMSIS-Atmel-1.0.0.tar.bz2",
-              "checksum": "SHA-256:b3c954570a2f8d9821c372e0864f5f0b86cfbeab8114ce95821f5c49758c7256",
-              "size": "1281654"
-            },
-            {
-              "host": "x86_64-apple-darwin",
-              "url": "http://downloads.arduino.cc/CMSIS-Atmel-1.0.0.tar.bz2",
-              "archiveFileName": "CMSIS-Atmel-1.0.0.tar.bz2",
-              "checksum": "SHA-256:b3c954570a2f8d9821c372e0864f5f0b86cfbeab8114ce95821f5c49758c7256",
-              "size": "1281654"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/CMSIS-Atmel-1.0.0.tar.bz2",
-              "archiveFileName": "CMSIS-Atmel-1.0.0.tar.bz2",
-              "checksum": "SHA-256:b3c954570a2f8d9821c372e0864f5f0b86cfbeab8114ce95821f5c49758c7256",
-              "size": "1281654"
-            },
-            {
-              "host": "i686-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/CMSIS-Atmel-1.0.0.tar.bz2",
-              "archiveFileName": "CMSIS-Atmel-1.0.0.tar.bz2",
-              "checksum": "SHA-256:b3c954570a2f8d9821c372e0864f5f0b86cfbeab8114ce95821f5c49758c7256",
-              "size": "1281654"
-            },
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/CMSIS-Atmel-1.0.0.tar.bz2",
-              "archiveFileName": "CMSIS-Atmel-1.0.0.tar.bz2",
-              "checksum": "SHA-256:b3c954570a2f8d9821c372e0864f5f0b86cfbeab8114ce95821f5c49758c7256",
-              "size": "1281654"
-            },
-            {
-              "host": "all",
-              "url": "http://downloads.arduino.cc/CMSIS-Atmel-1.0.0.tar.bz2",
-              "archiveFileName": "CMSIS-Atmel-1.0.0.tar.bz2",
-              "checksum": "SHA-256:b3c954570a2f8d9821c372e0864f5f0b86cfbeab8114ce95821f5c49758c7256",
-              "size": "1281654"
-            }
-          ]
-        },
-        {
-          "name": "CMSIS-Atmel",
-          "version": "1.1.0",
-          "systems": [
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/CMSIS-Atmel-1.1.0.tar.bz2",
-              "archiveFileName": "CMSIS-Atmel-1.1.0.tar.bz2",
-              "checksum": "SHA-256:3ea5ec0451f42dc2b97f869b027a9cf696241cfc927cfc48d74ccc7b396ba41b",
-              "size": "1659108"
-            },
-            {
-              "host": "x86_64-apple-darwin",
-              "url": "http://downloads.arduino.cc/CMSIS-Atmel-1.1.0.tar.bz2",
-              "archiveFileName": "CMSIS-Atmel-1.1.0.tar.bz2",
-              "checksum": "SHA-256:3ea5ec0451f42dc2b97f869b027a9cf696241cfc927cfc48d74ccc7b396ba41b",
-              "size": "1659108"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/CMSIS-Atmel-1.1.0.tar.bz2",
-              "archiveFileName": "CMSIS-Atmel-1.1.0.tar.bz2",
-              "checksum": "SHA-256:3ea5ec0451f42dc2b97f869b027a9cf696241cfc927cfc48d74ccc7b396ba41b",
-              "size": "1659108"
-            },
-            {
-              "host": "i686-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/CMSIS-Atmel-1.1.0.tar.bz2",
-              "archiveFileName": "CMSIS-Atmel-1.1.0.tar.bz2",
-              "checksum": "SHA-256:3ea5ec0451f42dc2b97f869b027a9cf696241cfc927cfc48d74ccc7b396ba41b",
-              "size": "1659108"
-            },
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/CMSIS-Atmel-1.1.0.tar.bz2",
-              "archiveFileName": "CMSIS-Atmel-1.1.0.tar.bz2",
-              "checksum": "SHA-256:3ea5ec0451f42dc2b97f869b027a9cf696241cfc927cfc48d74ccc7b396ba41b",
-              "size": "1659108"
-            },
-            {
-              "host": "all",
-              "url": "http://downloads.arduino.cc/CMSIS-Atmel-1.1.0.tar.bz2",
-              "archiveFileName": "CMSIS-Atmel-1.1.0.tar.bz2",
-              "checksum": "SHA-256:3ea5ec0451f42dc2b97f869b027a9cf696241cfc927cfc48d74ccc7b396ba41b",
-              "size": "1659108"
-            }
-          ]
-        },
-        {
-          "name": "CMSIS-Atmel",
-          "version": "1.2.0",
-          "systems": [
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/CMSIS-Atmel-1.2.0.tar.bz2",
-              "archiveFileName": "CMSIS-Atmel-1.2.0.tar.bz2",
-              "checksum": "SHA-256:5e02670be7e36be9691d059bee0b04ee8b249404687531f33893922d116b19a5",
-              "size": "2221805"
-            },
-            {
-              "host": "x86_64-apple-darwin",
-              "url": "http://downloads.arduino.cc/CMSIS-Atmel-1.2.0.tar.bz2",
-              "archiveFileName": "CMSIS-Atmel-1.2.0.tar.bz2",
-              "checksum": "SHA-256:5e02670be7e36be9691d059bee0b04ee8b249404687531f33893922d116b19a5",
-              "size": "2221805"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "https://downloads.arduino.cc/CMSIS-Atmel-1.2.0.tar.bz2",
-              "archiveFileName": "CMSIS-Atmel-1.2.0.tar.bz2",
-              "checksum": "SHA-256:5e02670be7e36be9691d059bee0b04ee8b249404687531f33893922d116b19a5",
-              "size": "2221805"
-            },
-            {
-              "host": "i686-pc-linux-gnu",
-              "url": "https://downloads.arduino.cc/CMSIS-Atmel-1.2.0.tar.bz2",
-              "archiveFileName": "CMSIS-Atmel-1.2.0.tar.bz2",
-              "checksum": "SHA-256:5e02670be7e36be9691d059bee0b04ee8b249404687531f33893922d116b19a5",
-              "size": "2221805"
-            },
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "https://downloads.arduino.cc/CMSIS-Atmel-1.2.0.tar.bz2",
-              "archiveFileName": "CMSIS-Atmel-1.2.0.tar.bz2",
-              "checksum": "SHA-256:5e02670be7e36be9691d059bee0b04ee8b249404687531f33893922d116b19a5",
-              "size": "2221805"
-            },
-            {
-              "host": "aarch64-linux-gnu",
-              "url": "https://downloads.arduino.cc/CMSIS-Atmel-1.2.0.tar.bz2",
-              "archiveFileName": "CMSIS-Atmel-1.2.0.tar.bz2",
-              "checksum": "SHA-256:5e02670be7e36be9691d059bee0b04ee8b249404687531f33893922d116b19a5",
-              "size": "2221805"
-            },
-            {
-              "host": "all",
-              "url": "https://downloads.arduino.cc/CMSIS-Atmel-1.2.0.tar.bz2",
-              "archiveFileName": "CMSIS-Atmel-1.2.0.tar.bz2",
-              "checksum": "SHA-256:5e02670be7e36be9691d059bee0b04ee8b249404687531f33893922d116b19a5",
-              "size": "2221805"
-            }
-          ]
-        },
-        {
-          "name": "dfu-util",
-          "version": "0.9.0-arduino1",
-          "systems": [
-            {
-              "host": "i386-apple-darwin11",
-              "url": "http://downloads.arduino.cc/tools/dfu-util-0.9.0-arduino1-osx.tar.bz2",
-              "archiveFileName": "dfu-util-0.9.0-arduino1-osx.tar.bz2",
-              "size": "68361",
-              "checksum": "SHA-256:ea9216c627b7aa2d3a9bffab97df937e3c580cce66753c428dc697c854a35271"
-            },
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/tools/dfu-util-0.9.0-arduino1-arm.tar.bz2",
-              "archiveFileName": "dfu-util-0.9.0-arduino1-arm.tar.bz2",
-              "size": "194826",
-              "checksum": "SHA-256:480637bf578e74b19753666a049f267d8ebcd9dfc8660d48f246bb76d5b806f9"
-            },
-            {
-              "host": "x86_64-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/dfu-util-0.9.0-arduino1-linux64.tar.bz2",
-              "archiveFileName": "dfu-util-0.9.0-arduino1-linux64.tar.bz2",
-              "size": "66230",
-              "checksum": "SHA-256:e8a4d5477ab8c44d8528f35bc7dfafa5f3f04dace513906514aea31adc6fd3ba"
-            },
-            {
-              "host": "i686-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/dfu-util-0.9.0-arduino1-linux32.tar.bz2",
-              "archiveFileName": "dfu-util-0.9.0-arduino1-linux32.tar.bz2",
-              "size": "62608",
-              "checksum": "SHA-256:17d69213914da04dadd6464d8adbcd3581dd930eb666b8f3336ab5383ce2127f"
-            },
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/tools/dfu-util-0.9.0-arduino1-windows.tar.bz2",
-              "archiveFileName": "dfu-util-0.9.0-arduino1-windows.tar.bz2",
-              "size": "377537",
-              "checksum": "SHA-256:29be01b298348be8b822391be7147b71a969d47bd5457d5b24cfa5981dbce78e"
-            }
-          ]
-        },
-        {
-          "name": "dfu-util",
-          "version": "0.9.0-arduino2",
-          "systems": [
-            {
-              "host": "i386-apple-darwin11",
-              "url": "http://downloads.arduino.cc/tools/dfu-util-0.9.0-arduino2-osx.tar.bz2",
-              "archiveFileName": "dfu-util-0.9.0-arduino2-osx.tar.bz2",
-              "size": "65137",
-              "checksum": "SHA-256:00e87178b81d5721f334d4b688267f19f36eed1d9710a912c9e44bb1a748f766"
-            },
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/tools/dfu-util-0.9.0-arduino2-arm.tar.bz2",
-              "archiveFileName": "dfu-util-0.9.0-arduino2-arm.tar.bz2",
-              "size": "198568",
-              "checksum": "SHA-256:b364a8fe1de697d7dd6c4135d341ddff6dbda7e33c707321c7dceab85dfc560b"
-            },
-            {
-              "host": "x86_64-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/dfu-util-0.9.0-arduino2-linux64.tar.bz2",
-              "archiveFileName": "dfu-util-0.9.0-arduino2-linux64.tar.bz2",
-              "size": "70996",
-              "checksum": "SHA-256:628e01772007e622dff6af82220c27bcdf1d0726ba886bd2b36807601f66e4e8"
-            },
-            {
-              "host": "i686-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/dfu-util-0.9.0-arduino2-linux32.tar.bz2",
-              "archiveFileName": "dfu-util-0.9.0-arduino2-linux32.tar.bz2",
-              "size": "71002",
-              "checksum": "SHA-256:7a6cec3d89c65119c52b6109cd92a9ec84bdf8a9d12083444d2c89e7ac16c84b"
-            },
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/tools/dfu-util-0.9.0-arduino2-windows.tar.bz2",
-              "archiveFileName": "dfu-util-0.9.0-arduino2-windows.tar.bz2",
-              "size": "394993",
-              "checksum": "SHA-256:8ec0e66acdc41941b6e25545f8c12e7eb7ba01a0bafae0b4ab4c99a70deb2ea5"
-            }
-          ]
-        },
-        {
-          "name": "windows-drivers",
-          "version": "1.6.9",
-          "systems": [
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/tools/drivers-arduino-windows-1.6.9.zip",
-              "archiveFileName": "drivers-arduino-windows-1.6.9.zip",
-              "checksum": "SHA-256:10d456ab18d164d42545255db8bef4ac9e1bf660cc89acb7a0980b5a486654ac",
-              "size": "7071714"
-            }
-          ]
-        },
-        {
-          "name": "windows-drivers",
-          "version": "1.8.0",
-          "systems": [
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/tools/drivers-arduino-windows-1.8.0.zip",
-              "archiveFileName": "drivers-arduino-windows-1.8.0.zip",
-              "checksum": "SHA-256:60614b326ad6860ed0cb99eb4cb2cb69f9ba6ba3784396d5441fe3f99004f8ec",
-              "size": "16302148"
-            }
-          ]
-        },
-        {
-          "name": "dfu-util",
-          "version": "0.8.0-stm32-arduino1",
-          "systems": [
-            {
-              "archiveFileName": "dfu-util-0.8.0-stm32-arduino1-darwin_amd64.tar.bz2",
-              "checksum": "SHA-256:bb146803a4152ce2647d72b2cde68ff95eb3017c2460f24c4db922adac1fbd12",
-              "host": "i386-apple-darwin11",
-              "size": "68381",
-              "url": "http://downloads.arduino.cc/arduino.org/dfu-util-0.8.0-stm32-arduino1-darwin_amd64.tar.bz2"
-            },
-            {
-              "archiveFileName": "dfu-util-0.8.0-stm32-arduino1-linux_arm.tar.bz2",
-              "checksum": "SHA-256:607e6b0f2d2787ed7837f26da30b100131e3db207f84b8aca94a377db6e9ae50",
-              "host": "arm-linux-gnueabihf",
-              "size": "213760",
-              "url": "http://downloads.arduino.cc/arduino.org/dfu-util-0.8.0-stm32-arduino1-linux_arm.tar.bz2"
-            },
-            {
-              "archiveFileName": "dfu-util-0.8.0-stm32-arduino1-stm32-linux_amd64.tar.bz2",
-              "checksum": "SHA-256:e44287494ebd22f59fc79766a94e20306e59c6c799f5bb1cddeed80db95000d9",
-              "host": "x86_64-linux-gnu",
-              "size": "68575",
-              "url": "http://downloads.arduino.cc/arduino.org/dfu-util-0.8.0-stm32-arduino1-linux_amd64.tar.bz2"
-            },
-            {
-              "archiveFileName": "dfu-util-0.8.0-stm32-arduino1-linux_386.tar.bz2",
-              "checksum": "SHA-256:58131e35ad5d7053b281bc6176face7b117c5ad63331e43c6801f8ccd57f59a4",
-              "host": "i686-linux-gnu",
-              "size": "69097",
-              "url": "http://downloads.arduino.cc/arduino.org/dfu-util-0.8.0-stm32-arduino1-linux_386.tar.bz2"
-            },
-            {
-              "archiveFileName": "dfu-util-0.8.0-stm32-arduino1-windows_386.tar.bz2",
-              "checksum": "SHA-256:25c2f84e1acf1f10fd2aa1afced441366d4545fd41eae56e64f0b990b4ce9f55",
-              "host": "i686-mingw32",
-              "size": "159753",
-              "url": "http://downloads.arduino.cc/arduino.org/dfu-util-0.8.0-stm32-arduino1-windows_386.tar.bz2"
-            }
-          ]
-        },
-        {
-          "name": "arduinoSTM32load",
-          "version": "2.0.0",
-          "systems": [
-            {
-              "archiveFileName": "arduinoSTM32load-2.0.0-darwin_amd64.tar.bz2",
-              "checksum": "SHA-256:92fb9714091850febaa9d159501cbca5ba68d03020e5e2d4eff596154040bfaa",
-              "host": "i386-apple-darwin11",
-              "size": "807514",
-              "url": "http://downloads.arduino.cc/arduino.org/arduinoSTM32load-2.0.0-darwin_amd64.tar.bz2"
-            },
-            {
-              "archiveFileName": "arduinoSTM32load-2.0.0-linux_arm.tar.bz2",
-              "checksum": "SHA-256:fc0d8058b57bda849e1ffc849f83f54b0b85f97954176db317da1c745c174e08",
-              "host": "arm-linux-gnueabihf",
-              "size": "809480",
-              "url": "http://downloads.arduino.cc/arduino.org/arduinoSTM32load-2.0.0-linux_arm.tar.bz2"
-            },
-            {
-              "archiveFileName": "arduinoSTM32load-2.0.0-linux_amd64.tar.bz2",
-              "checksum": "SHA-256:0ed5cf1ea05fe6c33567817c54daf9c296d058a3607c428e0b0bd9aad89b9809",
-              "host": "x86_64-linux-gnu",
-              "size": "818885",
-              "url": "http://downloads.arduino.cc/arduino.org/arduinoSTM32load-2.0.0-linux_amd64.tar.bz2"
-            },
-            {
-              "archiveFileName": "arduinoSTM32load-2.0.0-linux_386.tar.bz2",
-              "checksum": "SHA-256:fad50abaaca034e6d647d09b042291b761982aabfd42b6156411c86e4f873ca7",
-              "host": "i686-linux-gnu",
-              "size": "814283",
-              "url": "http://downloads.arduino.cc/arduino.org/arduinoSTM32load-2.0.0-linux_386.tar.bz2"
-            },
-            {
-              "archiveFileName": "arduinoSTM32load-2.0.0-windows_386.tar.bz2",
-              "checksum": "SHA-256:79467c0cde4b88c4884acb09445a2186af4e41f901eee56e99b5d89b7065d085",
-              "host": "i686-mingw32",
-              "size": "786335",
-              "url": "http://downloads.arduino.cc/arduino.org/arduinoSTM32load-2.0.0-windows_386.tar.bz2"
-            }
-          ]
-        },
-        {
-          "name": "openocd",
-          "version": "0.10.0-arduino1-static",
-          "systems": [
-            {
-              "host": "i386-apple-darwin11",
-              "url": "http://downloads.arduino.cc/arduino.org/OpenOCD-0.10.0-nrf52-osx-static.tar.gz",
-              "archiveFileName": "OpenOCD-0.10.0-nrf52-osx-static.tar.gz",
-              "size": "1529841",
-              "checksum": "SHA-256:46bd02c1d42c5d94c4936e4d4a0ff29697b621840be9a6f882e316203122049d"
-            },
-            {
-              "host": "x86_64-linux-gnu",
-              "url": "http://downloads.arduino.cc/arduino.org/OpenOCD-0.10.0-nrf52-linux64-static.tar.gz",
-              "archiveFileName": "OpenOCD-0.10.0-nrf52-linux64-static.tar.gz",
-              "size": "1777984",
-              "checksum": "SHA-256:1c9ae77930dd7377d8c13f84abe7307b67fdcd6da74cc1ce269a79e138e7a00a"
-            },
-            {
-              "host": "i686-linux-gnu",
-              "url": "http://downloads.arduino.cc/arduino.org/OpenOCD-0.10.0-nrf52-linux32-static.tar.gz",
-              "archiveFileName": "OpenOCD-0.10.0-nrf52-linux32-static.tar.gz",
-              "size": "1713236",
-              "checksum": "SHA-256:777371df34828810e1bea623b0f7c98f28fedf30fd3bc8e7d8f0a5745fb4e258"
-            },
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/arduino.org/OpenOCD-0.10.0-nrf52-win32-static.zip",
-              "archiveFileName": "OpenOCD-0.10.0-nrf52-win32-static.zip",
-              "size": "1773642",
-              "checksum": "SHA-256:9371b25d000bd589c058a5bf10720617adb91fd8b8a21d2e887cf45eaa2df93c"
-            },
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/arduino.org/OpenOCD-0.10.0-nrf52-arm-static.tar.gz",
-              "archiveFileName": "OpenOCD-0.10.0-nrf52-arm-static.tar.gz",
-              "size": "1526863",
-              "checksum": "SHA-256:b5172422077f87ff05b76ff40034979678c9c640e9d08cee15ce55e40dd8c929"
-            }
-          ]
-        },
-        {
-          "name": "fwupdater",
-          "version": "0.0.6",
-          "systems": [
-            {
-              "host": "i686-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/FirmwareUpdater-0.0.6-linux32.tar.bz2",
-              "archiveFileName": "FirmwareUpdater-0.0.6-linux32.tar.bz2",
-              "checksum": "SHA-256:8c4e562a8e6fa3d916c4bf6bb24d7eec0df013d8cc45dff187e5c63086a92c11",
-              "size": "7334059"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/FirmwareUpdater-0.0.6-linux64.tar.bz2",
-              "archiveFileName": "FirmwareUpdater-0.0.6-linux64.tar.bz2",
-              "checksum": "SHA-256:0e9132518acfe66e5a4e17ba4b6dd0c89dbd90cc0d9b4a54e007ac24d51fd36a",
-              "size": "7383853"
-            },
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/tools/FirmwareUpdater-0.0.6-windows.zip",
-              "archiveFileName": "FirmwareUpdater-0.0.6-windows.zip",
-              "checksum": "SHA-256:33a92661f43b8d025ca5f57be1116537ed153703067d9c86297619d58988feff",
-              "size": "7580663"
-            },
-            {
-              "host": "i386-apple-darwin11",
-              "url": "http://downloads.arduino.cc/tools/FirmwareUpdater-0.0.6-osx.tar.bz2",
-              "archiveFileName": "FirmwareUpdater-0.0.6-osx.tar.bz2",
-              "checksum": "SHA-256:3584d7581ff2469bdfde9de6f57d87b2a0370de5c902e9df687b7322a5405018",
-              "size": "7368819"
-            },
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/tools/FirmwareUpdater-0.0.6-linuxarm.tar.bz2",
-              "archiveFileName": "FirmwareUpdater-0.0.6-linuxarm.tar.bz2",
-              "checksum": "SHA-256:276f027e552eb620064b09591c7a7c79927c93c017428436c81f71bad666803c",
-              "size": "7180391"
-            },
-            {
-              "host": "aarch64-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/FirmwareUpdater-0.0.6-linuxarm64.tar.bz2",
-              "archiveFileName": "FirmwareUpdater-0.0.6-linuxarm64.tar.bz2",
-              "checksum": "SHA-256:d6a2e72b4869c031b434380da6e6a8a4a6269f8ee6312a3d23e85ea133a37ae9",
-              "size": "7149332"
-            }
-          ]
-        },
-        {
-          "name": "fwupdater",
-          "version": "0.0.7",
-          "systems": [
-            {
-              "host": "i686-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/FirmwareUpdater-0.0.7-linux32.tar.bz2",
-              "archiveFileName": "FirmwareUpdater-0.0.7-linux32.tar.bz2",
-              "checksum": "SHA-256:05e26d5df253246cf68ba8a8f5f2de1032ac6f4d7af5310b8080ef6f54030fb4",
-              "size": "13077590"
-            },
-            {
-              "host": "x86_64-pc-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/FirmwareUpdater-0.0.7-linux64.tar.bz2",
-              "archiveFileName": "FirmwareUpdater-0.0.7-linux64.tar.bz2",
-              "checksum": "SHA-256:e8a36b7ae19060748b8b0540c9c0c9341d8d4c7a630441922a030223f767258d",
-              "size": "13134534"
-            },
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/tools/FirmwareUpdater-0.0.7-windows.zip",
-              "archiveFileName": "FirmwareUpdater-0.0.7-windows.zip",
-              "checksum": "SHA-256:e881dd8a4bbb35f7271157e8dd226c2e032ac1115106170b0aaf9c969817df86",
-              "size": "13337495"
-            },
-            {
-              "host": "i386-apple-darwin11",
-              "url": "http://downloads.arduino.cc/tools/FirmwareUpdater-0.0.7-osx.tar.bz2",
-              "archiveFileName": "FirmwareUpdater-0.0.7-osx.tar.bz2",
-              "checksum": "SHA-256:647ad713d68a1531ba56c88e5b3db9516cc0072ca1d401de5142d85ffcfd931a",
-              "size": "13064291"
-            },
-            {
-              "host": "arm-linux-gnueabihf",
-              "url": "http://downloads.arduino.cc/tools/FirmwareUpdater-0.0.7-linuxarm.tar.bz2",
-              "archiveFileName": "FirmwareUpdater-0.0.7-linuxarm.tar.bz2",
-              "checksum": "SHA-256:b6e09c07e816a35935585db07e07437023d44e66d1d2f05861708ea6ceff7629",
-              "size": "12929770"
-            },
-            {
-              "host": "aarch64-linux-gnu",
-              "url": "http://downloads.arduino.cc/tools/FirmwareUpdater-0.0.7-linuxarm64.tar.bz2",
-              "archiveFileName": "FirmwareUpdater-0.0.7-linuxarm64.tar.bz2",
-              "checksum": "SHA-256:620f81148b13f70cdecbe59bbcbf605c7e0aae85f9cbee8ec48202f6018f23ce",
-              "size": "12897349"
-            }
-          ]
-        },
-        {
-          "name": "nrf5x-cl-tools",
-          "version": "9.3.1",
-          "systems": [
-            {
-              "host": "i386-apple-darwin11",
-              "url": "http://downloads.arduino.cc/arduino.org/nRF5x-Command-Line-Tools_9_3_1_OSX.tar.bz2",
-              "archiveFileName": "nRF5x-Command-Line-Tools_9_3_1_OSX.tar.bz2",
-              "size": "341674",
-              "checksum": "SHA-256:41e4580271b39459a7ef1b078d11ee08d8f4f23fab7ff03f3fe8c3bc986a0ed4"
-            },
-            {
-              "host": "x86_64-linux-gnu",
-              "url": "http://downloads.arduino.cc/arduino.org/nRF5x-Command-Line-Tools_9_3_1_Linux-x86_64.tar.bz2",
-              "archiveFileName": "nRF5x-Command-Line-Tools_9_3_1_Linux-x86_64.tar.bz2",
-              "size": "167414",
-              "checksum": "SHA-256:4074fffe678d60968006a72edd182c6506b264472c9957bc3eaa39336bfcf972"
-            },
-            {
-              "host": "i686-linux-gnu",
-              "url": "http://downloads.arduino.cc/arduino.org/nRF5x-Command-Line-Tools_9_3_1_Linux-i386.tar.bz2",
-              "archiveFileName": "nRF5x-Command-Line-Tools_9_3_1_Linux-i386.tar.bz2",
-              "size": "155680",
-              "checksum": "SHA-256:e880059b303e5aad3a8b34c83dfd8c22beee77ae2074fbd37511e3baa91464a5"
-            },
-            {
-              "host": "i686-mingw32",
-              "url": "http://downloads.arduino.cc/arduino.org/nRF5x-Command-Line-Tools_9_3_1_Win32.tar.bz2",
-              "archiveFileName": "nRF5x-Command-Line-Tools_9_3_1_Win32.tar.bz2",
-              "size": "812257",
-              "checksum": "SHA-256:a4467350e39314690cec2e96b80e7e3cab463c84eff9b81593ad57754d76ee00"
-            }
-          ]
-        }
-      ]
-    }
-  ]
-}
diff --git a/commands/cmderrors/cmderrors.go b/commands/cmderrors/cmderrors.go
index e6794ffdee9..a5024a37a26 100644
--- a/commands/cmderrors/cmderrors.go
+++ b/commands/cmderrors/cmderrors.go
@@ -37,8 +37,8 @@ func composeErrorMsg(msg string, cause error) string {
 
 // CommandError is an error that may be converted into a gRPC status.
 type CommandError interface {
-	// ToRPCStatus convertes the error into a *status.Status
-	ToRPCStatus() *status.Status
+	// GRPCStatus convertes the error into a *status.Status
+	GRPCStatus() *status.Status
 }
 
 // InvalidInstanceError is returned if the instance used in the command is not valid.
@@ -48,8 +48,8 @@ func (e *InvalidInstanceError) Error() string {
 	return tr("Invalid instance")
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *InvalidInstanceError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *InvalidInstanceError) GRPCStatus() *status.Status {
 	return status.New(codes.InvalidArgument, e.Error())
 }
 
@@ -62,8 +62,8 @@ func (e *InvalidFQBNError) Error() string {
 	return composeErrorMsg(tr("Invalid FQBN"), e.Cause)
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *InvalidFQBNError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *InvalidFQBNError) GRPCStatus() *status.Status {
 	return status.New(codes.InvalidArgument, e.Error())
 }
 
@@ -80,8 +80,8 @@ func (e *InvalidURLError) Error() string {
 	return composeErrorMsg(tr("Invalid URL"), e.Cause)
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *InvalidURLError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *InvalidURLError) GRPCStatus() *status.Status {
 	return status.New(codes.InvalidArgument, e.Error())
 }
 
@@ -98,8 +98,8 @@ func (e *InvalidLibraryError) Error() string {
 	return composeErrorMsg(tr("Invalid library"), e.Cause)
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *InvalidLibraryError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *InvalidLibraryError) GRPCStatus() *status.Status {
 	return status.New(codes.InvalidArgument, e.Error())
 }
 
@@ -116,8 +116,8 @@ func (e *InvalidVersionError) Error() string {
 	return composeErrorMsg(tr("Invalid version"), e.Cause)
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *InvalidVersionError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *InvalidVersionError) GRPCStatus() *status.Status {
 	return status.New(codes.InvalidArgument, e.Error())
 }
 
@@ -139,8 +139,8 @@ func (e *NoBoardsDetectedError) Error() string {
 	)
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *NoBoardsDetectedError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *NoBoardsDetectedError) GRPCStatus() *status.Status {
 	return status.New(codes.InvalidArgument, e.Error())
 }
 
@@ -159,8 +159,8 @@ func (e *MultipleBoardsDetectedError) Error() string {
 	)
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *MultipleBoardsDetectedError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *MultipleBoardsDetectedError) GRPCStatus() *status.Status {
 	return status.New(codes.InvalidArgument, e.Error())
 }
 
@@ -171,8 +171,8 @@ func (e *MissingFQBNError) Error() string {
 	return tr("Missing FQBN (Fully Qualified Board Name)")
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *MissingFQBNError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *MissingFQBNError) GRPCStatus() *status.Status {
 	return status.New(codes.InvalidArgument, e.Error())
 }
 
@@ -189,8 +189,8 @@ func (e *UnknownFQBNError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *UnknownFQBNError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *UnknownFQBNError) GRPCStatus() *status.Status {
 	return status.New(codes.NotFound, e.Error())
 }
 
@@ -208,8 +208,8 @@ func (e *UnknownProfileError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *UnknownProfileError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *UnknownProfileError) GRPCStatus() *status.Status {
 	return status.New(codes.NotFound, e.Error())
 }
 
@@ -226,8 +226,8 @@ func (e *InvalidProfileError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *InvalidProfileError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *InvalidProfileError) GRPCStatus() *status.Status {
 	return status.New(codes.FailedPrecondition, e.Error())
 }
 
@@ -238,8 +238,8 @@ func (e *MissingPortAddressError) Error() string {
 	return tr("Missing port address")
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *MissingPortAddressError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *MissingPortAddressError) GRPCStatus() *status.Status {
 	return status.New(codes.InvalidArgument, e.Error())
 }
 
@@ -250,8 +250,8 @@ func (e *MissingPortProtocolError) Error() string {
 	return tr("Missing port protocol")
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *MissingPortProtocolError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *MissingPortProtocolError) GRPCStatus() *status.Status {
 	return status.New(codes.InvalidArgument, e.Error())
 }
 
@@ -262,8 +262,8 @@ func (e *MissingPortError) Error() string {
 	return tr("Missing port")
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *MissingPortError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *MissingPortError) GRPCStatus() *status.Status {
 	return status.New(codes.InvalidArgument, e.Error())
 }
 
@@ -276,8 +276,8 @@ func (e *NoMonitorAvailableForProtocolError) Error() string {
 	return tr("No monitor available for the port protocol %s", e.Protocol)
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *NoMonitorAvailableForProtocolError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *NoMonitorAvailableForProtocolError) GRPCStatus() *status.Status {
 	return status.New(codes.InvalidArgument, e.Error())
 }
 
@@ -288,8 +288,8 @@ func (e *MissingProgrammerError) Error() string {
 	return tr("Missing programmer")
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *MissingProgrammerError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *MissingProgrammerError) GRPCStatus() *status.Status {
 	s, _ := status.New(codes.InvalidArgument, e.Error()).WithDetails(&rpc.MissingProgrammerError{})
 	return s
 }
@@ -301,8 +301,8 @@ func (e *ProgrammerRequiredForUploadError) Error() string {
 	return tr("A programmer is required to upload")
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *ProgrammerRequiredForUploadError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *ProgrammerRequiredForUploadError) GRPCStatus() *status.Status {
 	st, _ := status.
 		New(codes.InvalidArgument, e.Error()).
 		WithDetails(&rpc.ProgrammerIsRequiredForUploadError{})
@@ -320,8 +320,8 @@ func (ife *InitFailedError) Error() string {
 	return ife.Cause.Error()
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (ife *InitFailedError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (ife *InitFailedError) GRPCStatus() *status.Status {
 	st, _ := status.
 		New(ife.Code, ife.Cause.Error()).
 		WithDetails(&rpc.FailedInstanceInitError{
@@ -345,8 +345,8 @@ func (e *ProgrammerNotFoundError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *ProgrammerNotFoundError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *ProgrammerNotFoundError) GRPCStatus() *status.Status {
 	return status.New(codes.NotFound, e.Error())
 }
 
@@ -364,8 +364,8 @@ func (e *MonitorNotFoundError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *MonitorNotFoundError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *MonitorNotFoundError) GRPCStatus() *status.Status {
 	return status.New(codes.NotFound, e.Error())
 }
 
@@ -379,8 +379,8 @@ func (e *InvalidPlatformPropertyError) Error() string {
 	return tr("Invalid '%[1]s' property: %[2]s", e.Property, e.Value)
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *InvalidPlatformPropertyError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *InvalidPlatformPropertyError) GRPCStatus() *status.Status {
 	return status.New(codes.FailedPrecondition, e.Error())
 }
 
@@ -393,8 +393,8 @@ func (e *MissingPlatformPropertyError) Error() string {
 	return tr("Property '%s' is undefined", e.Property)
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *MissingPlatformPropertyError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *MissingPlatformPropertyError) GRPCStatus() *status.Status {
 	return status.New(codes.FailedPrecondition, e.Error())
 }
 
@@ -408,8 +408,8 @@ func (e *PlatformNotFoundError) Error() string {
 	return composeErrorMsg(tr("Platform '%s' not found", e.Platform), e.Cause)
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *PlatformNotFoundError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *PlatformNotFoundError) GRPCStatus() *status.Status {
 	return status.New(codes.FailedPrecondition, e.Error())
 }
 
@@ -426,8 +426,8 @@ func (e *PlatformLoadingError) Error() string {
 	return composeErrorMsg(tr("Error loading hardware platform"), e.Cause)
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *PlatformLoadingError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *PlatformLoadingError) GRPCStatus() *status.Status {
 	s, _ := status.New(codes.FailedPrecondition, e.Error()).
 		WithDetails(&rpc.PlatformLoadingError{})
 	return s
@@ -447,8 +447,8 @@ func (e *LibraryNotFoundError) Error() string {
 	return composeErrorMsg(tr("Library '%s' not found", e.Library), e.Cause)
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *LibraryNotFoundError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *LibraryNotFoundError) GRPCStatus() *status.Status {
 	return status.New(codes.FailedPrecondition, e.Error())
 }
 
@@ -466,8 +466,8 @@ func (e *LibraryDependenciesResolutionFailedError) Error() string {
 	return composeErrorMsg(tr("No valid dependencies solution found"), e.Cause)
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *LibraryDependenciesResolutionFailedError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *LibraryDependenciesResolutionFailedError) GRPCStatus() *status.Status {
 	return status.New(codes.FailedPrecondition, e.Error())
 }
 
@@ -484,8 +484,8 @@ func (e *PlatformAlreadyAtTheLatestVersionError) Error() string {
 	return tr("Platform '%s' is already at the latest version", e.Platform)
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *PlatformAlreadyAtTheLatestVersionError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *PlatformAlreadyAtTheLatestVersionError) GRPCStatus() *status.Status {
 	st, _ := status.
 		New(codes.AlreadyExists, e.Error()).
 		WithDetails(&rpc.AlreadyAtLatestVersionError{})
@@ -499,8 +499,8 @@ func (e *MissingSketchPathError) Error() string {
 	return tr("Missing sketch path")
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *MissingSketchPathError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *MissingSketchPathError) GRPCStatus() *status.Status {
 	return status.New(codes.InvalidArgument, e.Error())
 }
 
@@ -543,8 +543,8 @@ func (e *CantOpenSketchError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *CantOpenSketchError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *CantOpenSketchError) GRPCStatus() *status.Status {
 	return status.New(codes.NotFound, e.Error())
 }
 
@@ -562,8 +562,8 @@ func (e *FailedInstallError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *FailedInstallError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *FailedInstallError) GRPCStatus() *status.Status {
 	return status.New(codes.Internal, e.Error())
 }
 
@@ -580,8 +580,8 @@ func (e *FailedLibraryInstallError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *FailedLibraryInstallError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *FailedLibraryInstallError) GRPCStatus() *status.Status {
 	return status.New(codes.Internal, e.Error())
 }
 
@@ -599,8 +599,8 @@ func (e *FailedUninstallError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *FailedUninstallError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *FailedUninstallError) GRPCStatus() *status.Status {
 	return status.New(codes.Internal, e.Error())
 }
 
@@ -618,8 +618,8 @@ func (e *FailedDownloadError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *FailedDownloadError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *FailedDownloadError) GRPCStatus() *status.Status {
 	return status.New(codes.Internal, e.Error())
 }
 
@@ -637,8 +637,8 @@ func (e *FailedUploadError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *FailedUploadError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *FailedUploadError) GRPCStatus() *status.Status {
 	return status.New(codes.Internal, e.Error())
 }
 
@@ -656,8 +656,8 @@ func (e *FailedDebugError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *FailedDebugError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *FailedDebugError) GRPCStatus() *status.Status {
 	return status.New(codes.Internal, e.Error())
 }
 
@@ -674,8 +674,8 @@ func (e *FailedMonitorError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *FailedMonitorError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *FailedMonitorError) GRPCStatus() *status.Status {
 	return status.New(codes.Internal, e.Error())
 }
 
@@ -693,8 +693,8 @@ func (e *CompileFailedError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *CompileFailedError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *CompileFailedError) GRPCStatus() *status.Status {
 	return status.New(codes.Internal, e.Error())
 }
 
@@ -712,8 +712,8 @@ func (e *InvalidArgumentError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *InvalidArgumentError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *InvalidArgumentError) GRPCStatus() *status.Status {
 	return status.New(codes.InvalidArgument, e.Error())
 }
 
@@ -731,8 +731,8 @@ func (e *NotFoundError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *NotFoundError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *NotFoundError) GRPCStatus() *status.Status {
 	return status.New(codes.NotFound, e.Error())
 }
 
@@ -750,8 +750,8 @@ func (e *PermissionDeniedError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *PermissionDeniedError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *PermissionDeniedError) GRPCStatus() *status.Status {
 	return status.New(codes.PermissionDenied, e.Error())
 }
 
@@ -769,8 +769,8 @@ func (e *UnavailableError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *UnavailableError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *UnavailableError) GRPCStatus() *status.Status {
 	return status.New(codes.Unavailable, e.Error())
 }
 
@@ -787,8 +787,8 @@ func (e *TempDirCreationFailedError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *TempDirCreationFailedError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *TempDirCreationFailedError) GRPCStatus() *status.Status {
 	return status.New(codes.Unavailable, e.Error())
 }
 
@@ -805,8 +805,8 @@ func (e *TempFileCreationFailedError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *TempFileCreationFailedError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *TempFileCreationFailedError) GRPCStatus() *status.Status {
 	return status.New(codes.Unavailable, e.Error())
 }
 
@@ -824,8 +824,8 @@ func (e *SignatureVerificationFailedError) Unwrap() error {
 	return e.Cause
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *SignatureVerificationFailedError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *SignatureVerificationFailedError) GRPCStatus() *status.Status {
 	return status.New(codes.Unavailable, e.Error())
 }
 
@@ -842,8 +842,8 @@ func (e *MultiplePlatformsError) Error() string {
 		len(e.Platforms), e.UserPlatform, strings.Join(e.Platforms, ", "))
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *MultiplePlatformsError) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *MultiplePlatformsError) GRPCStatus() *status.Status {
 	return status.New(codes.InvalidArgument, e.Error())
 }
 
@@ -865,8 +865,8 @@ func (e *MultipleLibraryInstallDetected) Error() string {
 	return res
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *MultipleLibraryInstallDetected) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *MultipleLibraryInstallDetected) GRPCStatus() *status.Status {
 	return status.New(codes.InvalidArgument, e.Error())
 }
 
@@ -878,8 +878,8 @@ func (e *InstanceNeedsReinitialization) Error() string {
 	return tr("The instance is no longer valid and needs to be reinitialized")
 }
 
-// ToRPCStatus converts the error into a *status.Status
-func (e *InstanceNeedsReinitialization) ToRPCStatus() *status.Status {
+// GRPCStatus converts the error into a *status.Status
+func (e *InstanceNeedsReinitialization) GRPCStatus() *status.Status {
 	st, _ := status.
 		New(codes.InvalidArgument, e.Error()).
 		WithDetails(&rpc.InstanceNeedsReinitializationError{})
diff --git a/commands/daemon/daemon.go b/commands/daemon/daemon.go
deleted file mode 100644
index 97a2783e0e6..00000000000
--- a/commands/daemon/daemon.go
+++ /dev/null
@@ -1,597 +0,0 @@
-// This file is part of arduino-cli.
-//
-// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
-//
-// This software is released under the GNU General Public License version 3,
-// which covers the main part of arduino-cli.
-// The terms of this license can be found at:
-// https://www.gnu.org/licenses/gpl-3.0.en.html
-//
-// You can be released from the requirements of the above licenses by purchasing
-// a commercial license. Buying such a license is mandatory if you want to
-// modify or otherwise use the software for commercial activities involving the
-// Arduino software without disclosing the source code of your own applications.
-// To purchase a commercial license, send an email to license@arduino.cc.
-
-package daemon
-
-import (
-	"context"
-	"errors"
-	"fmt"
-	"io"
-	"sync/atomic"
-
-	"github.com/arduino/arduino-cli/commands"
-	"github.com/arduino/arduino-cli/commands/board"
-	"github.com/arduino/arduino-cli/commands/cache"
-	"github.com/arduino/arduino-cli/commands/cmderrors"
-	"github.com/arduino/arduino-cli/commands/compile"
-	"github.com/arduino/arduino-cli/commands/core"
-	"github.com/arduino/arduino-cli/commands/lib"
-	"github.com/arduino/arduino-cli/commands/monitor"
-	"github.com/arduino/arduino-cli/commands/sketch"
-	"github.com/arduino/arduino-cli/commands/updatecheck"
-	"github.com/arduino/arduino-cli/commands/upload"
-	"github.com/arduino/arduino-cli/internal/i18n"
-	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
-	"github.com/sirupsen/logrus"
-	"google.golang.org/grpc/metadata"
-)
-
-// ArduinoCoreServerImpl FIXMEDOC
-type ArduinoCoreServerImpl struct {
-	// Force compile error for unimplemented methods
-	rpc.UnsafeArduinoCoreServiceServer
-
-	VersionString string
-}
-
-var tr = i18n.Tr
-
-func convertErrorToRPCStatus(err error) error {
-	if err == nil {
-		return nil
-	}
-	if cmdErr, ok := err.(cmderrors.CommandError); ok {
-		return cmdErr.ToRPCStatus().Err()
-	}
-	return err
-}
-
-// BoardDetails FIXMEDOC
-func (s *ArduinoCoreServerImpl) BoardDetails(ctx context.Context, req *rpc.BoardDetailsRequest) (*rpc.BoardDetailsResponse, error) {
-	resp, err := board.Details(ctx, req)
-	return resp, convertErrorToRPCStatus(err)
-}
-
-// BoardList FIXMEDOC
-func (s *ArduinoCoreServerImpl) BoardList(ctx context.Context, req *rpc.BoardListRequest) (*rpc.BoardListResponse, error) {
-	ports, _, err := board.List(req)
-	if err != nil {
-		return nil, convertErrorToRPCStatus(err)
-	}
-	return &rpc.BoardListResponse{
-		Ports: ports,
-	}, nil
-}
-
-// BoardListAll FIXMEDOC
-func (s *ArduinoCoreServerImpl) BoardListAll(ctx context.Context, req *rpc.BoardListAllRequest) (*rpc.BoardListAllResponse, error) {
-	resp, err := board.ListAll(ctx, req)
-	return resp, convertErrorToRPCStatus(err)
-}
-
-// BoardSearch exposes to the gRPC interface the board search command
-func (s *ArduinoCoreServerImpl) BoardSearch(ctx context.Context, req *rpc.BoardSearchRequest) (*rpc.BoardSearchResponse, error) {
-	resp, err := board.Search(ctx, req)
-	return resp, convertErrorToRPCStatus(err)
-}
-
-// BoardListWatch FIXMEDOC
-func (s *ArduinoCoreServerImpl) BoardListWatch(req *rpc.BoardListWatchRequest, stream rpc.ArduinoCoreService_BoardListWatchServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-	if req.GetInstance() == nil {
-		err := fmt.Errorf(tr("no instance specified"))
-		syncSend.Send(&rpc.BoardListWatchResponse{
-			EventType: "error",
-			Error:     err.Error(),
-		})
-		return err
-	}
-
-	eventsChan, err := board.Watch(stream.Context(), req)
-	if err != nil {
-		return convertErrorToRPCStatus(err)
-	}
-
-	for event := range eventsChan {
-		if err := syncSend.Send(event); err != nil {
-			logrus.Infof("sending board watch message: %v", err)
-		}
-	}
-
-	return nil
-}
-
-// Destroy FIXMEDOC
-func (s *ArduinoCoreServerImpl) Destroy(ctx context.Context, req *rpc.DestroyRequest) (*rpc.DestroyResponse, error) {
-	resp, err := commands.Destroy(ctx, req)
-	return resp, convertErrorToRPCStatus(err)
-}
-
-// UpdateIndex FIXMEDOC
-func (s *ArduinoCoreServerImpl) UpdateIndex(req *rpc.UpdateIndexRequest, stream rpc.ArduinoCoreService_UpdateIndexServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-	res, err := commands.UpdateIndex(stream.Context(), req,
-		func(p *rpc.DownloadProgress) {
-			syncSend.Send(&rpc.UpdateIndexResponse{
-				Message: &rpc.UpdateIndexResponse_DownloadProgress{DownloadProgress: p},
-			})
-		},
-	)
-	if res != nil {
-		syncSend.Send(&rpc.UpdateIndexResponse{
-			Message: &rpc.UpdateIndexResponse_Result_{Result: res},
-		})
-	}
-	return convertErrorToRPCStatus(err)
-}
-
-// UpdateLibrariesIndex FIXMEDOC
-func (s *ArduinoCoreServerImpl) UpdateLibrariesIndex(req *rpc.UpdateLibrariesIndexRequest, stream rpc.ArduinoCoreService_UpdateLibrariesIndexServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-	res, err := commands.UpdateLibrariesIndex(stream.Context(), req,
-		func(p *rpc.DownloadProgress) {
-			syncSend.Send(&rpc.UpdateLibrariesIndexResponse{
-				Message: &rpc.UpdateLibrariesIndexResponse_DownloadProgress{DownloadProgress: p},
-			})
-		},
-	)
-	if res != nil {
-		syncSend.Send(&rpc.UpdateLibrariesIndexResponse{
-			Message: &rpc.UpdateLibrariesIndexResponse_Result_{Result: res},
-		})
-	}
-	return convertErrorToRPCStatus(err)
-}
-
-// Create FIXMEDOC
-func (s *ArduinoCoreServerImpl) Create(ctx context.Context, req *rpc.CreateRequest) (*rpc.CreateResponse, error) {
-	var userAgent []string
-	if md, ok := metadata.FromIncomingContext(ctx); ok {
-		userAgent = md.Get("user-agent")
-	}
-	if len(userAgent) == 0 {
-		userAgent = []string{"gRPCClientUnknown/0.0.0"}
-	}
-	res, err := commands.Create(req, userAgent...)
-	return res, convertErrorToRPCStatus(err)
-}
-
-// Init FIXMEDOC
-func (s *ArduinoCoreServerImpl) Init(req *rpc.InitRequest, stream rpc.ArduinoCoreService_InitServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-	err := commands.Init(req, func(message *rpc.InitResponse) { syncSend.Send(message) })
-	return convertErrorToRPCStatus(err)
-}
-
-// Version FIXMEDOC
-func (s *ArduinoCoreServerImpl) Version(ctx context.Context, req *rpc.VersionRequest) (*rpc.VersionResponse, error) {
-	return &rpc.VersionResponse{Version: s.VersionString}, nil
-}
-
-// NewSketch FIXMEDOC
-func (s *ArduinoCoreServerImpl) NewSketch(ctx context.Context, req *rpc.NewSketchRequest) (*rpc.NewSketchResponse, error) {
-	resp, err := sketch.NewSketch(ctx, req)
-	return resp, convertErrorToRPCStatus(err)
-}
-
-// LoadSketch FIXMEDOC
-func (s *ArduinoCoreServerImpl) LoadSketch(ctx context.Context, req *rpc.LoadSketchRequest) (*rpc.LoadSketchResponse, error) {
-	resp, err := sketch.LoadSketch(ctx, req)
-	return &rpc.LoadSketchResponse{Sketch: resp}, convertErrorToRPCStatus(err)
-}
-
-// SetSketchDefaults FIXMEDOC
-func (s *ArduinoCoreServerImpl) SetSketchDefaults(ctx context.Context, req *rpc.SetSketchDefaultsRequest) (*rpc.SetSketchDefaultsResponse, error) {
-	resp, err := sketch.SetSketchDefaults(ctx, req)
-	return resp, convertErrorToRPCStatus(err)
-}
-
-// Compile FIXMEDOC
-func (s *ArduinoCoreServerImpl) Compile(req *rpc.CompileRequest, stream rpc.ArduinoCoreService_CompileServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-	outStream := feedStreamTo(func(data []byte) {
-		syncSend.Send(&rpc.CompileResponse{
-			Message: &rpc.CompileResponse_OutStream{OutStream: data},
-		})
-	})
-	errStream := feedStreamTo(func(data []byte) {
-		syncSend.Send(&rpc.CompileResponse{
-			Message: &rpc.CompileResponse_ErrStream{ErrStream: data},
-		})
-	})
-	progressStream := func(p *rpc.TaskProgress) {
-		syncSend.Send(&rpc.CompileResponse{
-			Message: &rpc.CompileResponse_Progress{Progress: p},
-		})
-	}
-	compileRes, compileErr := compile.Compile(stream.Context(), req, outStream, errStream, progressStream)
-	outStream.Close()
-	errStream.Close()
-	var compileRespSendErr error
-	if compileRes != nil {
-		compileRespSendErr = syncSend.Send(&rpc.CompileResponse{
-			Message: &rpc.CompileResponse_Result{
-				Result: compileRes,
-			},
-		})
-	}
-	if compileErr != nil {
-		return convertErrorToRPCStatus(compileErr)
-	}
-	return compileRespSendErr
-}
-
-// PlatformInstall FIXMEDOC
-func (s *ArduinoCoreServerImpl) PlatformInstall(req *rpc.PlatformInstallRequest, stream rpc.ArduinoCoreService_PlatformInstallServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-	resp, err := core.PlatformInstall(
-		stream.Context(), req,
-		func(p *rpc.DownloadProgress) { syncSend.Send(&rpc.PlatformInstallResponse{Progress: p}) },
-		func(p *rpc.TaskProgress) { syncSend.Send(&rpc.PlatformInstallResponse{TaskProgress: p}) },
-	)
-	if err != nil {
-		return convertErrorToRPCStatus(err)
-	}
-	return syncSend.Send(resp)
-}
-
-// PlatformDownload FIXMEDOC
-func (s *ArduinoCoreServerImpl) PlatformDownload(req *rpc.PlatformDownloadRequest, stream rpc.ArduinoCoreService_PlatformDownloadServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-	resp, err := core.PlatformDownload(
-		stream.Context(), req,
-		func(p *rpc.DownloadProgress) { syncSend.Send(&rpc.PlatformDownloadResponse{Progress: p}) },
-	)
-	if err != nil {
-		return convertErrorToRPCStatus(err)
-	}
-	return syncSend.Send(resp)
-}
-
-// PlatformUninstall FIXMEDOC
-func (s *ArduinoCoreServerImpl) PlatformUninstall(req *rpc.PlatformUninstallRequest, stream rpc.ArduinoCoreService_PlatformUninstallServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-	resp, err := core.PlatformUninstall(
-		stream.Context(), req,
-		func(p *rpc.TaskProgress) { syncSend.Send(&rpc.PlatformUninstallResponse{TaskProgress: p}) },
-	)
-	if err != nil {
-		return convertErrorToRPCStatus(err)
-	}
-	return syncSend.Send(resp)
-}
-
-// PlatformUpgrade FIXMEDOC
-func (s *ArduinoCoreServerImpl) PlatformUpgrade(req *rpc.PlatformUpgradeRequest, stream rpc.ArduinoCoreService_PlatformUpgradeServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-	resp, err := core.PlatformUpgrade(
-		stream.Context(), req,
-		func(p *rpc.DownloadProgress) { syncSend.Send(&rpc.PlatformUpgradeResponse{Progress: p}) },
-		func(p *rpc.TaskProgress) { syncSend.Send(&rpc.PlatformUpgradeResponse{TaskProgress: p}) },
-	)
-	if err2 := syncSend.Send(resp); err2 != nil {
-		return err2
-	}
-	return convertErrorToRPCStatus(err)
-}
-
-// PlatformSearch FIXMEDOC
-func (s *ArduinoCoreServerImpl) PlatformSearch(ctx context.Context, req *rpc.PlatformSearchRequest) (*rpc.PlatformSearchResponse, error) {
-	resp, err := core.PlatformSearch(req)
-	return resp, convertErrorToRPCStatus(err)
-}
-
-// Upload FIXMEDOC
-func (s *ArduinoCoreServerImpl) Upload(req *rpc.UploadRequest, stream rpc.ArduinoCoreService_UploadServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-	outStream := feedStreamTo(func(data []byte) {
-		syncSend.Send(&rpc.UploadResponse{
-			Message: &rpc.UploadResponse_OutStream{OutStream: data},
-		})
-	})
-	errStream := feedStreamTo(func(data []byte) {
-		syncSend.Send(&rpc.UploadResponse{
-			Message: &rpc.UploadResponse_ErrStream{ErrStream: data},
-		})
-	})
-	res, err := upload.Upload(stream.Context(), req, outStream, errStream)
-	outStream.Close()
-	errStream.Close()
-	if res != nil {
-		syncSend.Send(&rpc.UploadResponse{
-			Message: &rpc.UploadResponse_Result{
-				Result: res,
-			},
-		})
-	}
-	return convertErrorToRPCStatus(err)
-}
-
-// UploadUsingProgrammer FIXMEDOC
-func (s *ArduinoCoreServerImpl) UploadUsingProgrammer(req *rpc.UploadUsingProgrammerRequest, stream rpc.ArduinoCoreService_UploadUsingProgrammerServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-	outStream := feedStreamTo(func(data []byte) {
-		syncSend.Send(&rpc.UploadUsingProgrammerResponse{
-			Message: &rpc.UploadUsingProgrammerResponse_OutStream{
-				OutStream: data,
-			},
-		})
-	})
-	errStream := feedStreamTo(func(data []byte) {
-		syncSend.Send(&rpc.UploadUsingProgrammerResponse{
-			Message: &rpc.UploadUsingProgrammerResponse_ErrStream{
-				ErrStream: data,
-			},
-		})
-	})
-	err := upload.UsingProgrammer(stream.Context(), req, outStream, errStream)
-	outStream.Close()
-	errStream.Close()
-	if err != nil {
-		return convertErrorToRPCStatus(err)
-	}
-	return nil
-}
-
-// SupportedUserFields FIXMEDOC
-func (s *ArduinoCoreServerImpl) SupportedUserFields(ctx context.Context, req *rpc.SupportedUserFieldsRequest) (*rpc.SupportedUserFieldsResponse, error) {
-	res, err := upload.SupportedUserFields(ctx, req)
-	return res, convertErrorToRPCStatus(err)
-}
-
-// BurnBootloader FIXMEDOC
-func (s *ArduinoCoreServerImpl) BurnBootloader(req *rpc.BurnBootloaderRequest, stream rpc.ArduinoCoreService_BurnBootloaderServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-	outStream := feedStreamTo(func(data []byte) {
-		syncSend.Send(&rpc.BurnBootloaderResponse{
-			Message: &rpc.BurnBootloaderResponse_OutStream{
-				OutStream: data,
-			},
-		})
-	})
-	errStream := feedStreamTo(func(data []byte) {
-		syncSend.Send(&rpc.BurnBootloaderResponse{
-			Message: &rpc.BurnBootloaderResponse_ErrStream{
-				ErrStream: data,
-			},
-		})
-	})
-	resp, err := upload.BurnBootloader(stream.Context(), req, outStream, errStream)
-	outStream.Close()
-	errStream.Close()
-	if err != nil {
-		return convertErrorToRPCStatus(err)
-	}
-	return syncSend.Send(resp)
-}
-
-// ListProgrammersAvailableForUpload FIXMEDOC
-func (s *ArduinoCoreServerImpl) ListProgrammersAvailableForUpload(ctx context.Context, req *rpc.ListProgrammersAvailableForUploadRequest) (*rpc.ListProgrammersAvailableForUploadResponse, error) {
-	resp, err := upload.ListProgrammersAvailableForUpload(ctx, req)
-	return resp, convertErrorToRPCStatus(err)
-}
-
-// LibraryDownload FIXMEDOC
-func (s *ArduinoCoreServerImpl) LibraryDownload(req *rpc.LibraryDownloadRequest, stream rpc.ArduinoCoreService_LibraryDownloadServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-	resp, err := lib.LibraryDownload(
-		stream.Context(), req,
-		func(p *rpc.DownloadProgress) { syncSend.Send(&rpc.LibraryDownloadResponse{Progress: p}) },
-	)
-	if err != nil {
-		return convertErrorToRPCStatus(err)
-	}
-	return syncSend.Send(resp)
-}
-
-// LibraryInstall FIXMEDOC
-func (s *ArduinoCoreServerImpl) LibraryInstall(req *rpc.LibraryInstallRequest, stream rpc.ArduinoCoreService_LibraryInstallServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-	err := lib.LibraryInstall(
-		stream.Context(), req,
-		func(p *rpc.DownloadProgress) { syncSend.Send(&rpc.LibraryInstallResponse{Progress: p}) },
-		func(p *rpc.TaskProgress) { syncSend.Send(&rpc.LibraryInstallResponse{TaskProgress: p}) },
-	)
-	return convertErrorToRPCStatus(err)
-}
-
-// LibraryUpgrade FIXMEDOC
-func (s *ArduinoCoreServerImpl) LibraryUpgrade(req *rpc.LibraryUpgradeRequest, stream rpc.ArduinoCoreService_LibraryUpgradeServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-	err := lib.LibraryUpgrade(
-		stream.Context(), req,
-		func(p *rpc.DownloadProgress) { syncSend.Send(&rpc.LibraryUpgradeResponse{Progress: p}) },
-		func(p *rpc.TaskProgress) { syncSend.Send(&rpc.LibraryUpgradeResponse{TaskProgress: p}) },
-	)
-	return convertErrorToRPCStatus(err)
-}
-
-// LibraryUninstall FIXMEDOC
-func (s *ArduinoCoreServerImpl) LibraryUninstall(req *rpc.LibraryUninstallRequest, stream rpc.ArduinoCoreService_LibraryUninstallServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-	err := lib.LibraryUninstall(stream.Context(), req,
-		func(p *rpc.TaskProgress) { syncSend.Send(&rpc.LibraryUninstallResponse{TaskProgress: p}) },
-	)
-	return convertErrorToRPCStatus(err)
-}
-
-// LibraryUpgradeAll FIXMEDOC
-func (s *ArduinoCoreServerImpl) LibraryUpgradeAll(req *rpc.LibraryUpgradeAllRequest, stream rpc.ArduinoCoreService_LibraryUpgradeAllServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-	err := lib.LibraryUpgradeAll(req,
-		func(p *rpc.DownloadProgress) { syncSend.Send(&rpc.LibraryUpgradeAllResponse{Progress: p}) },
-		func(p *rpc.TaskProgress) { syncSend.Send(&rpc.LibraryUpgradeAllResponse{TaskProgress: p}) },
-	)
-	return convertErrorToRPCStatus(err)
-}
-
-// LibraryResolveDependencies FIXMEDOC
-func (s *ArduinoCoreServerImpl) LibraryResolveDependencies(ctx context.Context, req *rpc.LibraryResolveDependenciesRequest) (*rpc.LibraryResolveDependenciesResponse, error) {
-	resp, err := lib.LibraryResolveDependencies(ctx, req)
-	return resp, convertErrorToRPCStatus(err)
-}
-
-// LibrarySearch FIXMEDOC
-func (s *ArduinoCoreServerImpl) LibrarySearch(ctx context.Context, req *rpc.LibrarySearchRequest) (*rpc.LibrarySearchResponse, error) {
-	resp, err := lib.LibrarySearch(ctx, req)
-	return resp, convertErrorToRPCStatus(err)
-}
-
-// LibraryList FIXMEDOC
-func (s *ArduinoCoreServerImpl) LibraryList(ctx context.Context, req *rpc.LibraryListRequest) (*rpc.LibraryListResponse, error) {
-	resp, err := lib.LibraryList(ctx, req)
-	return resp, convertErrorToRPCStatus(err)
-}
-
-// ArchiveSketch FIXMEDOC
-func (s *ArduinoCoreServerImpl) ArchiveSketch(ctx context.Context, req *rpc.ArchiveSketchRequest) (*rpc.ArchiveSketchResponse, error) {
-	resp, err := sketch.ArchiveSketch(ctx, req)
-	return resp, convertErrorToRPCStatus(err)
-}
-
-// ZipLibraryInstall FIXMEDOC
-func (s *ArduinoCoreServerImpl) ZipLibraryInstall(req *rpc.ZipLibraryInstallRequest, stream rpc.ArduinoCoreService_ZipLibraryInstallServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-	err := lib.ZipLibraryInstall(
-		stream.Context(), req,
-		func(p *rpc.TaskProgress) { syncSend.Send(&rpc.ZipLibraryInstallResponse{TaskProgress: p}) },
-	)
-	return convertErrorToRPCStatus(err)
-}
-
-// GitLibraryInstall FIXMEDOC
-func (s *ArduinoCoreServerImpl) GitLibraryInstall(req *rpc.GitLibraryInstallRequest, stream rpc.ArduinoCoreService_GitLibraryInstallServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-	err := lib.GitLibraryInstall(
-		stream.Context(), req,
-		func(p *rpc.TaskProgress) { syncSend.Send(&rpc.GitLibraryInstallResponse{TaskProgress: p}) },
-	)
-	return convertErrorToRPCStatus(err)
-}
-
-// EnumerateMonitorPortSettings FIXMEDOC
-func (s *ArduinoCoreServerImpl) EnumerateMonitorPortSettings(ctx context.Context, req *rpc.EnumerateMonitorPortSettingsRequest) (*rpc.EnumerateMonitorPortSettingsResponse, error) {
-	resp, err := monitor.EnumerateMonitorPortSettings(ctx, req)
-	return resp, convertErrorToRPCStatus(err)
-}
-
-// Monitor FIXMEDOC
-func (s *ArduinoCoreServerImpl) Monitor(stream rpc.ArduinoCoreService_MonitorServer) error {
-	syncSend := NewSynchronizedSend(stream.Send)
-
-	// The configuration must be sent on the first message
-	req, err := stream.Recv()
-	if err != nil {
-		return err
-	}
-
-	openReq := req.GetOpenRequest()
-	if openReq == nil {
-		return &cmderrors.InvalidInstanceError{}
-	}
-	portProxy, _, err := monitor.Monitor(stream.Context(), openReq)
-	if err != nil {
-		return err
-	}
-
-	// Send a message with Success set to true to notify the caller of the port being now active
-	_ = syncSend.Send(&rpc.MonitorResponse{Success: true})
-
-	cancelCtx, cancel := context.WithCancel(stream.Context())
-	gracefulCloseInitiated := &atomic.Bool{}
-	gracefuleCloseCtx, gracefulCloseCancel := context.WithCancel(context.Background())
-
-	// gRPC stream receiver (gRPC data -> monitor, config, close)
-	go func() {
-		defer cancel()
-		for {
-			msg, err := stream.Recv()
-			if errors.Is(err, io.EOF) {
-				return
-			}
-			if err != nil {
-				syncSend.Send(&rpc.MonitorResponse{Error: err.Error()})
-				return
-			}
-			if conf := msg.GetUpdatedConfiguration(); conf != nil {
-				for _, c := range conf.GetSettings() {
-					if err := portProxy.Config(c.GetSettingId(), c.GetValue()); err != nil {
-						syncSend.Send(&rpc.MonitorResponse{Error: err.Error()})
-					}
-				}
-			}
-			if closeMsg := msg.GetClose(); closeMsg {
-				gracefulCloseInitiated.Store(true)
-				if err := portProxy.Close(); err != nil {
-					logrus.WithError(err).Debug("Error closing monitor port")
-				}
-				gracefulCloseCancel()
-			}
-			tx := msg.GetTxData()
-			for len(tx) > 0 {
-				n, err := portProxy.Write(tx)
-				if errors.Is(err, io.EOF) {
-					return
-				}
-				if err != nil {
-					syncSend.Send(&rpc.MonitorResponse{Error: err.Error()})
-					return
-				}
-				tx = tx[n:]
-			}
-		}
-	}()
-
-	// gRPC stream sender (monitor -> gRPC)
-	go func() {
-		defer cancel() // unlock the receiver
-		buff := make([]byte, 4096)
-		for {
-			n, err := portProxy.Read(buff)
-			if errors.Is(err, io.EOF) {
-				break
-			}
-			if err != nil {
-				syncSend.Send(&rpc.MonitorResponse{Error: err.Error()})
-				break
-			}
-			if err := syncSend.Send(&rpc.MonitorResponse{RxData: buff[:n]}); err != nil {
-				break
-			}
-		}
-	}()
-
-	<-cancelCtx.Done()
-	if gracefulCloseInitiated.Load() {
-		// Port closing has been initiated in the receiver
-		<-gracefuleCloseCtx.Done()
-	} else {
-		portProxy.Close()
-	}
-	return nil
-}
-
-// CheckForArduinoCLIUpdates FIXMEDOC
-func (s *ArduinoCoreServerImpl) CheckForArduinoCLIUpdates(ctx context.Context, req *rpc.CheckForArduinoCLIUpdatesRequest) (*rpc.CheckForArduinoCLIUpdatesResponse, error) {
-	resp, err := updatecheck.CheckForArduinoCLIUpdates(ctx, req)
-	return resp, convertErrorToRPCStatus(err)
-}
-
-// CleanDownloadCacheDirectory FIXMEDOC
-func (s *ArduinoCoreServerImpl) CleanDownloadCacheDirectory(ctx context.Context, req *rpc.CleanDownloadCacheDirectoryRequest) (*rpc.CleanDownloadCacheDirectoryResponse, error) {
-	resp, err := cache.CleanDownloadCacheDirectory(ctx, req)
-	return resp, convertErrorToRPCStatus(err)
-}
diff --git a/commands/daemon/settings.go b/commands/daemon/settings.go
deleted file mode 100644
index 2d8b9ca9039..00000000000
--- a/commands/daemon/settings.go
+++ /dev/null
@@ -1,178 +0,0 @@
-// This file is part of arduino-cli.
-//
-// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
-//
-// This software is released under the GNU General Public License version 3,
-// which covers the main part of arduino-cli.
-// The terms of this license can be found at:
-// https://www.gnu.org/licenses/gpl-3.0.en.html
-//
-// You can be released from the requirements of the above licenses by purchasing
-// a commercial license. Buying such a license is mandatory if you want to
-// modify or otherwise use the software for commercial activities involving the
-// Arduino software without disclosing the source code of your own applications.
-// To purchase a commercial license, send an email to license@arduino.cc.
-
-package daemon
-
-import (
-	"context"
-	"encoding/json"
-	"errors"
-	"fmt"
-	"strings"
-
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
-	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
-)
-
-// SettingsGetAll returns a message with a string field containing all the settings
-// currently in use, marshalled in JSON format.
-func (s *ArduinoCoreServerImpl) SettingsGetAll(ctx context.Context, req *rpc.SettingsGetAllRequest) (*rpc.SettingsGetAllResponse, error) {
-	b, err := json.Marshal(configuration.Settings.AllSettings())
-	if err == nil {
-		return &rpc.SettingsGetAllResponse{
-			JsonData: string(b),
-		}, nil
-	}
-
-	return nil, err
-}
-
-// mapper converts a map of nested maps to a map of scalar values.
-// For example:
-//
-//	{"foo": "bar", "daemon":{"port":"420"}, "sketch": {"always_export_binaries": "true"}}
-//
-// would convert to:
-//
-//	{"foo": "bar", "daemon.port":"420", "sketch.always_export_binaries": "true"}
-func mapper(toMap map[string]interface{}) map[string]interface{} {
-	res := map[string]interface{}{}
-	for k, v := range toMap {
-		switch data := v.(type) {
-		case map[string]interface{}:
-			for mK, mV := range mapper(data) {
-				// Concatenate keys
-				res[fmt.Sprintf("%s.%s", k, mK)] = mV
-			}
-			// This is done to avoid skipping keys containing empty maps
-			if len(data) == 0 {
-				res[k] = map[string]interface{}{}
-			}
-		default:
-			res[k] = v
-		}
-	}
-	return res
-}
-
-// SettingsMerge applies multiple settings values at once.
-func (s *ArduinoCoreServerImpl) SettingsMerge(ctx context.Context, req *rpc.SettingsMergeRequest) (*rpc.SettingsMergeResponse, error) {
-	var toMerge map[string]interface{}
-	if err := json.Unmarshal([]byte(req.GetJsonData()), &toMerge); err != nil {
-		return nil, err
-	}
-
-	mapped := mapper(toMerge)
-
-	// Set each value individually.
-	// This is done because Viper ignores empty strings or maps when
-	// using the MergeConfigMap function.
-	updatedSettings := configuration.Init("")
-	for k, v := range mapped {
-		updatedSettings.Set(k, v)
-	}
-	configPath := configuration.Settings.ConfigFileUsed()
-	updatedSettings.SetConfigFile(configPath)
-	configuration.Settings = updatedSettings
-
-	return &rpc.SettingsMergeResponse{}, nil
-}
-
-// SettingsGetValue returns a settings value given its key. If the key is not present
-// an error will be returned, so that we distinguish empty settings from missing
-// ones.
-func (s *ArduinoCoreServerImpl) SettingsGetValue(ctx context.Context, req *rpc.SettingsGetValueRequest) (*rpc.SettingsGetValueResponse, error) {
-	key := req.GetKey()
-
-	// Check if settings key actually existing, we don't use Viper.InConfig()
-	// since that doesn't check for keys formatted like daemon.port or those set
-	// with Viper.Set(). This way we check for all existing settings for sure.
-	keyExists := false
-	for _, k := range configuration.Settings.AllKeys() {
-		if k == key || strings.HasPrefix(k, key) {
-			keyExists = true
-			break
-		}
-	}
-	if !keyExists {
-		return nil, errors.New(tr("key not found in settings"))
-	}
-
-	b, err := json.Marshal(configuration.Settings.Get(key))
-	value := &rpc.SettingsGetValueResponse{}
-	if err == nil {
-		value.Key = key
-		value.JsonData = string(b)
-	}
-
-	return value, err
-}
-
-// SettingsSetValue updates or set a value for a certain key.
-func (s *ArduinoCoreServerImpl) SettingsSetValue(ctx context.Context, val *rpc.SettingsSetValueRequest) (*rpc.SettingsSetValueResponse, error) {
-	key := val.GetKey()
-	var value interface{}
-
-	err := json.Unmarshal([]byte(val.GetJsonData()), &value)
-	if err == nil {
-		configuration.Settings.Set(key, value)
-	}
-
-	return &rpc.SettingsSetValueResponse{}, err
-}
-
-// SettingsWrite to file set in request the settings currently stored in memory.
-// We don't have a Read() function, that's not necessary since we only want one config file to be used
-// and that's picked up when the CLI is run as daemon, either using the default path or a custom one
-// set with the --config-file flag.
-func (s *ArduinoCoreServerImpl) SettingsWrite(ctx context.Context, req *rpc.SettingsWriteRequest) (*rpc.SettingsWriteResponse, error) {
-	if err := configuration.Settings.WriteConfigAs(req.GetFilePath()); err != nil {
-		return nil, err
-	}
-	return &rpc.SettingsWriteResponse{}, nil
-}
-
-// SettingsDelete removes a key from the config file
-func (s *ArduinoCoreServerImpl) SettingsDelete(ctx context.Context, req *rpc.SettingsDeleteRequest) (*rpc.SettingsDeleteResponse, error) {
-	toDelete := req.GetKey()
-
-	// Check if settings key actually existing, we don't use Viper.InConfig()
-	// since that doesn't check for keys formatted like daemon.port or those set
-	// with Viper.Set(). This way we check for all existing settings for sure.
-	keyExists := false
-	keys := []string{}
-	for _, k := range configuration.Settings.AllKeys() {
-		if !strings.HasPrefix(k, toDelete) {
-			keys = append(keys, k)
-			continue
-		}
-		keyExists = true
-	}
-
-	if !keyExists {
-		return nil, errors.New(tr("key not found in settings"))
-	}
-
-	// Override current settings to delete the key
-	updatedSettings := configuration.Init("")
-	for _, k := range keys {
-		updatedSettings.Set(k, configuration.Settings.Get(k))
-	}
-	configPath := configuration.Settings.ConfigFileUsed()
-	updatedSettings.SetConfigFile(configPath)
-	configuration.Settings = updatedSettings
-
-	return &rpc.SettingsDeleteResponse{}, nil
-}
diff --git a/commands/daemon/settings_test.go b/commands/daemon/settings_test.go
deleted file mode 100644
index 93f49174e50..00000000000
--- a/commands/daemon/settings_test.go
+++ /dev/null
@@ -1,185 +0,0 @@
-// This file is part of arduino-cli.
-//
-// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
-//
-// This software is released under the GNU General Public License version 3,
-// which covers the main part of arduino-cli.
-// The terms of this license can be found at:
-// https://www.gnu.org/licenses/gpl-3.0.en.html
-//
-// You can be released from the requirements of the above licenses by purchasing
-// a commercial license. Buying such a license is mandatory if you want to
-// modify or otherwise use the software for commercial activities involving the
-// Arduino software without disclosing the source code of your own applications.
-// To purchase a commercial license, send an email to license@arduino.cc.
-
-package daemon
-
-import (
-	"context"
-	"encoding/json"
-	"path/filepath"
-	"testing"
-
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
-	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
-	"github.com/arduino/go-paths-helper"
-	"github.com/stretchr/testify/require"
-)
-
-var svc = ArduinoCoreServerImpl{}
-
-func init() {
-	configuration.Settings = configuration.Init(filepath.Join("testdata", "arduino-cli.yaml"))
-}
-
-func reset() {
-	configuration.Settings = configuration.Init(filepath.Join("testdata", "arduino-cli.yaml"))
-}
-
-func TestGetAll(t *testing.T) {
-	resp, err := svc.SettingsGetAll(context.Background(), &rpc.SettingsGetAllRequest{})
-	require.Nil(t, err)
-
-	content, err := json.Marshal(configuration.Settings.AllSettings())
-	require.Nil(t, err)
-
-	require.Equal(t, string(content), resp.GetJsonData())
-}
-
-func TestMerge(t *testing.T) {
-	// Verify defaults
-	require.Equal(t, "50051", configuration.Settings.GetString("daemon.port"))
-	require.Equal(t, "", configuration.Settings.GetString("foo"))
-	require.Equal(t, false, configuration.Settings.GetBool("sketch.always_export_binaries"))
-
-	bulkSettings := `{"foo": "bar", "daemon":{"port":"420"}, "sketch": {"always_export_binaries": "true"}}`
-	res, err := svc.SettingsMerge(context.Background(), &rpc.SettingsMergeRequest{JsonData: bulkSettings})
-	require.NotNil(t, res)
-	require.NoError(t, err)
-
-	require.Equal(t, "420", configuration.Settings.GetString("daemon.port"))
-	require.Equal(t, "bar", configuration.Settings.GetString("foo"))
-	require.Equal(t, true, configuration.Settings.GetBool("sketch.always_export_binaries"))
-
-	bulkSettings = `{"foo":"", "daemon": {}, "sketch": {"always_export_binaries": "false"}}`
-	res, err = svc.SettingsMerge(context.Background(), &rpc.SettingsMergeRequest{JsonData: bulkSettings})
-	require.NotNil(t, res)
-	require.NoError(t, err)
-
-	require.Equal(t, "50051", configuration.Settings.GetString("daemon.port"))
-	require.Equal(t, "", configuration.Settings.GetString("foo"))
-	require.Equal(t, false, configuration.Settings.GetBool("sketch.always_export_binaries"))
-
-	bulkSettings = `{"daemon": {"port":""}}`
-	res, err = svc.SettingsMerge(context.Background(), &rpc.SettingsMergeRequest{JsonData: bulkSettings})
-	require.NotNil(t, res)
-	require.NoError(t, err)
-
-	require.Equal(t, "", configuration.Settings.GetString("daemon.port"))
-	// Verifies other values are not changed
-	require.Equal(t, "", configuration.Settings.GetString("foo"))
-	require.Equal(t, false, configuration.Settings.GetBool("sketch.always_export_binaries"))
-
-	bulkSettings = `{"network": {}}`
-	res, err = svc.SettingsMerge(context.Background(), &rpc.SettingsMergeRequest{JsonData: bulkSettings})
-	require.NotNil(t, res)
-	require.NoError(t, err)
-
-	require.Equal(t, "", configuration.Settings.GetString("proxy"))
-
-	reset()
-}
-
-func TestGetValue(t *testing.T) {
-	key := &rpc.SettingsGetValueRequest{Key: "daemon"}
-	resp, err := svc.SettingsGetValue(context.Background(), key)
-	require.NoError(t, err)
-	require.Equal(t, `{"port":"50051"}`, resp.GetJsonData())
-
-	key = &rpc.SettingsGetValueRequest{Key: "daemon.port"}
-	resp, err = svc.SettingsGetValue(context.Background(), key)
-	require.NoError(t, err)
-	require.Equal(t, `"50051"`, resp.GetJsonData())
-}
-
-func TestGetMergedValue(t *testing.T) {
-	// Verifies value is not set
-	key := &rpc.SettingsGetValueRequest{Key: "foo"}
-	res, err := svc.SettingsGetValue(context.Background(), key)
-	require.Nil(t, res)
-	require.Error(t, err, "Error getting settings value")
-
-	// Merge value
-	bulkSettings := `{"foo": "bar"}`
-	_, err = svc.SettingsMerge(context.Background(), &rpc.SettingsMergeRequest{JsonData: bulkSettings})
-	require.NoError(t, err)
-
-	// Verifies value is correctly returned
-	key = &rpc.SettingsGetValueRequest{Key: "foo"}
-	res, err = svc.SettingsGetValue(context.Background(), key)
-	require.NoError(t, err)
-	require.Equal(t, `"bar"`, res.GetJsonData())
-
-	reset()
-}
-
-func TestGetValueNotFound(t *testing.T) {
-	key := &rpc.SettingsGetValueRequest{Key: "DOESNTEXIST"}
-	_, err := svc.SettingsGetValue(context.Background(), key)
-	require.NotNil(t, err)
-	require.Equal(t, `key not found in settings`, err.Error())
-}
-
-func TestSetValue(t *testing.T) {
-	val := &rpc.SettingsSetValueRequest{
-		Key:      "foo",
-		JsonData: `"bar"`,
-	}
-	_, err := svc.SettingsSetValue(context.Background(), val)
-	require.Nil(t, err)
-	require.Equal(t, "bar", configuration.Settings.GetString("foo"))
-}
-
-func TestWrite(t *testing.T) {
-	// Writes some settings
-	val := &rpc.SettingsSetValueRequest{
-		Key:      "foo",
-		JsonData: `"bar"`,
-	}
-	_, err := svc.SettingsSetValue(context.Background(), val)
-	require.NoError(t, err)
-
-	tempDir := paths.TempDir()
-	testFolder, err := tempDir.MkTempDir("testdata")
-	require.NoError(t, err)
-	defer testFolder.RemoveAll()
-
-	// Verifies config files doesn't exist
-	configFile := testFolder.Join("arduino-cli.yml")
-	require.True(t, configFile.NotExist())
-
-	_, err = svc.SettingsWrite(context.Background(), &rpc.SettingsWriteRequest{
-		FilePath: configFile.String(),
-	})
-	require.NoError(t, err)
-
-	// Verifies config file is created.
-	// We don't verify the content since we expect config library, Viper, to work
-	require.True(t, configFile.Exist())
-}
-
-func TestDelete(t *testing.T) {
-	_, err := svc.SettingsDelete(context.Background(), &rpc.SettingsDeleteRequest{
-		Key: "doesnotexist",
-	})
-	require.Error(t, err)
-
-	_, err = svc.SettingsDelete(context.Background(), &rpc.SettingsDeleteRequest{
-		Key: "network",
-	})
-	require.NoError(t, err)
-
-	_, err = svc.SettingsGetValue(context.Background(), &rpc.SettingsGetValueRequest{Key: "network"})
-	require.Error(t, err)
-}
diff --git a/commands/instances.go b/commands/instances.go
index df1d96f4c41..30fe31ba783 100644
--- a/commands/instances.go
+++ b/commands/instances.go
@@ -36,22 +36,21 @@ import (
 	"github.com/arduino/arduino-cli/internal/arduino/resources"
 	"github.com/arduino/arduino-cli/internal/arduino/sketch"
 	"github.com/arduino/arduino-cli/internal/arduino/utils"
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/arduino/arduino-cli/internal/i18n"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	paths "github.com/arduino/go-paths-helper"
 	"github.com/sirupsen/logrus"
 	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/metadata"
 	"google.golang.org/grpc/status"
 )
 
-var tr = i18n.Tr
-
 func installTool(pm *packagemanager.PackageManager, tool *cores.ToolRelease, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) error {
 	pme, release := pm.NewExplorer()
 	defer release()
+
 	taskCB(&rpc.TaskProgress{Name: tr("Downloading missing tool %s", tool)})
-	if err := pme.DownloadToolRelease(tool, nil, downloadCB); err != nil {
+	if err := pme.DownloadToolRelease(tool, downloadCB); err != nil {
 		return fmt.Errorf(tr("downloading %[1]s tool: %[2]s"), tool, err)
 	}
 	taskCB(&rpc.TaskProgress{Completed: true})
@@ -61,10 +60,18 @@ func installTool(pm *packagemanager.PackageManager, tool *cores.ToolRelease, dow
 	return nil
 }
 
-// Create a new CoreInstance ready to be initialized, supporting directories are also created.
-func Create(req *rpc.CreateRequest, extraUserAgent ...string) (*rpc.CreateResponse, error) {
+// Create a new Instance ready to be initialized, supporting directories are also created.
+func (s *arduinoCoreServerImpl) Create(ctx context.Context, req *rpc.CreateRequest) (*rpc.CreateResponse, error) {
+	var userAgent string
+	if md, ok := metadata.FromIncomingContext(ctx); ok {
+		userAgent = strings.Join(md.Get("user-agent"), " ")
+	}
+	if userAgent == "" {
+		userAgent = "gRPCClientUnknown/0.0.0"
+	}
+
 	// Setup downloads directory
-	downloadsDir := configuration.DownloadsDir(configuration.Settings)
+	downloadsDir := s.settings.DownloadsDir()
 	if downloadsDir.NotExist() {
 		err := downloadsDir.MkdirAll()
 		if err != nil {
@@ -73,8 +80,9 @@ func Create(req *rpc.CreateRequest, extraUserAgent ...string) (*rpc.CreateRespon
 	}
 
 	// Setup data directory
-	dataDir := configuration.DataDir(configuration.Settings)
-	packagesDir := configuration.PackagesDir(configuration.Settings)
+	dataDir := s.settings.DataDir()
+	userPackagesDir := s.settings.UserDir().Join("hardware")
+	packagesDir := s.settings.PackagesDir()
 	if packagesDir.NotExist() {
 		err := packagesDir.MkdirAll()
 		if err != nil {
@@ -82,30 +90,43 @@ func Create(req *rpc.CreateRequest, extraUserAgent ...string) (*rpc.CreateRespon
 		}
 	}
 
-	inst, err := instances.Create(dataDir, packagesDir, downloadsDir, extraUserAgent...)
+	config, err := s.settings.DownloaderConfig()
+	if err != nil {
+		return nil, err
+	}
+	inst, err := instances.Create(dataDir, packagesDir, userPackagesDir, downloadsDir, userAgent, config)
 	if err != nil {
 		return nil, err
 	}
 	return &rpc.CreateResponse{Instance: inst}, nil
 }
 
+// InitStreamResponseToCallbackFunction returns a gRPC stream to be used in Init that sends
+// all responses to the callback function.
+func InitStreamResponseToCallbackFunction(ctx context.Context, cb func(r *rpc.InitResponse) error) rpc.ArduinoCoreService_InitServer {
+	return streamResponseToCallback(ctx, cb)
+}
+
 // Init loads installed libraries and Platforms in CoreInstance with specified ID,
 // a gRPC status error is returned if the CoreInstance doesn't exist.
 // All responses are sent through responseCallback, can be nil to ignore all responses.
 // Failures don't stop the loading process, in case of loading failure the Platform or library
 // is simply skipped and an error gRPC status is sent to responseCallback.
-func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) error {
-	if responseCallback == nil {
-		responseCallback = func(r *rpc.InitResponse) {}
-	}
+func (s *arduinoCoreServerImpl) Init(req *rpc.InitRequest, stream rpc.ArduinoCoreService_InitServer) error {
+	ctx := stream.Context()
+
 	instance := req.GetInstance()
 	if !instances.IsValid(instance) {
 		return &cmderrors.InvalidInstanceError{}
 	}
 
 	// Setup callback functions
-	if responseCallback == nil {
-		responseCallback = func(r *rpc.InitResponse) {}
+	var responseCallback func(*rpc.InitResponse) error
+	if stream != nil {
+		syncSend := NewSynchronizedSend(stream.Send)
+		responseCallback = syncSend.Send
+	} else {
+		responseCallback = func(*rpc.InitResponse) error { return nil }
 	}
 	responseError := func(st *status.Status) {
 		responseCallback(&rpc.InitResponse{
@@ -156,7 +177,7 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
 	defaultIndexURL, _ := utils.URLParse(globals.DefaultIndexURL)
 	allPackageIndexUrls := []*url.URL{defaultIndexURL}
 	if profile == nil {
-		for _, u := range configuration.Settings.GetStringSlice("board_manager.additional_urls") {
+		for _, u := range s.settings.BoardManagerAdditionalUrls() {
 			URL, err := utils.URLParse(u)
 			if err != nil {
 				e := &cmderrors.InitFailedError{
@@ -164,19 +185,20 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
 					Cause:  fmt.Errorf(tr("Invalid additional URL: %v", err)),
 					Reason: rpc.FailedInstanceInitReason_FAILED_INSTANCE_INIT_REASON_INVALID_INDEX_URL,
 				}
-				responseError(e.ToRPCStatus())
+				responseError(e.GRPCStatus())
 				continue
 			}
 			allPackageIndexUrls = append(allPackageIndexUrls, URL)
 		}
 	}
-	if err := firstUpdate(context.Background(), req.GetInstance(), downloadCallback, allPackageIndexUrls); err != nil {
+
+	if err := firstUpdate(ctx, s, req.GetInstance(), s.settings.DataDir(), downloadCallback, allPackageIndexUrls); err != nil {
 		e := &cmderrors.InitFailedError{
 			Code:   codes.InvalidArgument,
 			Cause:  err,
 			Reason: rpc.FailedInstanceInitReason_FAILED_INSTANCE_INIT_REASON_INDEX_DOWNLOAD_ERROR,
 		}
-		responseError(e.ToRPCStatus())
+		responseError(e.GRPCStatus())
 	}
 
 	{
@@ -201,7 +223,7 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
 						Cause:  fmt.Errorf(tr("Loading index file: %v", err)),
 						Reason: rpc.FailedInstanceInitReason_FAILED_INSTANCE_INIT_REASON_INDEX_LOAD_ERROR,
 					}
-					responseError(e.ToRPCStatus())
+					responseError(e.GRPCStatus())
 				}
 				continue
 			}
@@ -212,7 +234,7 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
 					Cause:  fmt.Errorf(tr("Loading index file: %v", err)),
 					Reason: rpc.FailedInstanceInitReason_FAILED_INSTANCE_INIT_REASON_INDEX_LOAD_ERROR,
 				}
-				responseError(e.ToRPCStatus())
+				responseError(e.GRPCStatus())
 			}
 		}
 
@@ -225,16 +247,14 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
 		if profile == nil {
 			for _, err := range pmb.LoadHardware() {
 				s := &cmderrors.PlatformLoadingError{Cause: err}
-				responseError(s.ToRPCStatus())
+				responseError(s.GRPCStatus())
 			}
 		} else {
 			// Load platforms from profile
-			errs := pmb.LoadHardwareForProfile(
-				profile, true, downloadCallback, taskCallback,
-			)
+			errs := pmb.LoadHardwareForProfile(ctx, profile, true, downloadCallback, taskCallback, s.settings)
 			for _, err := range errs {
 				s := &cmderrors.PlatformLoadingError{Cause: err}
-				responseError(s.ToRPCStatus())
+				responseError(s.GRPCStatus())
 			}
 
 			// Load "builtin" tools
@@ -254,7 +274,7 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
 					Cause:  fmt.Errorf(tr("can't find latest release of tool %s", name)),
 					Reason: rpc.FailedInstanceInitReason_FAILED_INSTANCE_INIT_REASON_TOOL_LOAD_ERROR,
 				}
-				responseError(e.ToRPCStatus())
+				responseError(e.GRPCStatus())
 			} else if !latest.IsInstalled() {
 				builtinToolsToInstall = append(builtinToolsToInstall, latest)
 			}
@@ -269,7 +289,7 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
 						Cause:  err,
 						Reason: rpc.FailedInstanceInitReason_FAILED_INSTANCE_INIT_REASON_TOOL_LOAD_ERROR,
 					}
-					responseError(e.ToRPCStatus())
+					responseError(e.GRPCStatus())
 				}
 			}
 
@@ -277,7 +297,7 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
 			// so we must reload again otherwise we would never found them.
 			for _, err := range loadBuiltinTools() {
 				s := &cmderrors.PlatformLoadingError{Cause: err}
-				responseError(s.ToRPCStatus())
+				responseError(s.GRPCStatus())
 			}
 		}
 
@@ -292,7 +312,7 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
 
 	for _, err := range pme.LoadDiscoveries() {
 		s := &cmderrors.PlatformLoadingError{Cause: err}
-		responseError(s.ToRPCStatus())
+		responseError(s.GRPCStatus())
 	}
 
 	// Create library manager and add libraries directories
@@ -329,7 +349,7 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
 
 	if profile == nil {
 		// Add directories of libraries bundled with IDE
-		if bundledLibsDir := configuration.IDEBuiltinLibrariesDir(configuration.Settings); bundledLibsDir != nil {
+		if bundledLibsDir := s.settings.IDEBuiltinLibrariesDir(); bundledLibsDir != nil {
 			lmb.AddLibrariesDir(librariesmanager.LibrariesDir{
 				Path:     bundledLibsDir,
 				Location: libraries.IDEBuiltIn,
@@ -338,14 +358,14 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
 
 		// Add libraries directory from config file
 		lmb.AddLibrariesDir(librariesmanager.LibrariesDir{
-			Path:     configuration.LibrariesDir(configuration.Settings),
+			Path:     s.settings.LibrariesDir(),
 			Location: libraries.User,
 		})
 	} else {
 		// Load libraries required for profile
 		for _, libraryRef := range profile.Libraries {
 			uid := libraryRef.InternalUniqueIdentifier()
-			libRoot := configuration.ProfilesCacheDir(configuration.Settings).Join(uid)
+			libRoot := s.settings.ProfilesCacheDir().Join(uid)
 			libDir := libRoot.Join(libraryRef.Library)
 
 			if !libDir.IsDir() {
@@ -355,13 +375,20 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
 				if err != nil {
 					taskCallback(&rpc.TaskProgress{Name: tr("Library %s not found", libraryRef)})
 					err := &cmderrors.LibraryNotFoundError{Library: libraryRef.Library}
-					responseError(err.ToRPCStatus())
+					responseError(err.GRPCStatus())
 					continue
 				}
-				if err := libRelease.Resource.Download(pme.DownloadDir, nil, libRelease.String(), downloadCallback, ""); err != nil {
+				config, err := s.settings.DownloaderConfig()
+				if err != nil {
 					taskCallback(&rpc.TaskProgress{Name: tr("Error downloading library %s", libraryRef)})
 					e := &cmderrors.FailedLibraryInstallError{Cause: err}
-					responseError(e.ToRPCStatus())
+					responseError(e.GRPCStatus())
+					continue
+				}
+				if err := libRelease.Resource.Download(pme.DownloadDir, config, libRelease.String(), downloadCallback, ""); err != nil {
+					taskCallback(&rpc.TaskProgress{Name: tr("Error downloading library %s", libraryRef)})
+					e := &cmderrors.FailedLibraryInstallError{Cause: err}
+					responseError(e.GRPCStatus())
 					continue
 				}
 				taskCallback(&rpc.TaskProgress{Completed: true})
@@ -371,7 +398,7 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
 				if err := libRelease.Resource.Install(pme.DownloadDir, libRoot, libDir); err != nil {
 					taskCallback(&rpc.TaskProgress{Name: tr("Error installing library %s", libraryRef)})
 					e := &cmderrors.FailedLibraryInstallError{Cause: err}
-					responseError(e.ToRPCStatus())
+					responseError(e.GRPCStatus())
 					continue
 				}
 				taskCallback(&rpc.TaskProgress{Completed: true})
@@ -394,43 +421,69 @@ func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) erro
 	// Refreshes the locale used, this will change the
 	// language of the CLI if the locale is different
 	// after started.
-	i18n.Init(configuration.Settings.GetString("locale"))
+	i18n.Init(s.settings.GetString("locale"))
 
 	return nil
 }
 
-// Destroy FIXMEDOC
-func Destroy(ctx context.Context, req *rpc.DestroyRequest) (*rpc.DestroyResponse, error) {
+// Destroy deletes an instance.
+func (s *arduinoCoreServerImpl) Destroy(ctx context.Context, req *rpc.DestroyRequest) (*rpc.DestroyResponse, error) {
 	if ok := instances.Delete(req.GetInstance()); !ok {
 		return nil, &cmderrors.InvalidInstanceError{}
 	}
 	return &rpc.DestroyResponse{}, nil
 }
 
+// UpdateLibrariesIndexStreamResponseToCallbackFunction returns a gRPC stream to be used in UpdateLibrariesIndex that sends
+// all responses to the callback function.
+func UpdateLibrariesIndexStreamResponseToCallbackFunction(ctx context.Context, downloadCB rpc.DownloadProgressCB) (rpc.ArduinoCoreService_UpdateLibrariesIndexServer, func() *rpc.UpdateLibrariesIndexResponse_Result) {
+	var result *rpc.UpdateLibrariesIndexResponse_Result
+	return streamResponseToCallback(ctx, func(r *rpc.UpdateLibrariesIndexResponse) error {
+			if r.GetDownloadProgress() != nil {
+				downloadCB(r.GetDownloadProgress())
+			}
+			if r.GetResult() != nil {
+				result = r.GetResult()
+			}
+			return nil
+		}), func() *rpc.UpdateLibrariesIndexResponse_Result {
+			return result
+		}
+}
+
 // UpdateLibrariesIndex updates the library_index.json
-func UpdateLibrariesIndex(ctx context.Context, req *rpc.UpdateLibrariesIndexRequest, downloadCB rpc.DownloadProgressCB) (*rpc.UpdateLibrariesIndexResponse_Result, error) {
-	logrus.Info("Updating libraries index")
+func (s *arduinoCoreServerImpl) UpdateLibrariesIndex(req *rpc.UpdateLibrariesIndexRequest, stream rpc.ArduinoCoreService_UpdateLibrariesIndexServer) error {
+	syncSend := NewSynchronizedSend(stream.Send)
+	downloadCB := func(p *rpc.DownloadProgress) {
+		syncSend.Send(&rpc.UpdateLibrariesIndexResponse{
+			Message: &rpc.UpdateLibrariesIndexResponse_DownloadProgress{DownloadProgress: p}})
+	}
 
 	pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
 	if err != nil {
-		return nil, err
+		return err
 	}
 	indexDir := pme.IndexDir
 	release()
-
 	index := globals.LibrariesIndexResource
-	result := func(status rpc.IndexUpdateReport_Status) *rpc.UpdateLibrariesIndexResponse_Result {
-		return &rpc.UpdateLibrariesIndexResponse_Result{
-			LibrariesIndex: &rpc.IndexUpdateReport{
-				IndexUrl: globals.LibrariesIndexResource.URL.String(),
-				Status:   status,
+
+	resultCB := func(status rpc.IndexUpdateReport_Status) {
+		syncSend.Send(&rpc.UpdateLibrariesIndexResponse{
+			Message: &rpc.UpdateLibrariesIndexResponse_Result_{
+				Result: &rpc.UpdateLibrariesIndexResponse_Result{
+					LibrariesIndex: &rpc.IndexUpdateReport{
+						IndexUrl: index.URL.String(),
+						Status:   status,
+					},
+				},
 			},
-		}
+		})
 	}
 
 	// Create the index directory if it doesn't exist
 	if err := indexDir.MkdirAll(); err != nil {
-		return result(rpc.IndexUpdateReport_STATUS_FAILED), &cmderrors.PermissionDeniedError{Message: tr("Could not create index directory"), Cause: err}
+		resultCB(rpc.IndexUpdateReport_STATUS_FAILED)
+		return &cmderrors.PermissionDeniedError{Message: tr("Could not create index directory"), Cause: err}
 	}
 
 	// Check if the index file is already up-to-date
@@ -438,22 +491,46 @@ func UpdateLibrariesIndex(ctx context.Context, req *rpc.UpdateLibrariesIndexRequ
 	if info, err := indexDir.Join(indexFileName).Stat(); err == nil {
 		ageSecs := int64(time.Since(info.ModTime()).Seconds())
 		if ageSecs < req.GetUpdateIfOlderThanSecs() {
-			return result(rpc.IndexUpdateReport_STATUS_ALREADY_UP_TO_DATE), nil
+			resultCB(rpc.IndexUpdateReport_STATUS_ALREADY_UP_TO_DATE)
+			return nil
 		}
 	}
 
 	// Perform index update
-	if err := globals.LibrariesIndexResource.Download(indexDir, downloadCB); err != nil {
-		return nil, err
+	config, err := s.settings.DownloaderConfig()
+	if err != nil {
+		return err
+	}
+	if err := globals.LibrariesIndexResource.Download(stream.Context(), indexDir, downloadCB, config); err != nil {
+		resultCB(rpc.IndexUpdateReport_STATUS_FAILED)
+		return err
 	}
 
-	return result(rpc.IndexUpdateReport_STATUS_UPDATED), nil
+	resultCB(rpc.IndexUpdateReport_STATUS_UPDATED)
+	return nil
+}
+
+// UpdateIndexStreamResponseToCallbackFunction returns a gRPC stream to be used in UpdateIndex that sends
+// all responses to the callback function.
+func UpdateIndexStreamResponseToCallbackFunction(ctx context.Context, downloadCB rpc.DownloadProgressCB) (rpc.ArduinoCoreService_UpdateIndexServer, func() *rpc.UpdateIndexResponse_Result) {
+	var result *rpc.UpdateIndexResponse_Result
+	return streamResponseToCallback(ctx, func(r *rpc.UpdateIndexResponse) error {
+			if r.GetDownloadProgress() != nil {
+				downloadCB(r.GetDownloadProgress())
+			}
+			if r.GetResult() != nil {
+				result = r.GetResult()
+			}
+			return nil
+		}), func() *rpc.UpdateIndexResponse_Result {
+			return result
+		}
 }
 
 // UpdateIndex FIXMEDOC
-func UpdateIndex(ctx context.Context, req *rpc.UpdateIndexRequest, downloadCB rpc.DownloadProgressCB) (*rpc.UpdateIndexResponse_Result, error) {
+func (s *arduinoCoreServerImpl) UpdateIndex(req *rpc.UpdateIndexRequest, stream rpc.ArduinoCoreService_UpdateIndexServer) error {
 	if !instances.IsValid(req.GetInstance()) {
-		return nil, &cmderrors.InvalidInstanceError{}
+		return &cmderrors.InvalidInstanceError{}
 	}
 
 	report := func(indexURL *url.URL, status rpc.IndexUpdateReport_Status) *rpc.IndexUpdateReport {
@@ -463,11 +540,17 @@ func UpdateIndex(ctx context.Context, req *rpc.UpdateIndexRequest, downloadCB rp
 		}
 	}
 
-	indexpath := configuration.DataDir(configuration.Settings)
+	syncSend := NewSynchronizedSend(stream.Send)
+	var downloadCB rpc.DownloadProgressCB = func(p *rpc.DownloadProgress) {
+		syncSend.Send(&rpc.UpdateIndexResponse{
+			Message: &rpc.UpdateIndexResponse_DownloadProgress{DownloadProgress: p},
+		})
+	}
+	indexpath := s.settings.DataDir()
 
 	urls := []string{globals.DefaultIndexURL}
 	if !req.GetIgnoreCustomPackageIndexes() {
-		urls = append(urls, configuration.Settings.GetStringSlice("board_manager.additional_urls")...)
+		urls = append(urls, s.settings.GetStringSlice("board_manager.additional_urls")...)
 	}
 
 	failed := false
@@ -524,36 +607,45 @@ func UpdateIndex(ctx context.Context, req *rpc.UpdateIndexRequest, downloadCB rp
 			}
 		}
 
+		config, err := s.settings.DownloaderConfig()
+		if err != nil {
+			downloadCB.Start(u, tr("Downloading index: %s", filepath.Base(URL.Path)))
+			downloadCB.End(false, tr("Invalid network configuration: %s", err))
+			failed = true
+			continue
+		}
+
 		if strings.HasSuffix(URL.Host, "arduino.cc") && strings.HasSuffix(URL.Path, ".json") {
 			indexResource.SignatureURL, _ = url.Parse(u) // should not fail because we already parsed it
 			indexResource.SignatureURL.Path += ".sig"
 		}
-		if err := indexResource.Download(indexpath, downloadCB); err != nil {
+		if err := indexResource.Download(stream.Context(), indexpath, downloadCB, config); err != nil {
 			failed = true
 			result.UpdatedIndexes = append(result.GetUpdatedIndexes(), report(URL, rpc.IndexUpdateReport_STATUS_FAILED))
 		} else {
 			result.UpdatedIndexes = append(result.GetUpdatedIndexes(), report(URL, rpc.IndexUpdateReport_STATUS_UPDATED))
 		}
 	}
-
+	syncSend.Send(&rpc.UpdateIndexResponse{
+		Message: &rpc.UpdateIndexResponse_Result_{Result: result},
+	})
 	if failed {
-		return result, &cmderrors.FailedDownloadError{Message: tr("Some indexes could not be updated.")}
+		return &cmderrors.FailedDownloadError{Message: tr("Some indexes could not be updated.")}
 	}
-	return result, nil
+	return nil
 }
 
 // firstUpdate downloads libraries and packages indexes if they don't exist.
 // This ideally is only executed the first time the CLI is run.
-func firstUpdate(ctx context.Context, instance *rpc.Instance, downloadCb func(msg *rpc.DownloadProgress), externalPackageIndexes []*url.URL) error {
-	// Gets the data directory to verify if library_index.json and package_index.json exist
-	dataDir := configuration.DataDir(configuration.Settings)
-	libraryIndex := dataDir.Join("library_index.json")
+func firstUpdate(ctx context.Context, srv rpc.ArduinoCoreServiceServer, instance *rpc.Instance, indexDir *paths.Path, downloadCb func(msg *rpc.DownloadProgress), externalPackageIndexes []*url.URL) error {
+	libraryIndex := indexDir.Join("library_index.json")
 
 	if libraryIndex.NotExist() {
 		// The library_index.json file doesn't exists, that means the CLI is run for the first time
 		// so we proceed with the first update that downloads the file
 		req := &rpc.UpdateLibrariesIndexRequest{Instance: instance}
-		if _, err := UpdateLibrariesIndex(ctx, req, downloadCb); err != nil {
+		stream, _ := UpdateLibrariesIndexStreamResponseToCallbackFunction(ctx, downloadCb)
+		if err := srv.UpdateLibrariesIndex(req, stream); err != nil {
 			return err
 		}
 	}
@@ -568,14 +660,15 @@ func firstUpdate(ctx context.Context, instance *rpc.Instance, downloadCb func(ms
 				Message: tr("Error downloading index '%s'", URL),
 				Cause:   &cmderrors.InvalidURLError{}}
 		}
-		packageIndexFile := dataDir.Join(packageIndexFileName)
+		packageIndexFile := indexDir.Join(packageIndexFileName)
 		if packageIndexFile.NotExist() {
 			// The index file doesn't exists, that means the CLI is run for the first time,
 			// or the 3rd party package index URL has just been added. Similarly to the
 			// library update we download that file and all the other package indexes from
 			// additional_urls
 			req := &rpc.UpdateIndexRequest{Instance: instance}
-			if _, err := UpdateIndex(ctx, req, downloadCb); err != nil {
+			stream, _ := UpdateIndexStreamResponseToCallbackFunction(ctx, downloadCb)
+			if err := srv.UpdateIndex(req, stream); err != nil {
 				return err
 			}
 			break
diff --git a/commands/internal/instances/instances.go b/commands/internal/instances/instances.go
index 019927a4967..272eaaaafdb 100644
--- a/commands/internal/instances/instances.go
+++ b/commands/internal/instances/instances.go
@@ -25,6 +25,7 @@ import (
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/arduino/arduino-cli/version"
 	"github.com/arduino/go-paths-helper"
+	"go.bug.st/downloader/v2"
 )
 
 // coreInstance is an instance of the Arduino Core Services. The user can
@@ -133,15 +134,15 @@ func SetLibraryManager(inst *rpc.Instance, lm *librariesmanager.LibrariesManager
 }
 
 // Create a new *rpc.Instance ready to be initialized
-func Create(dataDir, packagesDir, downloadsDir *paths.Path, extraUserAgent ...string) (*rpc.Instance, error) {
+func Create(dataDir, packagesDir, userPackagesDir, downloadsDir *paths.Path, extraUserAgent string, downloaderConfig downloader.Config) (*rpc.Instance, error) {
 	// Create package manager
 	userAgent := "arduino-cli/" + version.VersionInfo.VersionString
-	for _, ua := range extraUserAgent {
-		userAgent += " " + ua
+	if extraUserAgent != "" {
+		userAgent += " " + extraUserAgent
 	}
 	tempDir := dataDir.Join("tmp")
 
-	pm := packagemanager.NewBuilder(dataDir, packagesDir, downloadsDir, tempDir, userAgent).Build()
+	pm := packagemanager.NewBuilder(dataDir, packagesDir, userPackagesDir, downloadsDir, tempDir, userAgent, downloaderConfig).Build()
 	lm, _ := librariesmanager.NewBuilder().Build()
 
 	instance := &coreInstance{
diff --git a/commands/lib.go b/commands/lib.go
deleted file mode 100644
index 8794f4ea94a..00000000000
--- a/commands/lib.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// This file is part of arduino-cli.
-//
-// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
-//
-// This software is released under the GNU General Public License version 3,
-// which covers the main part of arduino-cli.
-// The terms of this license can be found at:
-// https://www.gnu.org/licenses/gpl-3.0.en.html
-//
-// You can be released from the requirements of the above licenses by purchasing
-// a commercial license. Buying such a license is mandatory if you want to
-// modify or otherwise use the software for commercial activities involving the
-// Arduino software without disclosing the source code of your own applications.
-// To purchase a commercial license, send an email to license@arduino.cc.
-
-package commands
-
-/*
-import (
-	"fmt"
-	"context"
-)
-
-func (s *Service) ListLibraries(ctx context.Context, in *ListLibrariesReq) (*ListLibrariesResp, error) {
-	if in.Instance == nil {
-		return nil, fmt.Errorf("invalid request")
-	}
-	instance, ok := instances[in.Instance.Id]
-	if !ok {
-		return nil, fmt.Errorf("instance not found")
-	}
-	libs := lib.ListLibraries(instance.lm, in.Updatable)
-
-	result := []*pb.Library{}
-	for _, lib := range libs.Libraries {
-		result = append(result, &pb.Library{
-			Name:        lib.Library.Name,
-			Paragraph:   lib.Library.Paragraph,
-			Precompiled: lib.Library.Precompiled,
-		})
-	}
-	return &pb.ListLibrariesResp{
-		Libraries: result,
-	}, nil
-}
-*/
diff --git a/commands/lib/upgrade.go b/commands/lib/upgrade.go
deleted file mode 100644
index 0377744b972..00000000000
--- a/commands/lib/upgrade.go
+++ /dev/null
@@ -1,107 +0,0 @@
-// This file is part of arduino-cli.
-//
-// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
-//
-// This software is released under the GNU General Public License version 3,
-// which covers the main part of arduino-cli.
-// The terms of this license can be found at:
-// https://www.gnu.org/licenses/gpl-3.0.en.html
-//
-// You can be released from the requirements of the above licenses by purchasing
-// a commercial license. Buying such a license is mandatory if you want to
-// modify or otherwise use the software for commercial activities involving the
-// Arduino software without disclosing the source code of your own applications.
-// To purchase a commercial license, send an email to license@arduino.cc.
-
-package lib
-
-import (
-	"context"
-
-	"github.com/arduino/arduino-cli/commands"
-	"github.com/arduino/arduino-cli/commands/cmderrors"
-	"github.com/arduino/arduino-cli/commands/internal/instances"
-	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
-)
-
-// LibraryUpgradeAll upgrades all the available libraries
-func LibraryUpgradeAll(req *rpc.LibraryUpgradeAllRequest, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) error {
-	li, err := instances.GetLibrariesIndex(req.GetInstance())
-	if err != nil {
-		return err
-	}
-
-	lme, release, err := instances.GetLibraryManagerExplorer(req.GetInstance())
-	if err != nil {
-		return err
-	}
-	libsToUpgrade := listLibraries(lme, li, true, false)
-	release()
-
-	if err := upgrade(req.GetInstance(), libsToUpgrade, downloadCB, taskCB); err != nil {
-		return err
-	}
-
-	if err := commands.Init(&rpc.InitRequest{Instance: req.GetInstance()}, nil); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-// LibraryUpgrade upgrades a library
-func LibraryUpgrade(ctx context.Context, req *rpc.LibraryUpgradeRequest, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) error {
-	li, err := instances.GetLibrariesIndex(req.GetInstance())
-	if err != nil {
-		return err
-	}
-
-	lme, release, err := instances.GetLibraryManagerExplorer(req.GetInstance())
-	if err != nil {
-		return err
-	}
-	libs := listLibraries(lme, li, false, false)
-	release()
-
-	// Get the library to upgrade
-	name := req.GetName()
-	lib := filterByName(libs, name)
-	if lib == nil {
-		// library not installed...
-		return &cmderrors.LibraryNotFoundError{Library: name}
-	}
-	if lib.Available == nil {
-		taskCB(&rpc.TaskProgress{Message: tr("Library %s is already at the latest version", name), Completed: true})
-		return nil
-	}
-
-	// Install update
-	return upgrade(req.GetInstance(), []*installedLib{lib}, downloadCB, taskCB)
-}
-
-func upgrade(instance *rpc.Instance, libs []*installedLib, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) error {
-	for _, lib := range libs {
-		libInstallReq := &rpc.LibraryInstallRequest{
-			Instance:    instance,
-			Name:        lib.Library.Name,
-			Version:     "",
-			NoDeps:      false,
-			NoOverwrite: false,
-		}
-		err := LibraryInstall(context.Background(), libInstallReq, downloadCB, taskCB)
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-func filterByName(libs []*installedLib, name string) *installedLib {
-	for _, lib := range libs {
-		if lib.Library.Name == name {
-			return lib
-		}
-	}
-	return nil
-}
diff --git a/commands/monitor/monitor.go b/commands/monitor/monitor.go
deleted file mode 100644
index 357a885be31..00000000000
--- a/commands/monitor/monitor.go
+++ /dev/null
@@ -1,176 +0,0 @@
-// This file is part of arduino-cli.
-//
-// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
-//
-// This software is released under the GNU General Public License version 3,
-// which covers the main part of arduino-cli.
-// The terms of this license can be found at:
-// https://www.gnu.org/licenses/gpl-3.0.en.html
-//
-// You can be released from the requirements of the above licenses by purchasing
-// a commercial license. Buying such a license is mandatory if you want to
-// modify or otherwise use the software for commercial activities involving the
-// Arduino software without disclosing the source code of your own applications.
-// To purchase a commercial license, send an email to license@arduino.cc.
-
-package monitor
-
-import (
-	"context"
-	"fmt"
-	"io"
-
-	"github.com/arduino/arduino-cli/commands/cmderrors"
-	"github.com/arduino/arduino-cli/commands/internal/instances"
-	"github.com/arduino/arduino-cli/internal/arduino/cores"
-	"github.com/arduino/arduino-cli/internal/arduino/cores/packagemanager"
-	pluggableMonitor "github.com/arduino/arduino-cli/internal/arduino/monitor"
-	"github.com/arduino/arduino-cli/internal/i18n"
-	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
-	"github.com/arduino/go-properties-orderedmap"
-	"github.com/sirupsen/logrus"
-)
-
-var tr = i18n.Tr
-
-// PortProxy is an io.ReadWriteCloser that maps into the monitor port of the board
-type PortProxy struct {
-	rw               io.ReadWriter
-	changeSettingsCB func(setting, value string) error
-	closeCB          func() error
-}
-
-func (p *PortProxy) Read(buff []byte) (int, error) {
-	return p.rw.Read(buff)
-}
-
-func (p *PortProxy) Write(buff []byte) (int, error) {
-	return p.rw.Write(buff)
-}
-
-// Config sets the port configuration setting to the specified value
-func (p *PortProxy) Config(setting, value string) error {
-	return p.changeSettingsCB(setting, value)
-}
-
-// Close the port
-func (p *PortProxy) Close() error {
-	return p.closeCB()
-}
-
-// Monitor opens a communication port. It returns a PortProxy to communicate with the port and a PortDescriptor
-// that describes the available configuration settings.
-func Monitor(ctx context.Context, req *rpc.MonitorPortOpenRequest) (*PortProxy, *pluggableMonitor.PortDescriptor, error) {
-	pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
-	if err != nil {
-		return nil, nil, err
-	}
-	defer release()
-
-	m, boardSettings, err := findMonitorAndSettingsForProtocolAndBoard(pme, req.GetPort().GetProtocol(), req.GetFqbn())
-	if err != nil {
-		return nil, nil, err
-	}
-
-	if err := m.Run(); err != nil {
-		return nil, nil, &cmderrors.FailedMonitorError{Cause: err}
-	}
-
-	descriptor, err := m.Describe()
-	if err != nil {
-		m.Quit()
-		return nil, nil, &cmderrors.FailedMonitorError{Cause: err}
-	}
-
-	// Apply user-requested settings
-	if portConfig := req.GetPortConfiguration(); portConfig != nil {
-		for _, setting := range portConfig.GetSettings() {
-			boardSettings.Remove(setting.GetSettingId()) // Remove board settings overridden by the user
-			if err := m.Configure(setting.GetSettingId(), setting.GetValue()); err != nil {
-				logrus.Errorf("Could not set configuration %s=%s: %s", setting.GetSettingId(), setting.GetValue(), err)
-			}
-		}
-	}
-	// Apply specific board settings
-	for setting, value := range boardSettings.AsMap() {
-		m.Configure(setting, value)
-	}
-
-	monIO, err := m.Open(req.GetPort().GetAddress(), req.GetPort().GetProtocol())
-	if err != nil {
-		m.Quit()
-		return nil, nil, &cmderrors.FailedMonitorError{Cause: err}
-	}
-
-	logrus.Infof("Port %s successfully opened", req.GetPort().GetAddress())
-	return &PortProxy{
-		rw:               monIO,
-		changeSettingsCB: m.Configure,
-		closeCB: func() error {
-			m.Close()
-			return m.Quit()
-		},
-	}, descriptor, nil
-}
-
-func findMonitorAndSettingsForProtocolAndBoard(pme *packagemanager.Explorer, protocol, fqbn string) (*pluggableMonitor.PluggableMonitor, *properties.Map, error) {
-	if protocol == "" {
-		return nil, nil, &cmderrors.MissingPortProtocolError{}
-	}
-
-	var monitorDepOrRecipe *cores.MonitorDependency
-	boardSettings := properties.NewMap()
-
-	// If a board is specified search the monitor in the board package first
-	if fqbn != "" {
-		fqbn, err := cores.ParseFQBN(fqbn)
-		if err != nil {
-			return nil, nil, &cmderrors.InvalidFQBNError{Cause: err}
-		}
-
-		_, boardPlatform, _, boardProperties, _, err := pme.ResolveFQBN(fqbn)
-		if err != nil {
-			return nil, nil, &cmderrors.UnknownFQBNError{Cause: err}
-		}
-
-		boardSettings = cores.GetMonitorSettings(protocol, boardProperties)
-
-		if mon, ok := boardPlatform.Monitors[protocol]; ok {
-			monitorDepOrRecipe = mon
-		} else if recipe, ok := boardPlatform.MonitorsDevRecipes[protocol]; ok {
-			// If we have a recipe we must resolve it
-			cmdLine := boardProperties.ExpandPropsInString(recipe)
-			cmdArgs, err := properties.SplitQuotedString(cmdLine, `"'`, false)
-			if err != nil {
-				return nil, nil, &cmderrors.InvalidArgumentError{Message: tr("Invalid recipe in platform.txt"), Cause: err}
-			}
-			id := fmt.Sprintf("%s-%s", boardPlatform, protocol)
-			return pluggableMonitor.New(id, cmdArgs...), boardSettings, nil
-		}
-	}
-
-	if monitorDepOrRecipe == nil {
-		// Otherwise look in all package for a suitable monitor
-		for _, platformRel := range pme.InstalledPlatformReleases() {
-			if mon, ok := platformRel.Monitors[protocol]; ok {
-				monitorDepOrRecipe = mon
-				break
-			}
-		}
-	}
-
-	if monitorDepOrRecipe == nil {
-		return nil, nil, &cmderrors.NoMonitorAvailableForProtocolError{Protocol: protocol}
-	}
-
-	// If it is a monitor dependency, resolve tool and create a monitor client
-	tool := pme.FindMonitorDependency(monitorDepOrRecipe)
-	if tool == nil {
-		return nil, nil, &cmderrors.MonitorNotFoundError{Monitor: monitorDepOrRecipe.String()}
-	}
-
-	return pluggableMonitor.New(
-		monitorDepOrRecipe.Name,
-		tool.InstallDir.Join(monitorDepOrRecipe.Name).String(),
-	), boardSettings, nil
-}
diff --git a/commands/service.go b/commands/service.go
new file mode 100644
index 00000000000..46c7bf3e6d5
--- /dev/null
+++ b/commands/service.go
@@ -0,0 +1,48 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package commands
+
+import (
+	"context"
+
+	"github.com/arduino/arduino-cli/internal/cli/configuration"
+	"github.com/arduino/arduino-cli/internal/i18n"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
+	"github.com/arduino/arduino-cli/version"
+)
+
+// NewArduinoCoreServer returns an implementation of the ArduinoCoreService gRPC service
+// that uses the provided version string.
+func NewArduinoCoreServer() rpc.ArduinoCoreServiceServer {
+	settings := configuration.NewSettings()
+
+	// Setup i18n
+	i18n.Init(settings.Locale())
+
+	return &arduinoCoreServerImpl{settings: settings}
+}
+
+type arduinoCoreServerImpl struct {
+	rpc.UnsafeArduinoCoreServiceServer // Force compile error for unimplemented methods
+
+	// Settings holds configurations of the CLI and the gRPC consumers
+	settings *configuration.Settings
+}
+
+// Version returns the version of the Arduino CLI
+func (s *arduinoCoreServerImpl) Version(ctx context.Context, req *rpc.VersionRequest) (*rpc.VersionResponse, error) {
+	return &rpc.VersionResponse{Version: version.VersionInfo.VersionString}, nil
+}
diff --git a/commands/board/details.go b/commands/service_board_details.go
similarity index 95%
rename from commands/board/details.go
rename to commands/service_board_details.go
index 22cfb36a354..5f042452582 100644
--- a/commands/board/details.go
+++ b/commands/service_board_details.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package board
+package commands
 
 import (
 	"context"
@@ -25,9 +25,9 @@ import (
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 )
 
-// Details returns all details for a board including tools and HW identifiers.
+// BoardDetails returns all details for a board including tools and HW identifiers.
 // This command basically gather al the information and translates it into the required grpc struct properties
-func Details(ctx context.Context, req *rpc.BoardDetailsRequest) (*rpc.BoardDetailsResponse, error) {
+func (s *arduinoCoreServerImpl) BoardDetails(ctx context.Context, req *rpc.BoardDetailsRequest) (*rpc.BoardDetailsResponse, error) {
 	pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
 	if err != nil {
 		return nil, err
diff --git a/commands/board/list.go b/commands/service_board_list.go
similarity index 77%
rename from commands/board/list.go
rename to commands/service_board_list.go
index 25c6ef53401..aff1085c9c2 100644
--- a/commands/board/list.go
+++ b/commands/service_board_list.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package board
+package commands
 
 import (
 	"context"
@@ -29,9 +29,10 @@ import (
 
 	"github.com/arduino/arduino-cli/commands/cmderrors"
 	"github.com/arduino/arduino-cli/commands/internal/instances"
+	f "github.com/arduino/arduino-cli/internal/algorithms"
 	"github.com/arduino/arduino-cli/internal/arduino/cores"
 	"github.com/arduino/arduino-cli/internal/arduino/cores/packagemanager"
-	"github.com/arduino/arduino-cli/internal/arduino/httpclient"
+	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/arduino/arduino-cli/internal/inventory"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/arduino/go-properties-orderedmap"
@@ -44,7 +45,7 @@ var (
 	validVidPid = regexp.MustCompile(`0[xX][a-fA-F\d]{4}`)
 )
 
-func cachedAPIByVidPid(vid, pid string) ([]*rpc.BoardListItem, error) {
+func cachedAPIByVidPid(vid, pid string, settings *configuration.Settings) ([]*rpc.BoardListItem, error) {
 	var resp []*rpc.BoardListItem
 
 	cacheKey := fmt.Sprintf("cache.builder-api.v3/boards/byvid/pid/%s/%s", vid, pid)
@@ -58,7 +59,7 @@ func cachedAPIByVidPid(vid, pid string) ([]*rpc.BoardListItem, error) {
 		}
 	}
 
-	resp, err := apiByVidPid(vid, pid) // Perform API requrest
+	resp, err := apiByVidPid(vid, pid, settings) // Perform API requrest
 
 	if err == nil {
 		if cachedResp, err := json.Marshal(resp); err == nil {
@@ -70,7 +71,7 @@ func cachedAPIByVidPid(vid, pid string) ([]*rpc.BoardListItem, error) {
 	return resp, err
 }
 
-func apiByVidPid(vid, pid string) ([]*rpc.BoardListItem, error) {
+func apiByVidPid(vid, pid string, settings *configuration.Settings) ([]*rpc.BoardListItem, error) {
 	// ensure vid and pid are valid before hitting the API
 	if !validVidPid.MatchString(vid) {
 		return nil, errors.New(tr("Invalid vid value: '%s'", vid))
@@ -83,10 +84,7 @@ func apiByVidPid(vid, pid string) ([]*rpc.BoardListItem, error) {
 	req, _ := http.NewRequest("GET", url, nil)
 	req.Header.Set("Content-Type", "application/json")
 
-	// TODO: use proxy if set
-
-	httpClient, err := httpclient.New()
-
+	httpClient, err := settings.NewHttpClient()
 	if err != nil {
 		return nil, fmt.Errorf("%s: %w", tr("failed to initialize http client"), err)
 	}
@@ -129,18 +127,18 @@ func apiByVidPid(vid, pid string) ([]*rpc.BoardListItem, error) {
 	}, nil
 }
 
-func identifyViaCloudAPI(props *properties.Map) ([]*rpc.BoardListItem, error) {
+func identifyViaCloudAPI(props *properties.Map, settings *configuration.Settings) ([]*rpc.BoardListItem, error) {
 	// If the port is not USB do not try identification via cloud
 	if !props.ContainsKey("vid") || !props.ContainsKey("pid") {
 		return nil, nil
 	}
 
 	logrus.Debug("Querying builder API for board identification...")
-	return cachedAPIByVidPid(props.Get("vid"), props.Get("pid"))
+	return cachedAPIByVidPid(props.Get("vid"), props.Get("pid"), settings)
 }
 
 // identify returns a list of boards checking first the installed platforms or the Cloud API
-func identify(pme *packagemanager.Explorer, port *discovery.Port) ([]*rpc.BoardListItem, error) {
+func identify(pme *packagemanager.Explorer, port *discovery.Port, settings *configuration.Settings) ([]*rpc.BoardListItem, error) {
 	boards := []*rpc.BoardListItem{}
 	if port.Properties == nil {
 		return boards, nil
@@ -172,7 +170,7 @@ func identify(pme *packagemanager.Explorer, port *discovery.Port) ([]*rpc.BoardL
 	// if installed cores didn't recognize the board, try querying
 	// the builder API if the board is a USB device port
 	if len(boards) == 0 {
-		items, err := identifyViaCloudAPI(port.Properties)
+		items, err := identifyViaCloudAPI(port.Properties, settings)
 		if err != nil {
 			// this is bad, but keep going
 			logrus.WithError(err).Debug("Error querying builder API")
@@ -201,13 +199,13 @@ func identify(pme *packagemanager.Explorer, port *discovery.Port) ([]*rpc.BoardL
 	return boards, nil
 }
 
-// List returns a list of boards found by the loaded discoveries.
+// BoardList returns a list of boards found by the loaded discoveries.
 // In case of errors partial results from discoveries that didn't fail
 // are returned.
-func List(req *rpc.BoardListRequest) (r []*rpc.DetectedPort, discoveryStartErrors []error, e error) {
+func (s *arduinoCoreServerImpl) BoardList(ctx context.Context, req *rpc.BoardListRequest) (*rpc.BoardListResponse, error) {
 	pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
 	if err != nil {
-		return nil, nil, err
+		return nil, err
 	}
 	defer release()
 
@@ -216,19 +214,19 @@ func List(req *rpc.BoardListRequest) (r []*rpc.DetectedPort, discoveryStartError
 		var err error
 		fqbnFilter, err = cores.ParseFQBN(f)
 		if err != nil {
-			return nil, nil, &cmderrors.InvalidFQBNError{Cause: err}
+			return nil, &cmderrors.InvalidFQBNError{Cause: err}
 		}
 	}
 
 	dm := pme.DiscoveryManager()
-	discoveryStartErrors = dm.Start()
+	warnings := f.Map(dm.Start(), (error).Error)
 	time.Sleep(time.Duration(req.GetTimeout()) * time.Millisecond)
 
-	retVal := []*rpc.DetectedPort{}
+	ports := []*rpc.DetectedPort{}
 	for _, port := range dm.List() {
-		boards, err := identify(pme, port)
+		boards, err := identify(pme, port, s.settings)
 		if err != nil {
-			return nil, discoveryStartErrors, err
+			warnings = append(warnings, err.Error())
 		}
 
 		// boards slice can be empty at this point if neither the cores nor the
@@ -239,10 +237,13 @@ func List(req *rpc.BoardListRequest) (r []*rpc.DetectedPort, discoveryStartError
 		}
 
 		if fqbnFilter == nil || hasMatchingBoard(b, fqbnFilter) {
-			retVal = append(retVal, b)
+			ports = append(ports, b)
 		}
 	}
-	return retVal, discoveryStartErrors, nil
+	return &rpc.BoardListResponse{
+		Ports:    ports,
+		Warnings: warnings,
+	}, nil
 }
 
 func hasMatchingBoard(b *rpc.DetectedPort, fqbnFilter *cores.FQBN) bool {
@@ -258,29 +259,43 @@ func hasMatchingBoard(b *rpc.DetectedPort, fqbnFilter *cores.FQBN) bool {
 	return false
 }
 
-// Watch returns a channel that receives boards connection and disconnection events.
-func Watch(ctx context.Context, req *rpc.BoardListWatchRequest) (<-chan *rpc.BoardListWatchResponse, error) {
+// BoardListWatchProxyToChan return a stream, to be used in BoardListWatch method,
+// that proxies all the responses to a channel.
+func BoardListWatchProxyToChan(ctx context.Context) (rpc.ArduinoCoreService_BoardListWatchServer, <-chan *rpc.BoardListWatchResponse) {
+	return streamResponseToChan[rpc.BoardListWatchResponse](ctx)
+}
+
+// BoardListWatch FIXMEDOC
+func (s *arduinoCoreServerImpl) BoardListWatch(req *rpc.BoardListWatchRequest, stream rpc.ArduinoCoreService_BoardListWatchServer) error {
+	syncSend := NewSynchronizedSend(stream.Send)
+	if req.GetInstance() == nil {
+		err := fmt.Errorf(tr("no instance specified"))
+		syncSend.Send(&rpc.BoardListWatchResponse{
+			EventType: "error",
+			Error:     err.Error(),
+		})
+		return err
+	}
+
 	pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
 	if err != nil {
-		return nil, err
+		return err
 	}
 	defer release()
 	dm := pme.DiscoveryManager()
 
 	watcher, err := dm.Watch()
 	if err != nil {
-		return nil, err
+		return err
 	}
 
 	go func() {
-		<-ctx.Done()
+		<-stream.Context().Done()
 		logrus.Trace("closed watch")
 		watcher.Close()
 	}()
 
-	outChan := make(chan *rpc.BoardListWatchResponse)
 	go func() {
-		defer close(outChan)
 		for event := range watcher.Feed() {
 			port := &rpc.DetectedPort{
 				Port: rpc.DiscoveryPortToRPC(event.Port),
@@ -288,19 +303,19 @@ func Watch(ctx context.Context, req *rpc.BoardListWatchRequest) (<-chan *rpc.Boa
 
 			boardsError := ""
 			if event.Type == "add" {
-				boards, err := identify(pme, event.Port)
+				boards, err := identify(pme, event.Port, s.settings)
 				if err != nil {
 					boardsError = err.Error()
 				}
 				port.MatchingBoards = boards
 			}
-			outChan <- &rpc.BoardListWatchResponse{
+			stream.Send(&rpc.BoardListWatchResponse{
 				EventType: event.Type,
 				Port:      port,
 				Error:     boardsError,
-			}
+			})
 		}
 	}()
 
-	return outChan, nil
+	return nil
 }
diff --git a/commands/board/list_test.go b/commands/service_board_list_test.go
similarity index 85%
rename from commands/board/list_test.go
rename to commands/service_board_list_test.go
index f6e46f19a46..6fb1366d111 100644
--- a/commands/board/list_test.go
+++ b/commands/service_board_list_test.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package board
+package commands
 
 import (
 	"fmt"
@@ -27,13 +27,10 @@ import (
 	"github.com/arduino/go-properties-orderedmap"
 	discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2"
 	"github.com/stretchr/testify/require"
+	"go.bug.st/downloader/v2"
 	semver "go.bug.st/relaxed-semver"
 )
 
-func init() {
-	configuration.Settings = configuration.Init("")
-}
-
 func TestGetByVidPid(t *testing.T) {
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		fmt.Fprintln(w, `
@@ -51,30 +48,36 @@ func TestGetByVidPid(t *testing.T) {
 	defer ts.Close()
 
 	vidPidURL = ts.URL
-	res, err := apiByVidPid("0xf420", "0XF069")
+	settings := configuration.NewSettings()
+	res, err := apiByVidPid("0xf420", "0XF069", settings)
 	require.Nil(t, err)
 	require.Len(t, res, 1)
 	require.Equal(t, "Arduino/Genuino MKR1000", res[0].GetName())
 	require.Equal(t, "arduino:samd:mkr1000", res[0].GetFqbn())
 
 	// wrong vid (too long), wrong pid (not an hex value)
-	_, err = apiByVidPid("0xfffff", "0xDEFG")
+
+	_, err = apiByVidPid("0xfffff", "0xDEFG", settings)
 	require.NotNil(t, err)
 }
 
 func TestGetByVidPidNotFound(t *testing.T) {
+	settings := configuration.NewSettings()
+
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		w.WriteHeader(http.StatusNotFound)
 	}))
 	defer ts.Close()
 
 	vidPidURL = ts.URL
-	res, err := apiByVidPid("0x0420", "0x0069")
+	res, err := apiByVidPid("0x0420", "0x0069", settings)
 	require.NoError(t, err)
 	require.Empty(t, res)
 }
 
 func TestGetByVidPid5xx(t *testing.T) {
+	settings := configuration.NewSettings()
+
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		w.WriteHeader(http.StatusInternalServerError)
 		w.Write([]byte("500 - Ooooops!"))
@@ -82,27 +85,30 @@ func TestGetByVidPid5xx(t *testing.T) {
 	defer ts.Close()
 
 	vidPidURL = ts.URL
-	res, err := apiByVidPid("0x0420", "0x0069")
+	res, err := apiByVidPid("0x0420", "0x0069", settings)
 	require.NotNil(t, err)
 	require.Equal(t, "the server responded with status 500 Internal Server Error", err.Error())
 	require.Len(t, res, 0)
 }
 
 func TestGetByVidPidMalformedResponse(t *testing.T) {
+	settings := configuration.NewSettings()
+
 	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		fmt.Fprintln(w, "{}")
 	}))
 	defer ts.Close()
 
 	vidPidURL = ts.URL
-	res, err := apiByVidPid("0x0420", "0x0069")
+	res, err := apiByVidPid("0x0420", "0x0069", settings)
 	require.NotNil(t, err)
 	require.Equal(t, "wrong format in server response", err.Error())
 	require.Len(t, res, 0)
 }
 
 func TestBoardDetectionViaAPIWithNonUSBPort(t *testing.T) {
-	items, err := identifyViaCloudAPI(properties.NewMap())
+	settings := configuration.NewSettings()
+	items, err := identifyViaCloudAPI(properties.NewMap(), settings)
 	require.NoError(t, err)
 	require.Empty(t, items)
 }
@@ -114,7 +120,7 @@ func TestBoardIdentifySorting(t *testing.T) {
 	defer paths.TempDir().Join("test").RemoveAll()
 
 	// We don't really care about the paths in this case
-	pmb := packagemanager.NewBuilder(dataDir, dataDir, dataDir, dataDir, "test")
+	pmb := packagemanager.NewBuilder(dataDir, dataDir, nil, dataDir, dataDir, "test", downloader.GetDefaultConfig())
 
 	// Create some boards with identical VID:PID combination
 	pack := pmb.GetOrCreatePackage("packager")
@@ -150,7 +156,8 @@ func TestBoardIdentifySorting(t *testing.T) {
 	pme, release := pm.NewExplorer()
 	defer release()
 
-	res, err := identify(pme, &discovery.Port{Properties: idPrefs})
+	settings := configuration.NewSettings()
+	res, err := identify(pme, &discovery.Port{Properties: idPrefs}, settings)
 	require.NoError(t, err)
 	require.NotNil(t, res)
 	require.Len(t, res, 4)
diff --git a/commands/board/listall.go b/commands/service_board_listall.go
similarity index 91%
rename from commands/board/listall.go
rename to commands/service_board_listall.go
index 2b4e4d153e2..93df1f4e338 100644
--- a/commands/board/listall.go
+++ b/commands/service_board_listall.go
@@ -13,22 +13,21 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package board
+package commands
 
 import (
 	"context"
 	"sort"
 	"strings"
 
-	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/commands/internal/instances"
 	"github.com/arduino/arduino-cli/internal/arduino/cores"
 	"github.com/arduino/arduino-cli/internal/arduino/utils"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 )
 
-// ListAll FIXMEDOC
-func ListAll(ctx context.Context, req *rpc.BoardListAllRequest) (*rpc.BoardListAllResponse, error) {
+// BoardListAll list all the boards provided by installed platforms.
+func (s *arduinoCoreServerImpl) BoardListAll(ctx context.Context, req *rpc.BoardListAllRequest) (*rpc.BoardListAllResponse, error) {
 	pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
 	if err != nil {
 		return nil, err
@@ -47,8 +46,8 @@ func ListAll(ctx context.Context, req *rpc.BoardListAllRequest) (*rpc.BoardListA
 			}
 
 			rpcPlatform := &rpc.Platform{
-				Metadata: commands.PlatformToRPCPlatformMetadata(platform),
-				Release:  commands.PlatformReleaseToRPC(installedPlatformRelease),
+				Metadata: platformToRPCPlatformMetadata(platform),
+				Release:  platformReleaseToRPC(installedPlatformRelease),
 			}
 
 			toTest := []string{
diff --git a/commands/board/search.go b/commands/service_board_search.go
similarity index 87%
rename from commands/board/search.go
rename to commands/service_board_search.go
index 00ea3ac1e20..bbe4ca22529 100644
--- a/commands/board/search.go
+++ b/commands/service_board_search.go
@@ -13,24 +13,23 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package board
+package commands
 
 import (
 	"context"
 	"sort"
 	"strings"
 
-	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/commands/internal/instances"
 	"github.com/arduino/arduino-cli/internal/arduino/utils"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 )
 
-// Search returns all boards that match the search arg.
+// BoardSearch returns all boards that match the search arg.
 // Boards are searched in all platforms, including those in the index that are not yet
 // installed. Note that platforms that are not installed don't include boards' FQBNs.
 // If no search argument is used all boards are returned.
-func Search(ctx context.Context, req *rpc.BoardSearchRequest) (*rpc.BoardSearchResponse, error) {
+func (s *arduinoCoreServerImpl) BoardSearch(ctx context.Context, req *rpc.BoardSearchRequest) (*rpc.BoardSearchResponse, error) {
 	pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
 	if err != nil {
 		return nil, err
@@ -68,8 +67,8 @@ func Search(ctx context.Context, req *rpc.BoardSearchRequest) (*rpc.BoardSearchR
 						Fqbn:     board.FQBN(),
 						IsHidden: board.IsHidden(),
 						Platform: &rpc.Platform{
-							Metadata: commands.PlatformToRPCPlatformMetadata(platform),
-							Release:  commands.PlatformReleaseToRPC(installedPlatformRelease),
+							Metadata: platformToRPCPlatformMetadata(platform),
+							Release:  platformReleaseToRPC(installedPlatformRelease),
 						},
 					})
 				}
@@ -83,8 +82,8 @@ func Search(ctx context.Context, req *rpc.BoardSearchRequest) (*rpc.BoardSearchR
 					foundBoards = append(foundBoards, &rpc.BoardListItem{
 						Name: strings.Trim(board.Name, " \n"),
 						Platform: &rpc.Platform{
-							Metadata: commands.PlatformToRPCPlatformMetadata(platform),
-							Release:  commands.PlatformReleaseToRPC(latestPlatformRelease),
+							Metadata: platformToRPCPlatformMetadata(platform),
+							Release:  platformReleaseToRPC(latestPlatformRelease),
 						},
 					})
 				}
diff --git a/commands/cache/clean.go b/commands/service_cache_clean.go
similarity index 73%
rename from commands/cache/clean.go
rename to commands/service_cache_clean.go
index f823b56fffa..5897ebd2e99 100644
--- a/commands/cache/clean.go
+++ b/commands/service_cache_clean.go
@@ -1,6 +1,6 @@
 // This file is part of arduino-cli.
 //
-// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
+// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
 //
 // This software is released under the GNU General Public License version 3,
 // which covers the main part of arduino-cli.
@@ -13,18 +13,17 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package cache
+package commands
 
 import (
 	"context"
 
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 )
 
 // CleanDownloadCacheDirectory clean the download cache directory (where archives are downloaded).
-func CleanDownloadCacheDirectory(ctx context.Context, req *rpc.CleanDownloadCacheDirectoryRequest) (*rpc.CleanDownloadCacheDirectoryResponse, error) {
-	cachePath := configuration.DownloadsDir(configuration.Settings)
+func (s *arduinoCoreServerImpl) CleanDownloadCacheDirectory(ctx context.Context, req *rpc.CleanDownloadCacheDirectoryRequest) (*rpc.CleanDownloadCacheDirectoryResponse, error) {
+	cachePath := s.settings.DownloadsDir()
 	err := cachePath.RemoveAll()
 	if err != nil {
 		return nil, err
diff --git a/commands/updatecheck/check_for_updates.go b/commands/service_check_for_updates.go
similarity index 84%
rename from commands/updatecheck/check_for_updates.go
rename to commands/service_check_for_updates.go
index e7cda71638d..15221905d56 100644
--- a/commands/updatecheck/check_for_updates.go
+++ b/commands/service_check_for_updates.go
@@ -13,15 +13,13 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package updatecheck
+package commands
 
 import (
 	"context"
 	"strings"
 	"time"
 
-	"github.com/arduino/arduino-cli/internal/arduino/httpclient"
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/inventory"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
@@ -29,13 +27,13 @@ import (
 	semver "go.bug.st/relaxed-semver"
 )
 
-func CheckForArduinoCLIUpdates(ctx context.Context, req *rpc.CheckForArduinoCLIUpdatesRequest) (*rpc.CheckForArduinoCLIUpdatesResponse, error) {
+func (s *arduinoCoreServerImpl) CheckForArduinoCLIUpdates(ctx context.Context, req *rpc.CheckForArduinoCLIUpdatesRequest) (*rpc.CheckForArduinoCLIUpdatesResponse, error) {
 	currentVersion, err := semver.Parse(version.VersionInfo.VersionString)
 	if err != nil {
 		return nil, err
 	}
 
-	if !shouldCheckForUpdate(currentVersion) && !req.GetForceCheck() {
+	if !s.shouldCheckForUpdate(currentVersion) && !req.GetForceCheck() {
 		return &rpc.CheckForArduinoCLIUpdatesResponse{}, nil
 	}
 
@@ -45,7 +43,7 @@ func CheckForArduinoCLIUpdates(ctx context.Context, req *rpc.CheckForArduinoCLIU
 		inventory.WriteStore()
 	}()
 
-	latestVersion, err := semver.Parse(getLatestRelease())
+	latestVersion, err := semver.Parse(s.getLatestRelease())
 	if err != nil {
 		return nil, err
 	}
@@ -62,13 +60,13 @@ func CheckForArduinoCLIUpdates(ctx context.Context, req *rpc.CheckForArduinoCLIU
 
 // shouldCheckForUpdate return true if it actually makes sense to check for new updates,
 // false in all other cases.
-func shouldCheckForUpdate(currentVersion *semver.Version) bool {
+func (s *arduinoCoreServerImpl) shouldCheckForUpdate(currentVersion *semver.Version) bool {
 	if strings.Contains(currentVersion.String(), "git-snapshot") || strings.Contains(currentVersion.String(), "nightly") {
 		// This is a dev build, no need to check for updates
 		return false
 	}
 
-	if !configuration.Settings.GetBool("updater.enable_notification") {
+	if !s.settings.GetBool("updater.enable_notification") {
 		// Don't check if the user disabled the notification
 		return false
 	}
@@ -84,8 +82,8 @@ func shouldCheckForUpdate(currentVersion *semver.Version) bool {
 
 // getLatestRelease queries the official Arduino download server for the latest release,
 // if there are no errors or issues a version string is returned, in all other case an empty string.
-func getLatestRelease() string {
-	client, err := httpclient.New()
+func (s *arduinoCoreServerImpl) getLatestRelease() string {
+	client, err := s.settings.NewHttpClient()
 	if err != nil {
 		return ""
 	}
diff --git a/commands/compile/compile.go b/commands/service_compile.go
similarity index 73%
rename from commands/compile/compile.go
rename to commands/service_compile.go
index 7a124aceb00..9722c72671a 100644
--- a/commands/compile/compile.go
+++ b/commands/service_compile.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package compile
+package commands
 
 import (
 	"context"
@@ -22,6 +22,7 @@ import (
 	"io"
 	"sort"
 	"strings"
+	"time"
 
 	"github.com/arduino/arduino-cli/commands/cmderrors"
 	"github.com/arduino/arduino-cli/commands/internal/instances"
@@ -31,46 +32,68 @@ import (
 	"github.com/arduino/arduino-cli/internal/arduino/sketch"
 	"github.com/arduino/arduino-cli/internal/arduino/utils"
 	"github.com/arduino/arduino-cli/internal/buildcache"
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
-	"github.com/arduino/arduino-cli/internal/i18n"
 	"github.com/arduino/arduino-cli/internal/inventory"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	paths "github.com/arduino/go-paths-helper"
 	"github.com/sirupsen/logrus"
 )
 
-var tr = i18n.Tr
+// CompilerServerToStreams creates a gRPC CompileServer that sends the responses to the provided streams.
+// The returned callback function can be used to retrieve the builder result after the compilation is done.
+func CompilerServerToStreams(ctx context.Context, stdOut, stderr io.Writer) (server rpc.ArduinoCoreService_CompileServer, resultCB func() *rpc.BuilderResult) {
+	var builderResult *rpc.BuilderResult
+	stream := streamResponseToCallback(ctx, func(resp *rpc.CompileResponse) error {
+		if out := resp.GetOutStream(); len(out) > 0 {
+			if _, err := stdOut.Write(out); err != nil {
+				return err
+			}
+		}
+		if err := resp.GetErrStream(); len(err) > 0 {
+			if _, err := stderr.Write(err); err != nil {
+				return err
+			}
+		}
+		if result := resp.GetResult(); result != nil {
+			builderResult = result
+		}
+		return nil
+	})
+	return stream, func() *rpc.BuilderResult { return builderResult }
+}
 
-// Compile FIXMEDOC
-func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream io.Writer, progressCB rpc.TaskProgressCB) (r *rpc.BuilderResult, e error) {
-	exportBinaries := configuration.Settings.GetBool("sketch.always_export_binaries")
+// Compile performs a compilation of a sketch.
+func (s *arduinoCoreServerImpl) Compile(req *rpc.CompileRequest, stream rpc.ArduinoCoreService_CompileServer) error {
+	ctx := stream.Context()
+	syncSend := NewSynchronizedSend(stream.Send)
+
+	exportBinaries := s.settings.SketchAlwaysExportBinaries()
 	if e := req.ExportBinaries; e != nil {
 		exportBinaries = *e
 	}
 
 	pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
 	if err != nil {
-		return nil, err
+		return err
 	}
 	defer release()
 
 	if pme.Dirty() {
-		return nil, &cmderrors.InstanceNeedsReinitialization{}
+		return &cmderrors.InstanceNeedsReinitialization{}
 	}
 
 	lm, err := instances.GetLibraryManager(req.GetInstance())
 	if err != nil {
-		return nil, err
+		return err
 	}
 
 	logrus.Tracef("Compile %s for %s started", req.GetSketchPath(), req.GetFqbn())
 	if req.GetSketchPath() == "" {
-		return nil, &cmderrors.MissingSketchPathError{}
+		return &cmderrors.MissingSketchPathError{}
 	}
 	sketchPath := paths.New(req.GetSketchPath())
 	sk, err := sketch.New(sketchPath)
 	if err != nil {
-		return nil, &cmderrors.CantOpenSketchError{Cause: err}
+		return &cmderrors.CantOpenSketchError{Cause: err}
 	}
 
 	fqbnIn := req.GetFqbn()
@@ -82,25 +105,30 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
 		}
 	}
 	if fqbnIn == "" {
-		return nil, &cmderrors.MissingFQBNError{}
+		return &cmderrors.MissingFQBNError{}
 	}
 
 	fqbn, err := cores.ParseFQBN(fqbnIn)
 	if err != nil {
-		return nil, &cmderrors.InvalidFQBNError{Cause: err}
+		return &cmderrors.InvalidFQBNError{Cause: err}
 	}
 	_, targetPlatform, targetBoard, boardBuildProperties, buildPlatform, err := pme.ResolveFQBN(fqbn)
 	if err != nil {
 		if targetPlatform == nil {
-			return nil, &cmderrors.PlatformNotFoundError{
+			return &cmderrors.PlatformNotFoundError{
 				Platform: fmt.Sprintf("%s:%s", fqbn.Package, fqbn.PlatformArch),
 				Cause:    fmt.Errorf(tr("platform not installed")),
 			}
 		}
-		return nil, &cmderrors.InvalidFQBNError{Cause: err}
+		return &cmderrors.InvalidFQBNError{Cause: err}
 	}
 
-	r = &rpc.BuilderResult{}
+	r := &rpc.BuilderResult{}
+	defer func() {
+		syncSend.Send(&rpc.CompileResponse{
+			Message: &rpc.CompileResponse_Result{Result: r},
+		})
+	}()
 	r.BoardPlatform = targetPlatform.ToRPCPlatformReference()
 	r.BuildPlatform = buildPlatform.ToRPCPlatformReference()
 
@@ -123,7 +151,7 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
 	encryptProp := boardBuildProperties.ContainsKey("build.keys.encrypt_key")
 	// we verify that all the properties for the secure boot keys are defined or none of them is defined.
 	if !(keychainProp == signProp && signProp == encryptProp) {
-		return nil, fmt.Errorf(tr("Firmware encryption/signing requires all the following properties to be defined: %s", "build.keys.keychain, build.keys.sign_key, build.keys.encrypt_key"))
+		return fmt.Errorf(tr("Firmware encryption/signing requires all the following properties to be defined: %s", "build.keys.keychain, build.keys.sign_key, build.keys.encrypt_key"))
 	}
 
 	// Generate or retrieve build path
@@ -132,7 +160,7 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
 		buildPath = paths.New(req.GetBuildPath()).Canonical()
 		if in, _ := buildPath.IsInsideDir(sk.FullPath); in && buildPath.IsDir() {
 			if sk.AdditionalFiles, err = removeBuildFromSketchFiles(sk.AdditionalFiles, buildPath); err != nil {
-				return nil, err
+				return err
 			}
 		}
 	}
@@ -140,11 +168,14 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
 		buildPath = sk.DefaultBuildPath()
 	}
 	if err = buildPath.MkdirAll(); err != nil {
-		return nil, &cmderrors.PermissionDeniedError{Message: tr("Cannot create build directory"), Cause: err}
+		return &cmderrors.PermissionDeniedError{Message: tr("Cannot create build directory"), Cause: err}
 	}
 	buildcache.New(buildPath.Parent()).GetOrCreate(buildPath.Base())
 	// cache is purged after compilation to not remove entries that might be required
-	defer maybePurgeBuildCache()
+
+	defer maybePurgeBuildCache(
+		s.settings.GetCompilationsBeforeBuildCachePurge(),
+		s.settings.GetBuildCacheTTL().Abs())
 
 	var coreBuildCachePath *paths.Path
 	if req.GetBuildCachePath() == "" {
@@ -152,28 +183,46 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
 	} else {
 		buildCachePath, err := paths.New(req.GetBuildCachePath()).Abs()
 		if err != nil {
-			return nil, &cmderrors.PermissionDeniedError{Message: tr("Cannot create build cache directory"), Cause: err}
+			return &cmderrors.PermissionDeniedError{Message: tr("Cannot create build cache directory"), Cause: err}
 		}
 		if err := buildCachePath.MkdirAll(); err != nil {
-			return nil, &cmderrors.PermissionDeniedError{Message: tr("Cannot create build cache directory"), Cause: err}
+			return &cmderrors.PermissionDeniedError{Message: tr("Cannot create build cache directory"), Cause: err}
 		}
 		coreBuildCachePath = buildCachePath.Join("core")
 	}
 
 	if _, err := pme.FindToolsRequiredForBuild(targetPlatform, buildPlatform); err != nil {
-		return nil, err
+		return err
 	}
 
 	actualPlatform := buildPlatform
 	otherLibrariesDirs := paths.NewPathList(req.GetLibraries()...)
-	otherLibrariesDirs.Add(configuration.LibrariesDir(configuration.Settings))
+	otherLibrariesDirs.Add(s.settings.LibrariesDir())
 
 	var libsManager *librariesmanager.LibrariesManager
 	if pme.GetProfile() != nil {
 		libsManager = lm
 	}
 
+	outStream := feedStreamTo(func(data []byte) {
+		syncSend.Send(&rpc.CompileResponse{
+			Message: &rpc.CompileResponse_OutStream{OutStream: data},
+		})
+	})
+	defer outStream.Close()
+	errStream := feedStreamTo(func(data []byte) {
+		syncSend.Send(&rpc.CompileResponse{
+			Message: &rpc.CompileResponse_ErrStream{ErrStream: data},
+		})
+	})
+	defer errStream.Close()
+	progressCB := func(p *rpc.TaskProgress) {
+		syncSend.Send(&rpc.CompileResponse{
+			Message: &rpc.CompileResponse_Progress{Progress: p},
+		})
+	}
 	sketchBuilder, err := builder.NewBuilder(
+		ctx,
 		sk,
 		boardBuildProperties,
 		buildPath,
@@ -181,9 +230,9 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
 		coreBuildCachePath,
 		int(req.GetJobs()),
 		req.GetBuildProperties(),
-		configuration.HardwareDirectories(configuration.Settings),
+		s.settings.HardwareDirectories(),
 		otherLibrariesDirs,
-		configuration.IDEBuiltinLibrariesDir(configuration.Settings),
+		s.settings.IDEBuiltinLibrariesDir(),
 		fqbn,
 		req.GetClean(),
 		req.GetSourceOverride(),
@@ -198,14 +247,14 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
 	)
 	if err != nil {
 		if strings.Contains(err.Error(), "invalid build properties") {
-			return nil, &cmderrors.InvalidArgumentError{Message: tr("Invalid build properties"), Cause: err}
+			return &cmderrors.InvalidArgumentError{Message: tr("Invalid build properties"), Cause: err}
 		}
 		if errors.Is(err, builder.ErrSketchCannotBeLocatedInBuildPath) {
-			return r, &cmderrors.CompileFailedError{
+			return &cmderrors.CompileFailedError{
 				Message: tr("Sketch cannot be located in build path. Please specify a different build path"),
 			}
 		}
-		return r, &cmderrors.CompileFailedError{Message: err.Error()}
+		return &cmderrors.CompileFailedError{Message: err.Error()}
 	}
 
 	defer func() {
@@ -235,7 +284,7 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
 
 	// Just get build properties and exit
 	if req.GetShowProperties() {
-		return r, nil
+		return nil
 	}
 
 	if req.GetPreprocess() {
@@ -243,10 +292,10 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
 		preprocessedSketch, err := sketchBuilder.Preprocess()
 		if err != nil {
 			err = &cmderrors.CompileFailedError{Message: err.Error()}
-			return r, err
+			return err
 		}
 		_, err = outStream.Write(preprocessedSketch)
-		return r, err
+		return err
 	}
 
 	defer func() {
@@ -288,7 +337,7 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
 	}
 
 	if err := sketchBuilder.Build(); err != nil {
-		return r, &cmderrors.CompileFailedError{Message: err.Error()}
+		return &cmderrors.CompileFailedError{Message: err.Error()}
 	}
 
 	// If the export directory is set we assume you want to export the binaries
@@ -300,9 +349,8 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
 		exportBinaries = false
 	}
 	if exportBinaries {
-		err := sketchBuilder.RunRecipe("recipe.hooks.savehex.presavehex", ".pattern", false)
-		if err != nil {
-			return r, err
+		if err := sketchBuilder.RunRecipe("recipe.hooks.savehex.presavehex", ".pattern", false); err != nil {
+			return err
 		}
 
 		exportPath := paths.New(req.GetExportDir())
@@ -316,44 +364,40 @@ func Compile(ctx context.Context, req *rpc.CompileRequest, outStream, errStream
 		if !buildPath.EqualsTo(exportPath) {
 			logrus.WithField("path", exportPath).Trace("Saving sketch to export path.")
 			if err := exportPath.MkdirAll(); err != nil {
-				return r, &cmderrors.PermissionDeniedError{Message: tr("Error creating output dir"), Cause: err}
+				return &cmderrors.PermissionDeniedError{Message: tr("Error creating output dir"), Cause: err}
 			}
 
 			baseName, ok := sketchBuilder.GetBuildProperties().GetOk("build.project_name") // == "sketch.ino"
 			if !ok {
-				return r, &cmderrors.MissingPlatformPropertyError{Property: "build.project_name"}
+				return &cmderrors.MissingPlatformPropertyError{Property: "build.project_name"}
 			}
 			buildFiles, err := sketchBuilder.GetBuildPath().ReadDir()
 			if err != nil {
-				return r, &cmderrors.PermissionDeniedError{Message: tr("Error reading build directory"), Cause: err}
+				return &cmderrors.PermissionDeniedError{Message: tr("Error reading build directory"), Cause: err}
 			}
 			buildFiles.FilterPrefix(baseName)
 			for _, buildFile := range buildFiles {
 				exportedFile := exportPath.Join(buildFile.Base())
 				logrus.WithField("src", buildFile).WithField("dest", exportedFile).Trace("Copying artifact.")
 				if err = buildFile.CopyTo(exportedFile); err != nil {
-					return r, &cmderrors.PermissionDeniedError{Message: tr("Error copying output file %s", buildFile), Cause: err}
+					return &cmderrors.PermissionDeniedError{Message: tr("Error copying output file %s", buildFile), Cause: err}
 				}
 			}
 		}
 
-		err = sketchBuilder.RunRecipe("recipe.hooks.savehex.postsavehex", ".pattern", false)
-		if err != nil {
-			return r, err
+		if err = sketchBuilder.RunRecipe("recipe.hooks.savehex.postsavehex", ".pattern", false); err != nil {
+			return err
 		}
 	}
 
 	r.ExecutableSectionsSize = sketchBuilder.ExecutableSectionsSize().ToRPCExecutableSectionSizeArray()
 
 	logrus.Tracef("Compile %s for %s successful", sk.Name, fqbnIn)
-
-	return r, nil
+	return nil
 }
 
 // maybePurgeBuildCache runs the build files cache purge if the policy conditions are met.
-func maybePurgeBuildCache() {
-
-	compilationsBeforePurge := configuration.Settings.GetUint("build_cache.compilations_before_purge")
+func maybePurgeBuildCache(compilationsBeforePurge uint, cacheTTL time.Duration) {
 	// 0 means never purge
 	if compilationsBeforePurge == 0 {
 		return
@@ -366,7 +410,6 @@ func maybePurgeBuildCache() {
 		return
 	}
 	inventory.Store.Set("build_cache.compilation_count_since_last_purge", 0)
-	cacheTTL := configuration.Settings.GetDuration("build_cache.ttl").Abs()
 	buildcache.New(paths.TempDir().Join("arduino", "cores")).Purge(cacheTTL)
 	buildcache.New(paths.TempDir().Join("arduino", "sketches")).Purge(cacheTTL)
 }
diff --git a/commands/daemon/debug.go b/commands/service_debug.go
similarity index 81%
rename from commands/daemon/debug.go
rename to commands/service_debug.go
index d3de963bae4..0f618ce7598 100644
--- a/commands/daemon/debug.go
+++ b/commands/service_debug.go
@@ -13,21 +13,20 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package daemon
+package commands
 
 import (
 	"context"
 	"errors"
 	"os"
 
-	cmd "github.com/arduino/arduino-cli/commands/debug"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 )
 
 // Debug returns a stream response that can be used to fetch data from the
 // target. The first message passed through the `Debug` request must
 // contain DebugRequest configuration params, not data.
-func (s *ArduinoCoreServerImpl) Debug(stream rpc.ArduinoCoreService_DebugServer) error {
+func (s *arduinoCoreServerImpl) Debug(stream rpc.ArduinoCoreService_DebugServer) error {
 	// Grab the first message
 	msg, err := stream.Recv()
 	if err != nil {
@@ -44,7 +43,7 @@ func (s *ArduinoCoreServerImpl) Debug(stream rpc.ArduinoCoreService_DebugServer)
 	signalChan := make(chan os.Signal)
 	defer close(signalChan)
 	outStream := feedStreamTo(func(data []byte) { stream.Send(&rpc.DebugResponse{Data: data}) })
-	resp, debugErr := cmd.Debug(stream.Context(), req,
+	resp, debugErr := Debug(stream.Context(), req,
 		consumeStreamFrom(func() ([]byte, error) {
 			command, err := stream.Recv()
 			if command.GetSendInterrupt() {
@@ -62,13 +61,11 @@ func (s *ArduinoCoreServerImpl) Debug(stream rpc.ArduinoCoreService_DebugServer)
 }
 
 // GetDebugConfig return metadata about a debug session
-func (s *ArduinoCoreServerImpl) GetDebugConfig(ctx context.Context, req *rpc.GetDebugConfigRequest) (*rpc.GetDebugConfigResponse, error) {
-	res, err := cmd.GetDebugConfig(ctx, req)
-	return res, convertErrorToRPCStatus(err)
+func (s *arduinoCoreServerImpl) GetDebugConfig(ctx context.Context, req *rpc.GetDebugConfigRequest) (*rpc.GetDebugConfigResponse, error) {
+	return GetDebugConfig(ctx, req)
 }
 
 // IsDebugSupported checks if debugging is supported for a given configuration
-func (s *ArduinoCoreServerImpl) IsDebugSupported(ctx context.Context, req *rpc.IsDebugSupportedRequest) (*rpc.IsDebugSupportedResponse, error) {
-	res, err := cmd.IsDebugSupported(ctx, req)
-	return res, convertErrorToRPCStatus(err)
+func (s *arduinoCoreServerImpl) IsDebugSupported(ctx context.Context, req *rpc.IsDebugSupportedRequest) (*rpc.IsDebugSupportedResponse, error) {
+	return IsDebugSupported(ctx, req)
 }
diff --git a/commands/debug/debug_info.go b/commands/service_debug_config.go
similarity index 99%
rename from commands/debug/debug_info.go
rename to commands/service_debug_config.go
index 5528dd53b8f..0f4040011f8 100644
--- a/commands/debug/debug_info.go
+++ b/commands/service_debug_config.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package debug
+package commands
 
 import (
 	"context"
diff --git a/commands/debug/debug.go b/commands/service_debug_run.go
similarity index 98%
rename from commands/debug/debug.go
rename to commands/service_debug_run.go
index 431c8fcb830..47ecbf4fe04 100644
--- a/commands/debug/debug.go
+++ b/commands/service_debug_run.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package debug
+package commands
 
 import (
 	"context"
@@ -27,14 +27,11 @@ import (
 	"github.com/arduino/arduino-cli/commands/cmderrors"
 	"github.com/arduino/arduino-cli/commands/internal/instances"
 	"github.com/arduino/arduino-cli/internal/arduino/cores/packagemanager"
-	"github.com/arduino/arduino-cli/internal/i18n"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/arduino/go-paths-helper"
 	"github.com/sirupsen/logrus"
 )
 
-var tr = i18n.Tr
-
 // Debug command launches a debug tool for a sketch.
 // It also implements streams routing:
 // gRPC In -> tool stdIn
diff --git a/commands/debug/debug_test.go b/commands/service_debug_test.go
similarity index 95%
rename from commands/debug/debug_test.go
rename to commands/service_debug_test.go
index 9ca9d7cf056..1aa4feb9796 100644
--- a/commands/debug/debug_test.go
+++ b/commands/service_debug_test.go
@@ -12,7 +12,7 @@
 // modify or otherwise use the software for commercial activities involving the
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
-package debug
+package commands
 
 import (
 	"fmt"
@@ -27,16 +27,17 @@ import (
 	"github.com/arduino/go-properties-orderedmap"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	"go.bug.st/downloader/v2"
 )
 
 func TestGetCommandLine(t *testing.T) {
-	customHardware := paths.New("testdata", "custom_hardware")
-	dataDir := paths.New("testdata", "data_dir", "packages")
+	customHardware := paths.New("testdata", "debug", "custom_hardware")
+	dataDir := paths.New("testdata", "debug", "data_dir", "packages")
 	sketch := "hello"
-	sketchPath := paths.New("testdata", sketch)
+	sketchPath := paths.New("testdata", "debug", sketch)
 	require.NoError(t, sketchPath.ToAbs())
 
-	pmb := packagemanager.NewBuilder(nil, nil, nil, nil, "test")
+	pmb := packagemanager.NewBuilder(nil, nil, nil, nil, nil, "test", downloader.GetDefaultConfig())
 	pmb.LoadHardwareFromDirectory(customHardware)
 	pmb.LoadHardwareFromDirectory(dataDir)
 
diff --git a/commands/lib/download.go b/commands/service_library_download.go
similarity index 57%
rename from commands/lib/download.go
rename to commands/service_library_download.go
index 9d174ae1927..8543e8cdea6 100644
--- a/commands/lib/download.go
+++ b/commands/service_library_download.go
@@ -13,32 +13,39 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package lib
+package commands
 
 import (
 	"context"
 
-	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/commands/cmderrors"
 	"github.com/arduino/arduino-cli/commands/internal/instances"
-	"github.com/arduino/arduino-cli/internal/arduino/httpclient"
 	"github.com/arduino/arduino-cli/internal/arduino/libraries/librariesindex"
-	"github.com/arduino/arduino-cli/internal/i18n"
+	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/arduino/go-paths-helper"
-	"github.com/sirupsen/logrus"
 )
 
-var tr = i18n.Tr
+// LibraryDownloadStreamResponseToCallbackFunction returns a gRPC stream to be used in LibraryDownload that sends
+// all responses to the callback function.
+func LibraryDownloadStreamResponseToCallbackFunction(ctx context.Context, downloadCB rpc.DownloadProgressCB) rpc.ArduinoCoreService_LibraryDownloadServer {
+	return streamResponseToCallback(ctx, func(r *rpc.LibraryDownloadResponse) error {
+		if r.GetProgress() != nil {
+			downloadCB(r.GetProgress())
+		}
+		return nil
+	})
+}
 
-// LibraryDownload executes the download of the library.
-// A DownloadProgressCB callback function must be passed to monitor download progress.
-func LibraryDownload(ctx context.Context, req *rpc.LibraryDownloadRequest, downloadCB rpc.DownloadProgressCB) (*rpc.LibraryDownloadResponse, error) {
-	logrus.Info("Executing `arduino-cli lib download`")
+// LibraryDownload downloads a library
+func (s *arduinoCoreServerImpl) LibraryDownload(req *rpc.LibraryDownloadRequest, stream rpc.ArduinoCoreService_LibraryDownloadServer) error {
+	syncSend := NewSynchronizedSend(stream.Send)
+	ctx := stream.Context()
+	downloadCB := func(p *rpc.DownloadProgress) { syncSend.Send(&rpc.LibraryDownloadResponse{Progress: p}) }
 
 	var downloadsDir *paths.Path
 	if pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance()); err != nil {
-		return nil, err
+		return err
 	} else {
 		downloadsDir = pme.DownloadDir
 		release()
@@ -46,36 +53,35 @@ func LibraryDownload(ctx context.Context, req *rpc.LibraryDownloadRequest, downl
 
 	li, err := instances.GetLibrariesIndex(req.GetInstance())
 	if err != nil {
-		return nil, err
+		return err
 	}
 
-	logrus.Info("Preparing download")
-
-	version, err := commands.ParseVersion(req.GetVersion())
+	version, err := parseVersion(req.GetVersion())
 	if err != nil {
-		return nil, err
+		return err
 	}
 
 	lib, err := li.FindRelease(req.GetName(), version)
 	if err != nil {
-		return nil, err
+		return err
 	}
 
-	if err := downloadLibrary(downloadsDir, lib, downloadCB, func(*rpc.TaskProgress) {}, "download"); err != nil {
-		return nil, err
+	if err := downloadLibrary(ctx, downloadsDir, lib, downloadCB, func(*rpc.TaskProgress) {}, "download", s.settings); err != nil {
+		return err
 	}
 
-	return &rpc.LibraryDownloadResponse{}, nil
+	return syncSend.Send(&rpc.LibraryDownloadResponse{})
 }
 
-func downloadLibrary(downloadsDir *paths.Path, libRelease *librariesindex.Release,
-	downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB, queryParameter string) error {
+func downloadLibrary(_ context.Context, downloadsDir *paths.Path, libRelease *librariesindex.Release,
+	downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB, queryParameter string, settings *configuration.Settings) error {
 
 	taskCB(&rpc.TaskProgress{Name: tr("Downloading %s", libRelease)})
-	config, err := httpclient.GetDownloaderConfig()
+	config, err := settings.DownloaderConfig()
 	if err != nil {
 		return &cmderrors.FailedDownloadError{Message: tr("Can't download library"), Cause: err}
 	}
+	// TODO: Pass context
 	if err := libRelease.Resource.Download(downloadsDir, config, libRelease.String(), downloadCB, queryParameter); err != nil {
 		return &cmderrors.FailedDownloadError{Message: tr("Can't download library"), Cause: err}
 	}
diff --git a/commands/lib/install.go b/commands/service_library_install.go
similarity index 66%
rename from commands/lib/install.go
rename to commands/service_library_install.go
index 873e442e4a0..20b98695de2 100644
--- a/commands/lib/install.go
+++ b/commands/service_library_install.go
@@ -13,14 +13,13 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package lib
+package commands
 
 import (
 	"context"
 	"errors"
 	"fmt"
 
-	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/commands/cmderrors"
 	"github.com/arduino/arduino-cli/commands/internal/instances"
 	"github.com/arduino/arduino-cli/internal/arduino/libraries"
@@ -31,8 +30,27 @@ import (
 	"github.com/sirupsen/logrus"
 )
 
+// LibraryInstallStreamResponseToCallbackFunction returns a gRPC stream to be used in LibraryInstall that sends
+// all responses to the callback function.
+func LibraryInstallStreamResponseToCallbackFunction(ctx context.Context, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) rpc.ArduinoCoreService_LibraryInstallServer {
+	return streamResponseToCallback(ctx, func(r *rpc.LibraryInstallResponse) error {
+		if r.GetProgress() != nil {
+			downloadCB(r.GetProgress())
+		}
+		if r.GetTaskProgress() != nil {
+			taskCB(r.GetTaskProgress())
+		}
+		return nil
+	})
+}
+
 // LibraryInstall resolves the library dependencies, then downloads and installs the libraries into the install location.
-func LibraryInstall(ctx context.Context, req *rpc.LibraryInstallRequest, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) error {
+func (s *arduinoCoreServerImpl) LibraryInstall(req *rpc.LibraryInstallRequest, stream rpc.ArduinoCoreService_LibraryInstallServer) error {
+	ctx := stream.Context()
+	syncSend := NewSynchronizedSend(stream.Send)
+	downloadCB := func(p *rpc.DownloadProgress) { syncSend.Send(&rpc.LibraryInstallResponse{Progress: p}) }
+	taskCB := func(p *rpc.TaskProgress) { syncSend.Send(&rpc.LibraryInstallResponse{TaskProgress: p}) }
+
 	// Obtain the library index from the manager
 	li, err := instances.GetLibrariesIndex(req.GetInstance())
 	if err != nil {
@@ -52,7 +70,7 @@ func LibraryInstall(ctx context.Context, req *rpc.LibraryInstallRequest, downloa
 			return err
 		}
 
-		res, err := libraryResolveDependencies(ctx, lme, li, req.GetName(), req.GetVersion(), req.GetNoOverwrite())
+		res, err := libraryResolveDependencies(lme, li, req.GetName(), req.GetVersion(), req.GetNoOverwrite())
 		releaseLme()
 		if err != nil {
 			return err
@@ -91,7 +109,7 @@ func LibraryInstall(ctx context.Context, req *rpc.LibraryInstallRequest, downloa
 	libReleasesToInstall := map[*librariesindex.Release]*librariesmanager.LibraryInstallPlan{}
 	installLocation := libraries.FromRPCLibraryInstallLocation(req.GetInstallLocation())
 	for _, lib := range toInstall {
-		version, err := commands.ParseVersion(lib.GetVersionRequired())
+		version, err := parseVersion(lib.GetVersionRequired())
 		if err != nil {
 			return err
 		}
@@ -129,7 +147,7 @@ func LibraryInstall(ctx context.Context, req *rpc.LibraryInstallRequest, downloa
 				downloadReason += "-builtin"
 			}
 		}
-		if err := downloadLibrary(downloadsDir, libRelease, downloadCB, taskCB, downloadReason); err != nil {
+		if err := downloadLibrary(ctx, downloadsDir, libRelease, downloadCB, taskCB, downloadReason, s.settings); err != nil {
 			return err
 		}
 		if err := installLibrary(lmi, downloadsDir, libRelease, installTask, taskCB); err != nil {
@@ -137,7 +155,10 @@ func LibraryInstall(ctx context.Context, req *rpc.LibraryInstallRequest, downloa
 		}
 	}
 
-	if err := commands.Init(&rpc.InitRequest{Instance: req.GetInstance()}, nil); err != nil {
+	err = s.Init(
+		&rpc.InitRequest{Instance: req.GetInstance()},
+		InitStreamResponseToCallbackFunction(ctx, nil))
+	if err != nil {
 		return err
 	}
 
@@ -166,8 +187,23 @@ func installLibrary(lmi *librariesmanager.Installer, downloadsDir *paths.Path, l
 	return nil
 }
 
+// ZipLibraryInstallStreamResponseToCallbackFunction returns a gRPC stream to be used in ZipLibraryInstall that sends
+// all responses to the callback function.
+func ZipLibraryInstallStreamResponseToCallbackFunction(ctx context.Context, taskCB rpc.TaskProgressCB) rpc.ArduinoCoreService_ZipLibraryInstallServer {
+	return streamResponseToCallback(ctx, func(r *rpc.ZipLibraryInstallResponse) error {
+		if r.GetTaskProgress() != nil {
+			taskCB(r.GetTaskProgress())
+		}
+		return nil
+	})
+}
+
 // ZipLibraryInstall FIXMEDOC
-func ZipLibraryInstall(ctx context.Context, req *rpc.ZipLibraryInstallRequest, taskCB rpc.TaskProgressCB) error {
+func (s *arduinoCoreServerImpl) ZipLibraryInstall(req *rpc.ZipLibraryInstallRequest, stream rpc.ArduinoCoreService_ZipLibraryInstallServer) error {
+	ctx := stream.Context()
+	syncSend := NewSynchronizedSend(stream.Send)
+	taskCB := func(p *rpc.TaskProgress) { syncSend.Send(&rpc.ZipLibraryInstallResponse{TaskProgress: p}) }
+
 	lm, err := instances.GetLibraryManager(req.GetInstance())
 	if err != nil {
 		return err
@@ -181,14 +217,30 @@ func ZipLibraryInstall(ctx context.Context, req *rpc.ZipLibraryInstallRequest, t
 	return nil
 }
 
+// GitLibraryInstallStreamResponseToCallbackFunction returns a gRPC stream to be used in GitLibraryInstall that sends
+// all responses to the callback function.
+func GitLibraryInstallStreamResponseToCallbackFunction(ctx context.Context, taskCB rpc.TaskProgressCB) rpc.ArduinoCoreService_GitLibraryInstallServer {
+	return streamResponseToCallback(ctx, func(r *rpc.GitLibraryInstallResponse) error {
+		if r.GetTaskProgress() != nil {
+			taskCB(r.GetTaskProgress())
+		}
+		return nil
+	})
+}
+
 // GitLibraryInstall FIXMEDOC
-func GitLibraryInstall(ctx context.Context, req *rpc.GitLibraryInstallRequest, taskCB rpc.TaskProgressCB) error {
+func (s *arduinoCoreServerImpl) GitLibraryInstall(req *rpc.GitLibraryInstallRequest, stream rpc.ArduinoCoreService_GitLibraryInstallServer) error {
+	syncSend := NewSynchronizedSend(stream.Send)
+	taskCB := func(p *rpc.TaskProgress) { syncSend.Send(&rpc.GitLibraryInstallResponse{TaskProgress: p}) }
 	lm, err := instances.GetLibraryManager(req.GetInstance())
 	if err != nil {
 		return err
 	}
 	lmi, release := lm.NewInstaller()
 	defer release()
+
+	// TODO: pass context
+	// ctx := stream.Context()
 	if err := lmi.InstallGitLib(req.GetUrl(), req.GetOverwrite()); err != nil {
 		return &cmderrors.FailedLibraryInstallError{Cause: err}
 	}
diff --git a/commands/lib/list.go b/commands/service_library_list.go
similarity index 96%
rename from commands/lib/list.go
rename to commands/service_library_list.go
index f5b28bdc426..be293c6161d 100644
--- a/commands/lib/list.go
+++ b/commands/service_library_list.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package lib
+package commands
 
 import (
 	"context"
@@ -35,7 +35,7 @@ type installedLib struct {
 }
 
 // LibraryList FIXMEDOC
-func LibraryList(ctx context.Context, req *rpc.LibraryListRequest) (*rpc.LibraryListResponse, error) {
+func (s *arduinoCoreServerImpl) LibraryList(ctx context.Context, req *rpc.LibraryListRequest) (*rpc.LibraryListResponse, error) {
 	pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
 	if err != nil {
 		return nil, err
diff --git a/commands/lib/resolve_deps.go b/commands/service_library_resolve_deps.go
similarity index 88%
rename from commands/lib/resolve_deps.go
rename to commands/service_library_resolve_deps.go
index b734e3d4ec7..c50cc2d9f6a 100644
--- a/commands/lib/resolve_deps.go
+++ b/commands/service_library_resolve_deps.go
@@ -13,14 +13,13 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package lib
+package commands
 
 import (
 	"context"
 	"errors"
 	"sort"
 
-	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/commands/cmderrors"
 	"github.com/arduino/arduino-cli/commands/internal/instances"
 	"github.com/arduino/arduino-cli/internal/arduino/libraries"
@@ -31,7 +30,7 @@ import (
 )
 
 // LibraryResolveDependencies FIXMEDOC
-func LibraryResolveDependencies(ctx context.Context, req *rpc.LibraryResolveDependenciesRequest) (*rpc.LibraryResolveDependenciesResponse, error) {
+func (s *arduinoCoreServerImpl) LibraryResolveDependencies(ctx context.Context, req *rpc.LibraryResolveDependenciesRequest) (*rpc.LibraryResolveDependenciesResponse, error) {
 	lme, release, err := instances.GetLibraryManagerExplorer(req.GetInstance())
 	if err != nil {
 		return nil, err
@@ -43,12 +42,12 @@ func LibraryResolveDependencies(ctx context.Context, req *rpc.LibraryResolveDepe
 		return nil, err
 	}
 
-	return libraryResolveDependencies(ctx, lme, li, req.GetName(), req.GetVersion(), req.GetDoNotUpdateInstalledLibraries())
+	return libraryResolveDependencies(lme, li, req.GetName(), req.GetVersion(), req.GetDoNotUpdateInstalledLibraries())
 }
 
-func libraryResolveDependencies(ctx context.Context, lme *librariesmanager.Explorer, li *librariesindex.Index,
+func libraryResolveDependencies(lme *librariesmanager.Explorer, li *librariesindex.Index,
 	reqName, reqVersion string, noOverwrite bool) (*rpc.LibraryResolveDependenciesResponse, error) {
-	version, err := commands.ParseVersion(reqVersion)
+	version, err := parseVersion(reqVersion)
 	if err != nil {
 		return nil, err
 	}
diff --git a/commands/lib/search.go b/commands/service_library_search.go
similarity index 96%
rename from commands/lib/search.go
rename to commands/service_library_search.go
index 7c2d21cdd70..39ff4c51c6e 100644
--- a/commands/lib/search.go
+++ b/commands/service_library_search.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package lib
+package commands
 
 import (
 	"context"
@@ -27,7 +27,7 @@ import (
 )
 
 // LibrarySearch FIXMEDOC
-func LibrarySearch(ctx context.Context, req *rpc.LibrarySearchRequest) (*rpc.LibrarySearchResponse, error) {
+func (s *arduinoCoreServerImpl) LibrarySearch(ctx context.Context, req *rpc.LibrarySearchRequest) (*rpc.LibrarySearchResponse, error) {
 	li, err := instances.GetLibrariesIndex(req.GetInstance())
 	if err != nil {
 		return nil, err
diff --git a/commands/lib/search_test.go b/commands/service_library_search_test.go
similarity index 95%
rename from commands/lib/search_test.go
rename to commands/service_library_search_test.go
index faae69ef1a9..84f625b511e 100644
--- a/commands/lib/search_test.go
+++ b/commands/service_library_search_test.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package lib
+package commands
 
 import (
 	"strings"
@@ -28,9 +28,9 @@ import (
 )
 
 var indexFilename, _ = globals.LibrariesIndexResource.IndexFileName()
-var customIndexPath = paths.New("testdata", "test1", indexFilename)
-var fullIndexPath = paths.New("testdata", "full", indexFilename)
-var qualifiedSearchIndexPath = paths.New("testdata", "qualified_search", indexFilename)
+var customIndexPath = paths.New("testdata", "libraries", "test1", indexFilename)
+var fullIndexPath = paths.New("testdata", "libraries", "full", indexFilename)
+var qualifiedSearchIndexPath = paths.New("testdata", "libraries", "qualified_search", indexFilename)
 
 func TestSearchLibrary(t *testing.T) {
 	li, err := librariesindex.LoadIndex(customIndexPath)
diff --git a/commands/lib/uninstall.go b/commands/service_library_uninstall.go
similarity index 66%
rename from commands/lib/uninstall.go
rename to commands/service_library_uninstall.go
index aee4ea68143..8164427f139 100644
--- a/commands/lib/uninstall.go
+++ b/commands/service_library_uninstall.go
@@ -13,12 +13,11 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package lib
+package commands
 
 import (
 	"context"
 
-	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/commands/cmderrors"
 	"github.com/arduino/arduino-cli/commands/internal/instances"
 	"github.com/arduino/arduino-cli/internal/arduino/libraries"
@@ -26,14 +25,29 @@ import (
 	"github.com/arduino/go-paths-helper"
 )
 
-// LibraryUninstall FIXMEDOC
-func LibraryUninstall(ctx context.Context, req *rpc.LibraryUninstallRequest, taskCB rpc.TaskProgressCB) error {
+// LibraryUninstallStreamResponseToCallbackFunction returns a gRPC stream to be used in LibraryUninstall that sends
+// all responses to the callback function.
+func LibraryUninstallStreamResponseToCallbackFunction(ctx context.Context, taskCB rpc.TaskProgressCB) rpc.ArduinoCoreService_LibraryUninstallServer {
+	return streamResponseToCallback(ctx, func(r *rpc.LibraryUninstallResponse) error {
+		if r.GetTaskProgress() != nil {
+			taskCB(r.GetTaskProgress())
+		}
+		return nil
+	})
+}
+
+// LibraryUninstall uninstalls a library
+func (s *arduinoCoreServerImpl) LibraryUninstall(req *rpc.LibraryUninstallRequest, stream rpc.ArduinoCoreService_LibraryUninstallServer) error {
+	// ctx := stream.Context()
+	syncSend := NewSynchronizedSend(stream.Send)
+	taskCB := func(p *rpc.TaskProgress) { syncSend.Send(&rpc.LibraryUninstallResponse{TaskProgress: p}) }
+
 	lm, err := instances.GetLibraryManager(req.GetInstance())
 	if err != nil {
 		return err
 	}
 
-	version, err := commands.ParseVersion(req.GetVersion())
+	version, err := parseVersion(req.GetVersion())
 	if err != nil {
 		return err
 	}
@@ -48,6 +62,7 @@ func LibraryUninstall(ctx context.Context, req *rpc.LibraryUninstallRequest, tas
 
 	if len(libs) == 1 {
 		taskCB(&rpc.TaskProgress{Name: tr("Uninstalling %s", libs)})
+		// TODO: pass context
 		lmi.Uninstall(libs[0])
 		taskCB(&rpc.TaskProgress{Completed: true})
 		return nil
diff --git a/commands/service_library_upgrade.go b/commands/service_library_upgrade.go
new file mode 100644
index 00000000000..ff8af393705
--- /dev/null
+++ b/commands/service_library_upgrade.go
@@ -0,0 +1,147 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package commands
+
+import (
+	"context"
+
+	"github.com/arduino/arduino-cli/commands/cmderrors"
+	"github.com/arduino/arduino-cli/commands/internal/instances"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
+)
+
+// LibraryUpgradeAllStreamResponseToCallbackFunction returns a gRPC stream to be used in LibraryUpgradeAll that sends
+// all responses to the callback function.
+func LibraryUpgradeAllStreamResponseToCallbackFunction(ctx context.Context, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) rpc.ArduinoCoreService_LibraryUpgradeAllServer {
+	return streamResponseToCallback(ctx, func(r *rpc.LibraryUpgradeAllResponse) error {
+		if r.GetProgress() != nil {
+			downloadCB(r.GetProgress())
+		}
+		if r.GetTaskProgress() != nil {
+			taskCB(r.GetTaskProgress())
+		}
+		return nil
+	})
+}
+
+// LibraryUpgradeAll upgrades all the available libraries
+func (s *arduinoCoreServerImpl) LibraryUpgradeAll(req *rpc.LibraryUpgradeAllRequest, stream rpc.ArduinoCoreService_LibraryUpgradeAllServer) error {
+	ctx := stream.Context()
+	syncSend := NewSynchronizedSend(stream.Send)
+	downloadCB := func(p *rpc.DownloadProgress) { syncSend.Send(&rpc.LibraryUpgradeAllResponse{Progress: p}) }
+	taskCB := func(p *rpc.TaskProgress) { syncSend.Send(&rpc.LibraryUpgradeAllResponse{TaskProgress: p}) }
+
+	li, err := instances.GetLibrariesIndex(req.GetInstance())
+	if err != nil {
+		return err
+	}
+
+	lme, release, err := instances.GetLibraryManagerExplorer(req.GetInstance())
+	if err != nil {
+		return err
+	}
+	libsToUpgrade := listLibraries(lme, li, true, false)
+	release()
+
+	if err := s.libraryUpgrade(ctx, req.GetInstance(), libsToUpgrade, downloadCB, taskCB); err != nil {
+		return err
+	}
+
+	err = s.Init(
+		&rpc.InitRequest{Instance: req.GetInstance()},
+		InitStreamResponseToCallbackFunction(ctx, nil))
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// LibraryUpgradeStreamResponseToCallbackFunction returns a gRPC stream to be used in LibraryUpgrade that sends
+// all responses to the callback function.
+func LibraryUpgradeStreamResponseToCallbackFunction(ctx context.Context, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) rpc.ArduinoCoreService_LibraryUpgradeServer {
+	return streamResponseToCallback(ctx, func(r *rpc.LibraryUpgradeResponse) error {
+		if r.GetProgress() != nil {
+			downloadCB(r.GetProgress())
+		}
+		if r.GetTaskProgress() != nil {
+			taskCB(r.GetTaskProgress())
+		}
+		return nil
+	})
+}
+
+// LibraryUpgrade upgrades a library
+func (s *arduinoCoreServerImpl) LibraryUpgrade(req *rpc.LibraryUpgradeRequest, stream rpc.ArduinoCoreService_LibraryUpgradeServer) error {
+	ctx := stream.Context()
+	syncSend := NewSynchronizedSend(stream.Send)
+	downloadCB := func(p *rpc.DownloadProgress) { syncSend.Send(&rpc.LibraryUpgradeResponse{Progress: p}) }
+	taskCB := func(p *rpc.TaskProgress) { syncSend.Send(&rpc.LibraryUpgradeResponse{TaskProgress: p}) }
+
+	li, err := instances.GetLibrariesIndex(req.GetInstance())
+	if err != nil {
+		return err
+	}
+
+	lme, release, err := instances.GetLibraryManagerExplorer(req.GetInstance())
+	if err != nil {
+		return err
+	}
+	libs := listLibraries(lme, li, false, false)
+	release()
+
+	// Get the library to upgrade
+	name := req.GetName()
+	lib := filterByName(libs, name)
+	if lib == nil {
+		// library not installed...
+		return &cmderrors.LibraryNotFoundError{Library: name}
+	}
+	if lib.Available == nil {
+		taskCB(&rpc.TaskProgress{Message: tr("Library %s is already at the latest version", name), Completed: true})
+		return nil
+	}
+
+	// Install update
+	return s.libraryUpgrade(ctx, req.GetInstance(), []*installedLib{lib}, downloadCB, taskCB)
+}
+
+func (s *arduinoCoreServerImpl) libraryUpgrade(ctx context.Context, instance *rpc.Instance, libs []*installedLib, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) error {
+	for _, lib := range libs {
+		libInstallReq := &rpc.LibraryInstallRequest{
+			Instance:    instance,
+			Name:        lib.Library.Name,
+			Version:     "",
+			NoDeps:      false,
+			NoOverwrite: false,
+		}
+		stream := LibraryInstallStreamResponseToCallbackFunction(ctx, downloadCB, taskCB)
+		if err := s.LibraryInstall(libInstallReq, stream); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func filterByName(libs []*installedLib, name string) *installedLib {
+	for _, lib := range libs {
+		if lib.Library.Name == name {
+			return lib
+		}
+	}
+	return nil
+}
diff --git a/commands/service_monitor.go b/commands/service_monitor.go
new file mode 100644
index 00000000000..1e9b0e7d1f5
--- /dev/null
+++ b/commands/service_monitor.go
@@ -0,0 +1,299 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package commands
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"sync/atomic"
+
+	"github.com/arduino/arduino-cli/commands/cmderrors"
+	"github.com/arduino/arduino-cli/commands/internal/instances"
+	"github.com/arduino/arduino-cli/internal/arduino/cores"
+	"github.com/arduino/arduino-cli/internal/arduino/cores/packagemanager"
+	pluggableMonitor "github.com/arduino/arduino-cli/internal/arduino/monitor"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
+	"github.com/arduino/go-properties-orderedmap"
+	"github.com/djherbis/buffer"
+	"github.com/djherbis/nio/v3"
+	"github.com/sirupsen/logrus"
+	"google.golang.org/grpc/metadata"
+)
+
+type monitorPipeServer struct {
+	ctx context.Context
+	req atomic.Pointer[rpc.MonitorPortOpenRequest]
+	in  *nio.PipeReader
+	out *nio.PipeWriter
+}
+
+func (s *monitorPipeServer) Send(resp *rpc.MonitorResponse) error {
+	if len(resp.GetRxData()) > 0 {
+		if _, err := s.out.Write(resp.GetRxData()); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (s *monitorPipeServer) Recv() (r *rpc.MonitorRequest, e error) {
+	if conf := s.req.Swap(nil); conf != nil {
+		return &rpc.MonitorRequest{Message: &rpc.MonitorRequest_OpenRequest{OpenRequest: conf}}, nil
+	}
+	buff := make([]byte, 4096)
+	n, err := s.in.Read(buff)
+	if err != nil {
+		return nil, err
+	}
+	return &rpc.MonitorRequest{Message: &rpc.MonitorRequest_TxData{TxData: buff[:n]}}, nil
+}
+
+func (s *monitorPipeServer) Context() context.Context {
+	return s.ctx
+}
+
+func (s *monitorPipeServer) RecvMsg(m any) error          { return nil }
+func (s *monitorPipeServer) SendHeader(metadata.MD) error { return nil }
+func (s *monitorPipeServer) SendMsg(m any) error          { return nil }
+func (s *monitorPipeServer) SetHeader(metadata.MD) error  { return nil }
+func (s *monitorPipeServer) SetTrailer(metadata.MD)       {}
+
+type monitorPipeClient struct {
+	in    *nio.PipeReader
+	out   *nio.PipeWriter
+	close func()
+}
+
+func (s *monitorPipeClient) Read(buff []byte) (n int, err error) {
+	return s.in.Read(buff)
+}
+
+func (s *monitorPipeClient) Write(buff []byte) (n int, err error) {
+	return s.out.Write(buff)
+}
+
+func (s *monitorPipeClient) Close() error {
+	s.in.Close()
+	s.out.Close()
+	s.close()
+	return nil
+}
+
+// MonitorServerToReadWriteCloser creates a monitor server that proxies the data to a ReadWriteCloser.
+// The server is returned along with the ReadWriteCloser that can be used to send and receive data
+// to the server. The MonitorPortOpenRequest is used to configure the monitor.
+func MonitorServerToReadWriteCloser(ctx context.Context, req *rpc.MonitorPortOpenRequest) (rpc.ArduinoCoreService_MonitorServer, io.ReadWriteCloser) {
+	server := &monitorPipeServer{}
+	client := &monitorPipeClient{}
+	server.req.Store(req)
+	server.ctx, client.close = context.WithCancel(ctx)
+	client.in, server.out = nio.Pipe(buffer.New(32 * 1024))
+	server.in, client.out = nio.Pipe(buffer.New(32 * 1024))
+	return server, client
+}
+
+// Monitor opens a port monitor and streams data back and forth until the request is kept alive.
+func (s *arduinoCoreServerImpl) Monitor(stream rpc.ArduinoCoreService_MonitorServer) error {
+	// The configuration must be sent on the first message
+	req, err := stream.Recv()
+	if err != nil {
+		return err
+	}
+
+	openReq := req.GetOpenRequest()
+	if openReq == nil {
+		return &cmderrors.InvalidInstanceError{}
+	}
+
+	pme, release, err := instances.GetPackageManagerExplorer(openReq.GetInstance())
+	if err != nil {
+		return err
+	}
+	defer release()
+	monitor, boardSettings, err := findMonitorAndSettingsForProtocolAndBoard(pme, openReq.GetPort().GetProtocol(), openReq.GetFqbn())
+	if err != nil {
+		return err
+	}
+	if err := monitor.Run(); err != nil {
+		return &cmderrors.FailedMonitorError{Cause: err}
+	}
+	if _, err := monitor.Describe(); err != nil {
+		monitor.Quit()
+		return &cmderrors.FailedMonitorError{Cause: err}
+	}
+	if portConfig := openReq.GetPortConfiguration(); portConfig != nil {
+		for _, setting := range portConfig.GetSettings() {
+			boardSettings.Remove(setting.GetSettingId())
+			if err := monitor.Configure(setting.GetSettingId(), setting.GetValue()); err != nil {
+				logrus.Errorf("Could not set configuration %s=%s: %s", setting.GetSettingId(), setting.GetValue(), err)
+			}
+		}
+	}
+	for setting, value := range boardSettings.AsMap() {
+		monitor.Configure(setting, value)
+	}
+	monitorIO, err := monitor.Open(openReq.GetPort().GetAddress(), openReq.GetPort().GetProtocol())
+	if err != nil {
+		monitor.Quit()
+		return &cmderrors.FailedMonitorError{Cause: err}
+	}
+	logrus.Infof("Port %s successfully opened", openReq.GetPort().GetAddress())
+	monitorClose := func() error {
+		monitor.Close()
+		return monitor.Quit()
+	}
+
+	// Send a message with Success set to true to notify the caller of the port being now active
+	syncSend := NewSynchronizedSend(stream.Send)
+	_ = syncSend.Send(&rpc.MonitorResponse{Success: true})
+
+	ctx, cancel := context.WithCancel(stream.Context())
+	gracefulCloseInitiated := &atomic.Bool{}
+	gracefuleCloseCtx, gracefulCloseCancel := context.WithCancel(context.Background())
+
+	// gRPC stream receiver (gRPC data -> monitor, config, close)
+	go func() {
+		defer cancel()
+		for {
+			msg, err := stream.Recv()
+			if errors.Is(err, io.EOF) {
+				return
+			}
+			if err != nil {
+				syncSend.Send(&rpc.MonitorResponse{Error: err.Error()})
+				return
+			}
+			if conf := msg.GetUpdatedConfiguration(); conf != nil {
+				for _, c := range conf.GetSettings() {
+					if err := monitor.Configure(c.GetSettingId(), c.GetValue()); err != nil {
+						syncSend.Send(&rpc.MonitorResponse{Error: err.Error()})
+					}
+				}
+			}
+			if closeMsg := msg.GetClose(); closeMsg {
+				gracefulCloseInitiated.Store(true)
+				if err := monitorClose(); err != nil {
+					logrus.WithError(err).Debug("Error closing monitor port")
+				}
+				gracefulCloseCancel()
+			}
+			tx := msg.GetTxData()
+			for len(tx) > 0 {
+				n, err := monitorIO.Write(tx)
+				if errors.Is(err, io.EOF) {
+					return
+				}
+				if err != nil {
+					syncSend.Send(&rpc.MonitorResponse{Error: err.Error()})
+					return
+				}
+				tx = tx[n:]
+			}
+		}
+	}()
+
+	// gRPC stream sender (monitor -> gRPC)
+	go func() {
+		defer cancel() // unlock the receiver
+		buff := make([]byte, 4096)
+		for {
+			n, err := monitorIO.Read(buff)
+			if errors.Is(err, io.EOF) {
+				break
+			}
+			if err != nil {
+				syncSend.Send(&rpc.MonitorResponse{Error: err.Error()})
+				break
+			}
+			if err := syncSend.Send(&rpc.MonitorResponse{RxData: buff[:n]}); err != nil {
+				break
+			}
+		}
+	}()
+
+	<-ctx.Done()
+	if gracefulCloseInitiated.Load() {
+		// Port closing has been initiated in the receiver
+		<-gracefuleCloseCtx.Done()
+	} else {
+		monitorClose()
+	}
+	return nil
+}
+
+func findMonitorAndSettingsForProtocolAndBoard(pme *packagemanager.Explorer, protocol, fqbn string) (*pluggableMonitor.PluggableMonitor, *properties.Map, error) {
+	if protocol == "" {
+		return nil, nil, &cmderrors.MissingPortProtocolError{}
+	}
+
+	var monitorDepOrRecipe *cores.MonitorDependency
+	boardSettings := properties.NewMap()
+
+	// If a board is specified search the monitor in the board package first
+	if fqbn != "" {
+		fqbn, err := cores.ParseFQBN(fqbn)
+		if err != nil {
+			return nil, nil, &cmderrors.InvalidFQBNError{Cause: err}
+		}
+
+		_, boardPlatform, _, boardProperties, _, err := pme.ResolveFQBN(fqbn)
+		if err != nil {
+			return nil, nil, &cmderrors.UnknownFQBNError{Cause: err}
+		}
+
+		boardSettings = cores.GetMonitorSettings(protocol, boardProperties)
+
+		if mon, ok := boardPlatform.Monitors[protocol]; ok {
+			monitorDepOrRecipe = mon
+		} else if recipe, ok := boardPlatform.MonitorsDevRecipes[protocol]; ok {
+			// If we have a recipe we must resolve it
+			cmdLine := boardProperties.ExpandPropsInString(recipe)
+			cmdArgs, err := properties.SplitQuotedString(cmdLine, `"'`, false)
+			if err != nil {
+				return nil, nil, &cmderrors.InvalidArgumentError{Message: tr("Invalid recipe in platform.txt"), Cause: err}
+			}
+			id := fmt.Sprintf("%s-%s", boardPlatform, protocol)
+			return pluggableMonitor.New(id, cmdArgs...), boardSettings, nil
+		}
+	}
+
+	if monitorDepOrRecipe == nil {
+		// Otherwise look in all package for a suitable monitor
+		for _, platformRel := range pme.InstalledPlatformReleases() {
+			if mon, ok := platformRel.Monitors[protocol]; ok {
+				monitorDepOrRecipe = mon
+				break
+			}
+		}
+	}
+
+	if monitorDepOrRecipe == nil {
+		return nil, nil, &cmderrors.NoMonitorAvailableForProtocolError{Protocol: protocol}
+	}
+
+	// If it is a monitor dependency, resolve tool and create a monitor client
+	tool := pme.FindMonitorDependency(monitorDepOrRecipe)
+	if tool == nil {
+		return nil, nil, &cmderrors.MonitorNotFoundError{Monitor: monitorDepOrRecipe.String()}
+	}
+
+	return pluggableMonitor.New(
+		monitorDepOrRecipe.Name,
+		tool.InstallDir.Join(monitorDepOrRecipe.Name).String(),
+	), boardSettings, nil
+}
diff --git a/commands/monitor/settings.go b/commands/service_monitor_settings.go
similarity index 92%
rename from commands/monitor/settings.go
rename to commands/service_monitor_settings.go
index f6d41c15219..4eb71c7add9 100644
--- a/commands/monitor/settings.go
+++ b/commands/service_monitor_settings.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package monitor
+package commands
 
 import (
 	"context"
@@ -25,7 +25,7 @@ import (
 )
 
 // EnumerateMonitorPortSettings returns a description of the configuration settings of a monitor port
-func EnumerateMonitorPortSettings(ctx context.Context, req *rpc.EnumerateMonitorPortSettingsRequest) (*rpc.EnumerateMonitorPortSettingsResponse, error) {
+func (s *arduinoCoreServerImpl) EnumerateMonitorPortSettings(ctx context.Context, req *rpc.EnumerateMonitorPortSettingsRequest) (*rpc.EnumerateMonitorPortSettingsResponse, error) {
 	pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
 	if err != nil {
 		return nil, err
diff --git a/commands/core/download.go b/commands/service_platform_download.go
similarity index 52%
rename from commands/core/download.go
rename to commands/service_platform_download.go
index 7c686f4aee9..09f49a2d76b 100644
--- a/commands/core/download.go
+++ b/commands/service_platform_download.go
@@ -13,12 +13,11 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package core
+package commands
 
 import (
 	"context"
 
-	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/commands/cmderrors"
 	"github.com/arduino/arduino-cli/commands/internal/instances"
 	"github.com/arduino/arduino-cli/internal/arduino/cores/packagemanager"
@@ -28,17 +27,30 @@ import (
 
 var tr = i18n.Tr
 
-// PlatformDownload FIXMEDOC
-func PlatformDownload(ctx context.Context, req *rpc.PlatformDownloadRequest, downloadCB rpc.DownloadProgressCB) (*rpc.PlatformDownloadResponse, error) {
+// PlatformDownloadStreamResponseToCallbackFunction returns a gRPC stream to be used in PlatformDownload that sends
+// all responses to the callback function.
+func PlatformDownloadStreamResponseToCallbackFunction(ctx context.Context, downloadCB rpc.DownloadProgressCB) rpc.ArduinoCoreService_PlatformDownloadServer {
+	return streamResponseToCallback(ctx, func(r *rpc.PlatformDownloadResponse) error {
+		if r.GetProgress() != nil {
+			downloadCB(r.GetProgress())
+		}
+		return nil
+	})
+}
+
+// PlatformDownload downloads a platform package
+func (s *arduinoCoreServerImpl) PlatformDownload(req *rpc.PlatformDownloadRequest, stream rpc.ArduinoCoreService_PlatformDownloadServer) error {
+	syncSend := NewSynchronizedSend(stream.Send)
+
 	pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
 	if err != nil {
-		return nil, err
+		return err
 	}
 	defer release()
 
-	version, err := commands.ParseVersion(req.GetVersion())
+	version, err := parseVersion(req.GetVersion())
 	if err != nil {
-		return nil, &cmderrors.InvalidVersionError{Cause: err}
+		return &cmderrors.InvalidVersionError{Cause: err}
 	}
 
 	ref := &packagemanager.PlatformReference{
@@ -48,18 +60,23 @@ func PlatformDownload(ctx context.Context, req *rpc.PlatformDownloadRequest, dow
 	}
 	platform, tools, err := pme.FindPlatformReleaseDependencies(ref)
 	if err != nil {
-		return nil, &cmderrors.PlatformNotFoundError{Platform: ref.String(), Cause: err}
+		return &cmderrors.PlatformNotFoundError{Platform: ref.String(), Cause: err}
 	}
 
-	if err := pme.DownloadPlatformRelease(platform, nil, downloadCB); err != nil {
-		return nil, err
+	downloadCB := func(p *rpc.DownloadProgress) { syncSend.Send(&rpc.PlatformDownloadResponse{Progress: p}) }
+
+	// TODO: pass context
+	// ctx := stream.Context()
+	if err := pme.DownloadPlatformRelease(platform, downloadCB); err != nil {
+		return err
 	}
 
 	for _, tool := range tools {
-		if err := pme.DownloadToolRelease(tool, nil, downloadCB); err != nil {
-			return nil, err
+		// TODO: pass context
+		if err := pme.DownloadToolRelease(tool, downloadCB); err != nil {
+			return err
 		}
 	}
 
-	return &rpc.PlatformDownloadResponse{}, nil
+	return syncSend.Send(&rpc.PlatformDownloadResponse{})
 }
diff --git a/commands/core/install.go b/commands/service_platform_install.go
similarity index 62%
rename from commands/core/install.go
rename to commands/service_platform_install.go
index a568a9dcfba..caa351a6ec6 100644
--- a/commands/core/install.go
+++ b/commands/service_platform_install.go
@@ -13,21 +13,39 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package core
+package commands
 
 import (
 	"context"
 	"fmt"
 
-	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/commands/cmderrors"
 	"github.com/arduino/arduino-cli/commands/internal/instances"
 	"github.com/arduino/arduino-cli/internal/arduino/cores/packagemanager"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 )
 
-// PlatformInstall FIXMEDOC
-func PlatformInstall(ctx context.Context, req *rpc.PlatformInstallRequest, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) (*rpc.PlatformInstallResponse, error) {
+// UpdateIndexStreamResponseToCallbackFunction returns a gRPC stream to be used in PlatformInstall that sends
+// all responses to the callback function.
+func PlatformInstallStreamResponseToCallbackFunction(ctx context.Context, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) rpc.ArduinoCoreService_PlatformInstallServer {
+	return streamResponseToCallback(ctx, func(r *rpc.PlatformInstallResponse) error {
+		if r.GetProgress() != nil {
+			downloadCB(r.GetProgress())
+		}
+		if r.GetTaskProgress() != nil {
+			taskCB(r.GetTaskProgress())
+		}
+		return nil
+	})
+}
+
+// PlatformInstall installs a platform package
+func (s *arduinoCoreServerImpl) PlatformInstall(req *rpc.PlatformInstallRequest, stream rpc.ArduinoCoreService_PlatformInstallServer) error {
+	ctx := stream.Context()
+	syncSend := NewSynchronizedSend(stream.Send)
+	taskCB := func(p *rpc.TaskProgress) { syncSend.Send(&rpc.PlatformInstallResponse{TaskProgress: p}) }
+	downloadCB := func(p *rpc.DownloadProgress) { syncSend.Send(&rpc.PlatformInstallResponse{Progress: p}) }
+
 	install := func() error {
 		pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
 		if err != nil {
@@ -35,7 +53,7 @@ func PlatformInstall(ctx context.Context, req *rpc.PlatformInstallRequest, downl
 		}
 		defer release()
 
-		version, err := commands.ParseVersion(req.GetVersion())
+		version, err := parseVersion(req.GetVersion())
 		if err != nil {
 			return &cmderrors.InvalidVersionError{Cause: err}
 		}
@@ -64,6 +82,7 @@ func PlatformInstall(ctx context.Context, req *rpc.PlatformInstallRequest, downl
 			}
 		}
 
+		// TODO: Pass context
 		if err := pme.DownloadAndInstallPlatformAndTools(platformRelease, tools, downloadCB, taskCB, req.GetSkipPostInstall(), req.GetSkipPreUninstall()); err != nil {
 			return err
 		}
@@ -72,10 +91,16 @@ func PlatformInstall(ctx context.Context, req *rpc.PlatformInstallRequest, downl
 	}
 
 	if err := install(); err != nil {
-		return nil, err
+		return err
 	}
-	if err := commands.Init(&rpc.InitRequest{Instance: req.GetInstance()}, nil); err != nil {
-		return nil, err
+
+	err := s.Init(
+		&rpc.InitRequest{Instance: req.GetInstance()},
+		InitStreamResponseToCallbackFunction(ctx, nil),
+	)
+	if err != nil {
+		return err
 	}
-	return &rpc.PlatformInstallResponse{}, nil
+
+	return syncSend.Send(&rpc.PlatformInstallResponse{})
 }
diff --git a/commands/core/search.go b/commands/service_platform_search.go
similarity index 92%
rename from commands/core/search.go
rename to commands/service_platform_search.go
index f68712c17e3..4117bf7be18 100644
--- a/commands/core/search.go
+++ b/commands/service_platform_search.go
@@ -13,14 +13,14 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package core
+package commands
 
 import (
+	"context"
 	"regexp"
 	"sort"
 	"strings"
 
-	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/commands/internal/instances"
 	"github.com/arduino/arduino-cli/internal/arduino/cores"
 	"github.com/arduino/arduino-cli/internal/arduino/utils"
@@ -28,7 +28,7 @@ import (
 )
 
 // PlatformSearch FIXMEDOC
-func PlatformSearch(req *rpc.PlatformSearchRequest) (*rpc.PlatformSearchResponse, error) {
+func (s *arduinoCoreServerImpl) PlatformSearch(_ context.Context, req *rpc.PlatformSearchRequest) (*rpc.PlatformSearchResponse, error) {
 	pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
 	if err != nil {
 		return nil, err
@@ -82,7 +82,7 @@ func PlatformSearch(req *rpc.PlatformSearchRequest) (*rpc.PlatformSearchResponse
 	out := []*rpc.PlatformSummary{}
 	for _, platform := range res {
 		rpcPlatformSummary := &rpc.PlatformSummary{
-			Metadata: commands.PlatformToRPCPlatformMetadata(platform),
+			Metadata: platformToRPCPlatformMetadata(platform),
 			Releases: map[string]*rpc.PlatformRelease{},
 		}
 		if installed := pme.GetInstalledPlatformRelease(platform); installed != nil {
@@ -92,7 +92,7 @@ func PlatformSearch(req *rpc.PlatformSearchRequest) (*rpc.PlatformSearchResponse
 			rpcPlatformSummary.LatestVersion = latestCompatible.Version.String()
 		}
 		for _, platformRelease := range platform.GetAllReleases() {
-			rpcPlatformRelease := commands.PlatformReleaseToRPC(platformRelease)
+			rpcPlatformRelease := platformReleaseToRPC(platformRelease)
 			rpcPlatformSummary.Releases[rpcPlatformRelease.GetVersion()] = rpcPlatformRelease
 		}
 		out = append(out, rpcPlatformSummary)
diff --git a/commands/core/search_test.go b/commands/service_platform_search_test.go
similarity index 87%
rename from commands/core/search_test.go
rename to commands/service_platform_search_test.go
index e67c07c7e95..0e98b28fe59 100644
--- a/commands/core/search_test.go
+++ b/commands/service_platform_search_test.go
@@ -13,13 +13,12 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package core
+package commands
 
 import (
+	"context"
 	"testing"
 
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
-	"github.com/arduino/arduino-cli/internal/cli/instance"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/arduino/go-paths-helper"
 	"github.com/stretchr/testify/require"
@@ -33,16 +32,26 @@ func TestPlatformSearch(t *testing.T) {
 	dataDir.MkdirAll()
 	downloadDir.MkdirAll()
 	defer paths.TempDir().Join("test").RemoveAll()
-	err := paths.New("testdata").Join("package_index.json").CopyTo(dataDir.Join("package_index.json"))
+	err := paths.New("testdata", "platform", "package_index.json").CopyTo(dataDir.Join("package_index.json"))
 	require.Nil(t, err)
 
-	configuration.Settings = configuration.Init(paths.TempDir().Join("test", "arduino-cli.yaml").String())
+	ctx := context.Background()
+	srv := NewArduinoCoreServer()
 
-	inst := instance.CreateAndInit()
+	_, err = srv.ConfigurationOpen(ctx, &rpc.ConfigurationOpenRequest{SettingsFormat: "yaml"})
+	require.NoError(t, err)
+
+	createResp, err := srv.Create(ctx, &rpc.CreateRequest{})
+	require.NoError(t, err)
+
+	inst := createResp.GetInstance()
 	require.NotNil(t, inst)
 
+	err = srv.Init(&rpc.InitRequest{Instance: inst}, InitStreamResponseToCallbackFunction(ctx, nil))
+	require.NoError(t, err)
+
 	t.Run("SearchAllVersions", func(t *testing.T) {
-		res, stat := PlatformSearch(&rpc.PlatformSearchRequest{
+		res, stat := srv.PlatformSearch(ctx, &rpc.PlatformSearchRequest{
 			Instance:   inst,
 			SearchArgs: "retrokit",
 		})
@@ -83,7 +92,7 @@ func TestPlatformSearch(t *testing.T) {
 	})
 
 	t.Run("SearchThePackageMaintainer", func(t *testing.T) {
-		res, stat := PlatformSearch(&rpc.PlatformSearchRequest{
+		res, stat := srv.PlatformSearch(ctx, &rpc.PlatformSearchRequest{
 			Instance:   inst,
 			SearchArgs: "Retrokits (www.retrokits.com)",
 		})
@@ -123,7 +132,7 @@ func TestPlatformSearch(t *testing.T) {
 	})
 
 	t.Run("SearchPackageName", func(t *testing.T) {
-		res, stat := PlatformSearch(&rpc.PlatformSearchRequest{
+		res, stat := srv.PlatformSearch(ctx, &rpc.PlatformSearchRequest{
 			Instance:   inst,
 			SearchArgs: "Retrokits-RK002",
 		})
@@ -163,7 +172,7 @@ func TestPlatformSearch(t *testing.T) {
 	})
 
 	t.Run("SearchPlatformName", func(t *testing.T) {
-		res, stat := PlatformSearch(&rpc.PlatformSearchRequest{
+		res, stat := srv.PlatformSearch(ctx, &rpc.PlatformSearchRequest{
 			Instance:   inst,
 			SearchArgs: "rk002",
 		})
@@ -203,7 +212,7 @@ func TestPlatformSearch(t *testing.T) {
 	})
 
 	t.Run("SearchBoardName", func(t *testing.T) {
-		res, stat := PlatformSearch(&rpc.PlatformSearchRequest{
+		res, stat := srv.PlatformSearch(ctx, &rpc.PlatformSearchRequest{
 			Instance:   inst,
 			SearchArgs: "Yún",
 		})
@@ -261,7 +270,7 @@ func TestPlatformSearch(t *testing.T) {
 	})
 
 	t.Run("SearchBoardName2", func(t *testing.T) {
-		res, stat := PlatformSearch(&rpc.PlatformSearchRequest{
+		res, stat := srv.PlatformSearch(ctx, &rpc.PlatformSearchRequest{
 			Instance:   inst,
 			SearchArgs: "yun",
 		})
@@ -327,15 +336,23 @@ func TestPlatformSearchSorting(t *testing.T) {
 	dataDir.MkdirAll()
 	downloadDir.MkdirAll()
 	defer paths.TempDir().Join("test").RemoveAll()
-	err := paths.New("testdata").Join("package_index.json").CopyTo(dataDir.Join("package_index.json"))
+	err := paths.New("testdata", "platform", "package_index.json").CopyTo(dataDir.Join("package_index.json"))
 	require.Nil(t, err)
 
-	configuration.Settings = configuration.Init(paths.TempDir().Join("test", "arduino-cli.yaml").String())
+	ctx := context.Background()
+	srv := NewArduinoCoreServer()
+
+	_, err = srv.ConfigurationOpen(ctx, &rpc.ConfigurationOpenRequest{SettingsFormat: "yaml"})
+	require.NoError(t, err)
 
-	inst := instance.CreateAndInit()
+	createResp, err := srv.Create(ctx, &rpc.CreateRequest{})
+	require.NoError(t, err)
+	inst := createResp.GetInstance()
 	require.NotNil(t, inst)
+	err = srv.Init(&rpc.InitRequest{Instance: inst}, InitStreamResponseToCallbackFunction(ctx, nil))
+	require.NoError(t, err)
 
-	res, stat := PlatformSearch(&rpc.PlatformSearchRequest{
+	res, stat := srv.PlatformSearch(ctx, &rpc.PlatformSearchRequest{
 		Instance:   inst,
 		SearchArgs: "",
 	})
diff --git a/commands/core/uninstall.go b/commands/service_platform_uninstall.go
similarity index 66%
rename from commands/core/uninstall.go
rename to commands/service_platform_uninstall.go
index d1c826ad210..07a32988a6a 100644
--- a/commands/core/uninstall.go
+++ b/commands/service_platform_uninstall.go
@@ -13,31 +13,44 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package core
+package commands
 
 import (
 	"context"
 
-	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/commands/cmderrors"
 	"github.com/arduino/arduino-cli/commands/internal/instances"
 	"github.com/arduino/arduino-cli/internal/arduino/cores/packagemanager"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 )
 
-// PlatformUninstall FIXMEDOC
-func PlatformUninstall(ctx context.Context, req *rpc.PlatformUninstallRequest, taskCB rpc.TaskProgressCB) (*rpc.PlatformUninstallResponse, error) {
+// PlatformUninstallStreamResponseToCallbackFunction returns a gRPC stream to be used in PlatformUninstall that sends
+// all responses to the callback function.
+func PlatformUninstallStreamResponseToCallbackFunction(ctx context.Context, taskCB rpc.TaskProgressCB) rpc.ArduinoCoreService_PlatformUninstallServer {
+	return streamResponseToCallback(ctx, func(r *rpc.PlatformUninstallResponse) error {
+		if r.GetTaskProgress() != nil {
+			taskCB(r.GetTaskProgress())
+		}
+		return nil
+	})
+}
+
+// PlatformUninstall uninstalls a platform package
+func (s *arduinoCoreServerImpl) PlatformUninstall(req *rpc.PlatformUninstallRequest, stream rpc.ArduinoCoreService_PlatformUninstallServer) error {
+	syncSend := NewSynchronizedSend(stream.Send)
+	ctx := stream.Context()
+	taskCB := func(p *rpc.TaskProgress) { syncSend.Send(&rpc.PlatformUninstallResponse{TaskProgress: p}) }
 	if err := platformUninstall(ctx, req, taskCB); err != nil {
-		return nil, err
+		return err
 	}
-	if err := commands.Init(&rpc.InitRequest{Instance: req.GetInstance()}, nil); err != nil {
-		return nil, err
+	if err := s.Init(&rpc.InitRequest{Instance: req.GetInstance()}, InitStreamResponseToCallbackFunction(ctx, nil)); err != nil {
+		return err
 	}
-	return &rpc.PlatformUninstallResponse{}, nil
+	return syncSend.Send(&rpc.PlatformUninstallResponse{})
 }
 
 // platformUninstall is the implementation of platform unistaller
-func platformUninstall(ctx context.Context, req *rpc.PlatformUninstallRequest, taskCB rpc.TaskProgressCB) error {
+func platformUninstall(_ context.Context, req *rpc.PlatformUninstallRequest, taskCB rpc.TaskProgressCB) error {
 	pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
 	if err != nil {
 		return &cmderrors.InvalidInstanceError{}
@@ -65,6 +78,7 @@ func platformUninstall(ctx context.Context, req *rpc.PlatformUninstallRequest, t
 		return &cmderrors.NotFoundError{Message: tr("Can't find dependencies for platform %s", ref), Cause: err}
 	}
 
+	// TODO: pass context
 	if err := pme.UninstallPlatform(platform, taskCB, req.GetSkipPreUninstall()); err != nil {
 		return err
 	}
diff --git a/commands/core/upgrade.go b/commands/service_platform_upgrade.go
similarity index 51%
rename from commands/core/upgrade.go
rename to commands/service_platform_upgrade.go
index 57ba8de045e..2737eac4f37 100644
--- a/commands/core/upgrade.go
+++ b/commands/service_platform_upgrade.go
@@ -13,20 +13,45 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package core
+package commands
 
 import (
 	"context"
 
-	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/commands/internal/instances"
 	"github.com/arduino/arduino-cli/internal/arduino/cores"
 	"github.com/arduino/arduino-cli/internal/arduino/cores/packagemanager"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 )
 
-// PlatformUpgrade FIXMEDOC
-func PlatformUpgrade(ctx context.Context, req *rpc.PlatformUpgradeRequest, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) (*rpc.PlatformUpgradeResponse, error) {
+// PlatformUpgradeStreamResponseToCallbackFunction returns a gRPC stream to be used in PlatformUpgrade that sends
+// all responses to the callback function.
+func PlatformUpgradeStreamResponseToCallbackFunction(ctx context.Context, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) (rpc.ArduinoCoreService_PlatformUpgradeServer, func() *rpc.Platform) {
+	var resp *rpc.Platform
+	return streamResponseToCallback(ctx, func(r *rpc.PlatformUpgradeResponse) error {
+			// TODO: use oneof in protoc files?
+			if r.GetProgress() != nil {
+				downloadCB(r.GetProgress())
+			}
+			if r.GetTaskProgress() != nil {
+				taskCB(r.GetTaskProgress())
+			}
+			if r.GetPlatform() != nil {
+				resp = r.GetPlatform()
+			}
+			return nil
+		}), func() *rpc.Platform {
+			return resp
+		}
+}
+
+// PlatformUpgrade upgrades a platform package
+func (s *arduinoCoreServerImpl) PlatformUpgrade(req *rpc.PlatformUpgradeRequest, stream rpc.ArduinoCoreService_PlatformUpgradeServer) error {
+	syncSend := NewSynchronizedSend(stream.Send)
+	ctx := stream.Context()
+	downloadCB := func(p *rpc.DownloadProgress) { syncSend.Send(&rpc.PlatformUpgradeResponse{Progress: p}) }
+	taskCB := func(p *rpc.TaskProgress) { syncSend.Send(&rpc.PlatformUpgradeResponse{TaskProgress: p}) }
+
 	upgrade := func() (*cores.PlatformRelease, error) {
 		pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
 		if err != nil {
@@ -47,20 +72,22 @@ func PlatformUpgrade(ctx context.Context, req *rpc.PlatformUpgradeRequest, downl
 		return platform, nil
 	}
 
-	var rpcPlatform *rpc.Platform
 	platformRelease, err := upgrade()
 	if platformRelease != nil {
-		rpcPlatform = &rpc.Platform{
-			Metadata: commands.PlatformToRPCPlatformMetadata(platformRelease.Platform),
-			Release:  commands.PlatformReleaseToRPC(platformRelease),
-		}
+		syncSend.Send(&rpc.PlatformUpgradeResponse{
+			Platform: &rpc.Platform{
+				Metadata: platformToRPCPlatformMetadata(platformRelease.Platform),
+				Release:  platformReleaseToRPC(platformRelease),
+			},
+		})
 	}
 	if err != nil {
-		return &rpc.PlatformUpgradeResponse{Platform: rpcPlatform}, err
+		return err
 	}
-	if err := commands.Init(&rpc.InitRequest{Instance: req.GetInstance()}, nil); err != nil {
-		return nil, err
+
+	if err := s.Init(&rpc.InitRequest{Instance: req.GetInstance()}, InitStreamResponseToCallbackFunction(ctx, nil)); err != nil {
+		return err
 	}
 
-	return &rpc.PlatformUpgradeResponse{Platform: rpcPlatform}, nil
+	return nil
 }
diff --git a/commands/sketch/set_defaults.go b/commands/service_set_sketch_defaults.go
similarity index 93%
rename from commands/sketch/set_defaults.go
rename to commands/service_set_sketch_defaults.go
index c53bc1b1ae5..6b3ba044899 100644
--- a/commands/sketch/set_defaults.go
+++ b/commands/service_set_sketch_defaults.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package sketch
+package commands
 
 import (
 	"context"
@@ -26,7 +26,7 @@ import (
 
 // SetSketchDefaults updates the sketch project file (sketch.yaml) with the given defaults
 // for the values `default_fqbn`, `default_port`, and `default_protocol`.
-func SetSketchDefaults(ctx context.Context, req *rpc.SetSketchDefaultsRequest) (*rpc.SetSketchDefaultsResponse, error) {
+func (s *arduinoCoreServerImpl) SetSketchDefaults(ctx context.Context, req *rpc.SetSketchDefaultsRequest) (*rpc.SetSketchDefaultsResponse, error) {
 	sk, err := sketch.New(paths.New(req.GetSketchPath()))
 	if err != nil {
 		return nil, &cmderrors.CantOpenSketchError{Cause: err}
diff --git a/commands/service_settings.go b/commands/service_settings.go
new file mode 100644
index 00000000000..d38f4a95cfd
--- /dev/null
+++ b/commands/service_settings.go
@@ -0,0 +1,229 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package commands
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"reflect"
+
+	"github.com/arduino/arduino-cli/commands/cmderrors"
+	f "github.com/arduino/arduino-cli/internal/algorithms"
+	"github.com/arduino/arduino-cli/internal/cli/configuration"
+	"github.com/arduino/arduino-cli/internal/go-configmap"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
+	"google.golang.org/protobuf/proto"
+	"gopkg.in/yaml.v3"
+)
+
+// SettingsGetValue returns a settings value given its key. If the key is not present
+// an error will be returned, so that we distinguish empty settings from missing
+// ones.
+func (s *arduinoCoreServerImpl) ConfigurationGet(ctx context.Context, req *rpc.ConfigurationGetRequest) (*rpc.ConfigurationGetResponse, error) {
+	conf := &rpc.Configuration{
+		Directories: &rpc.Configuration_Directories{
+			Builtin:   &rpc.Configuration_Directories_Builtin{},
+			Data:      s.settings.DataDir().String(),
+			Downloads: s.settings.DownloadsDir().String(),
+			User:      s.settings.UserDir().String(),
+		},
+		Network: &rpc.Configuration_Network{},
+		Sketch: &rpc.Configuration_Sketch{
+			AlwaysExportBinaries: s.settings.SketchAlwaysExportBinaries(),
+		},
+		BuildCache: &rpc.Configuration_BuildCache{
+			CompilationsBeforePurge: uint64(s.settings.GetCompilationsBeforeBuildCachePurge()),
+			TtlSecs:                 uint64(s.settings.GetBuildCacheTTL().Seconds()),
+		},
+		BoardManager: &rpc.Configuration_BoardManager{
+			AdditionalUrls: s.settings.BoardManagerAdditionalUrls(),
+		},
+		Daemon: &rpc.Configuration_Daemon{
+			Port: s.settings.DaemonPort(),
+		},
+		Output: &rpc.Configuration_Output{
+			NoColor: s.settings.NoColor(),
+		},
+		Logging: &rpc.Configuration_Logging{
+			Level:  s.settings.LoggingLevel(),
+			Format: s.settings.LoggingFormat(),
+		},
+		Library: &rpc.Configuration_Library{
+			EnableUnsafeInstall: s.settings.LibraryEnableUnsafeInstall(),
+		},
+		Updater: &rpc.Configuration_Updater{
+			EnableNotification: s.settings.UpdaterEnableNotification(),
+		},
+	}
+
+	if builtinLibs := s.settings.IDEBuiltinLibrariesDir(); builtinLibs != nil {
+		conf.Directories.Builtin.Libraries = proto.String(builtinLibs.String())
+	}
+
+	if ua := s.settings.ExtraUserAgent(); ua != "" {
+		conf.Network.ExtraUserAgent = &ua
+	}
+	if proxy, err := s.settings.NetworkProxy(); err == nil && proxy != nil {
+		conf.Network.Proxy = proto.String(proxy.String())
+	}
+
+	if logFile := s.settings.LoggingFile(); logFile != nil {
+		file := logFile.String()
+		conf.Logging.File = &file
+	}
+
+	if locale := s.settings.Locale(); locale != "" {
+		conf.Locale = &locale
+	}
+
+	return &rpc.ConfigurationGetResponse{Configuration: conf}, nil
+}
+
+func (s *arduinoCoreServerImpl) SettingsSetValue(ctx context.Context, req *rpc.SettingsSetValueRequest) (*rpc.SettingsSetValueResponse, error) {
+	// Determine the existence and the kind of the value
+	key := req.GetKey()
+
+	// Extract the value from the request
+	encodedValue := []byte(req.GetEncodedValue())
+	if len(encodedValue) == 0 {
+		// If the value is empty, unset the key
+		s.settings.Delete(key)
+		return &rpc.SettingsSetValueResponse{}, nil
+	}
+
+	var newValue any
+	switch req.GetValueFormat() {
+	case "", "json":
+		if err := json.Unmarshal(encodedValue, &newValue); err != nil {
+			return nil, &cmderrors.InvalidArgumentError{Message: fmt.Sprintf("invalid value: %v", err)}
+		}
+	case "yaml":
+		if err := yaml.Unmarshal(encodedValue, &newValue); err != nil {
+			return nil, &cmderrors.InvalidArgumentError{Message: fmt.Sprintf("invalid value: %v", err)}
+		}
+	case "cli":
+		err := s.settings.SetFromCLIArgs(key, req.GetEncodedValue())
+		if err != nil {
+			return nil, err
+		}
+		return &rpc.SettingsSetValueResponse{}, nil
+	default:
+		return nil, &cmderrors.InvalidArgumentError{Message: fmt.Sprintf("unsupported value format: %s", req.ValueFormat)}
+	}
+
+	// If the value is "null", unset the key
+	if reflect.TypeOf(newValue) == reflect.TypeOf(nil) {
+		s.settings.Delete(key)
+		return &rpc.SettingsSetValueResponse{}, nil
+	}
+
+	// Set the value
+	if err := s.settings.Set(key, newValue); err != nil {
+		return nil, err
+	}
+
+	return &rpc.SettingsSetValueResponse{}, nil
+}
+
+func (s *arduinoCoreServerImpl) SettingsGetValue(ctx context.Context, req *rpc.SettingsGetValueRequest) (*rpc.SettingsGetValueResponse, error) {
+	key := req.GetKey()
+	value, ok := s.settings.GetOk(key)
+	if !ok {
+		value, ok = s.settings.Defaults.GetOk(key)
+	}
+	if !ok {
+		return nil, &cmderrors.InvalidArgumentError{Message: fmt.Sprintf("key %s not found", key)}
+	}
+
+	switch req.GetValueFormat() {
+	case "", "json":
+		valueJson, err := json.Marshal(value)
+		if err != nil {
+			return nil, fmt.Errorf("error marshalling value: %v", err)
+		}
+		return &rpc.SettingsGetValueResponse{EncodedValue: string(valueJson)}, nil
+	case "yaml":
+		valueYaml, err := yaml.Marshal(value)
+		if err != nil {
+			return nil, fmt.Errorf("error marshalling value: %v", err)
+		}
+		return &rpc.SettingsGetValueResponse{EncodedValue: string(valueYaml)}, nil
+	default:
+		return nil, &cmderrors.InvalidArgumentError{Message: fmt.Sprintf("unsupported value format: %s", req.ValueFormat)}
+	}
+}
+
+// ConfigurationSave encodes the current configuration in the specified format
+func (s *arduinoCoreServerImpl) ConfigurationSave(ctx context.Context, req *rpc.ConfigurationSaveRequest) (*rpc.ConfigurationSaveResponse, error) {
+	switch req.GetSettingsFormat() {
+	case "yaml":
+		data, err := yaml.Marshal(s.settings)
+		if err != nil {
+			return nil, fmt.Errorf("error marshalling settings: %v", err)
+		}
+		return &rpc.ConfigurationSaveResponse{EncodedSettings: string(data)}, nil
+	case "json":
+		data, err := json.MarshalIndent(s.settings, "", "  ")
+		if err != nil {
+			return nil, fmt.Errorf("error marshalling settings: %v", err)
+		}
+		return &rpc.ConfigurationSaveResponse{EncodedSettings: string(data)}, nil
+	default:
+		return nil, &cmderrors.InvalidArgumentError{Message: fmt.Sprintf("unsupported format: %s", req.GetSettingsFormat())}
+	}
+}
+
+// SettingsReadFromFile read settings from a YAML file and replace the settings currently stored in memory.
+func (s *arduinoCoreServerImpl) ConfigurationOpen(ctx context.Context, req *rpc.ConfigurationOpenRequest) (*rpc.ConfigurationOpenResponse, error) {
+	warnings := []string{}
+
+	switch req.GetSettingsFormat() {
+	case "yaml":
+		err := yaml.Unmarshal([]byte(req.GetEncodedSettings()), s.settings)
+		if errs, ok := err.(*configmap.UnmarshalErrors); ok {
+			warnings = f.Map(errs.WrappedErrors(), (error).Error)
+		} else if err != nil {
+			return nil, fmt.Errorf("error unmarshalling settings: %v", err)
+		}
+	case "json":
+		err := json.Unmarshal([]byte(req.GetEncodedSettings()), s.settings)
+		if errs, ok := err.(*configmap.UnmarshalErrors); ok {
+			warnings = f.Map(errs.WrappedErrors(), (error).Error)
+		} else if err != nil {
+			return nil, fmt.Errorf("error unmarshalling settings: %v", err)
+		}
+	default:
+		return nil, &cmderrors.InvalidArgumentError{Message: fmt.Sprintf("unsupported format: %s", req.GetSettingsFormat())}
+	}
+
+	configuration.InjectEnvVars(s.settings)
+	return &rpc.ConfigurationOpenResponse{Warnings: warnings}, nil
+}
+
+// SettingsEnumerate returns the list of all the settings keys.
+func (s *arduinoCoreServerImpl) SettingsEnumerate(ctx context.Context, req *rpc.SettingsEnumerateRequest) (*rpc.SettingsEnumerateResponse, error) {
+	var entries []*rpc.SettingsEnumerateResponse_Entry
+	for k, t := range s.settings.Defaults.Schema() {
+		entries = append(entries, &rpc.SettingsEnumerateResponse_Entry{
+			Key:  k,
+			Type: t.String(),
+		})
+	}
+	return &rpc.SettingsEnumerateResponse{
+		Entries: entries,
+	}, nil
+}
diff --git a/commands/service_settings_test.go b/commands/service_settings_test.go
new file mode 100644
index 00000000000..1c77856a336
--- /dev/null
+++ b/commands/service_settings_test.go
@@ -0,0 +1,236 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package commands
+
+import (
+	"context"
+	"encoding/json"
+	"testing"
+
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
+	"github.com/arduino/go-paths-helper"
+	"github.com/stretchr/testify/require"
+)
+
+func loadConfig(t *testing.T, srv rpc.ArduinoCoreServiceServer, confPath *paths.Path) {
+	confPath.ToAbs()
+	conf, err := confPath.ReadFile()
+	require.NoError(t, err)
+	_, err = srv.ConfigurationOpen(context.Background(), &rpc.ConfigurationOpenRequest{
+		EncodedSettings: string(conf),
+		SettingsFormat:  "yaml",
+	})
+	require.NoError(t, err)
+}
+
+func TestGetAll(t *testing.T) {
+	srv := NewArduinoCoreServer()
+	loadConfig(t, srv, paths.New("testdata", "arduino-cli.yml"))
+	resp, err := srv.ConfigurationGet(context.Background(), &rpc.ConfigurationGetRequest{})
+	require.Nil(t, err)
+
+	defaultUserDir, err := srv.SettingsGetValue(context.Background(), &rpc.SettingsGetValueRequest{Key: "directories.user"})
+	require.NoError(t, err)
+
+	content, err := json.Marshal(resp.GetConfiguration())
+	require.Nil(t, err)
+	require.JSONEq(t, `{
+		"board_manager": {
+			"additional_urls": [ "http://foobar.com", "http://example.com" ]
+		},
+		"build_cache": {
+			"compilations_before_purge": 10,
+			"ttl_secs": 2592000
+		},
+		"directories": {
+			"builtin": {},
+			"data": "/home/massi/.arduino15",
+			"downloads": "/home/massi/.arduino15/staging",
+			"user": `+defaultUserDir.GetEncodedValue()+`
+		},
+		"library": {},
+		"locale": "en",
+		"logging": {
+			"format": "text",
+			"level": "info"
+		},
+		"daemon":{
+			"port":"50051"
+		},
+		"network":{
+			"proxy":"123"
+		},
+		"output": {},
+		"sketch": {},
+		"updater": {}
+	}`, string(content))
+}
+
+func TestMerge(t *testing.T) {
+	srv := NewArduinoCoreServer()
+	loadConfig(t, srv, paths.New("testdata", "arduino-cli.yml"))
+
+	ctx := context.Background()
+
+	get := func(key string) string {
+		resp, err := srv.SettingsGetValue(ctx, &rpc.SettingsGetValueRequest{Key: key})
+		if err != nil {
+			return "<error>"
+		}
+		return resp.GetEncodedValue()
+	}
+
+	// Verify defaults
+	require.Equal(t, `"50051"`, get("daemon.port"))
+	require.Equal(t, "<error>", get("foo"))
+	require.Equal(t, "false", get("sketch.always_export_binaries"))
+
+	bulkSettings := `{"foo": "bar", "daemon":{"port":"420"}, "sketch": {"always_export_binaries": "true"}}`
+	_, err := srv.ConfigurationOpen(ctx, &rpc.ConfigurationOpenRequest{EncodedSettings: bulkSettings, SettingsFormat: "json"})
+	require.Error(t, err)
+
+	bulkSettings = `{"daemon":{"port":"420"}, "sketch": {"always_export_binaries": "true"}}`
+	_, err = srv.ConfigurationOpen(ctx, &rpc.ConfigurationOpenRequest{EncodedSettings: bulkSettings, SettingsFormat: "json"})
+	require.Error(t, err)
+
+	bulkSettings = `{"daemon":{"port":"420"}, "sketch": {"always_export_binaries": true}}`
+	_, err = srv.ConfigurationOpen(ctx, &rpc.ConfigurationOpenRequest{EncodedSettings: bulkSettings, SettingsFormat: "json"})
+	require.NoError(t, err)
+
+	require.Equal(t, `"420"`, get("daemon.port"))
+	require.Equal(t, `<error>`, get("foo"))
+	require.Equal(t, "true", get("sketch.always_export_binaries"))
+
+	bulkSettings = `{"daemon": {}, "sketch": {"always_export_binaries": false}}`
+	_, err = srv.ConfigurationOpen(ctx, &rpc.ConfigurationOpenRequest{EncodedSettings: bulkSettings, SettingsFormat: "json"})
+	require.NoError(t, err)
+
+	require.Equal(t, `"50051"`, get("daemon.port"))
+	require.Equal(t, "<error>", get("foo"))
+	require.Equal(t, "false", get("sketch.always_export_binaries"))
+
+	_, err = srv.SettingsSetValue(ctx, &rpc.SettingsSetValueRequest{Key: "daemon.port", EncodedValue: ""})
+	require.NoError(t, err)
+
+	require.Equal(t, `"50051"`, get("daemon.port"))
+	// Verifies other values are not changed
+	require.Equal(t, "<error>", get("foo"))
+	require.Equal(t, "false", get("sketch.always_export_binaries"))
+
+}
+
+func TestGetValue(t *testing.T) {
+	srv := NewArduinoCoreServer()
+	loadConfig(t, srv, paths.New("testdata", "arduino-cli.yml"))
+
+	key := &rpc.SettingsGetValueRequest{Key: "daemon"}
+	resp, err := srv.SettingsGetValue(context.Background(), key)
+	require.NoError(t, err)
+	require.Equal(t, `{"port":"50051"}`, resp.GetEncodedValue())
+
+	key = &rpc.SettingsGetValueRequest{Key: "daemon.port"}
+	resp, err = srv.SettingsGetValue(context.Background(), key)
+	require.NoError(t, err)
+	require.Equal(t, `"50051"`, resp.GetEncodedValue())
+}
+
+func TestGetOfSettedValue(t *testing.T) {
+	srv := NewArduinoCoreServer()
+	loadConfig(t, srv, paths.New("testdata", "arduino-cli.yml"))
+
+	// Verifies value is not set (try with a key without a default, like "directories.builtin.libraries")
+	ctx := context.Background()
+	res, err := srv.SettingsGetValue(ctx, &rpc.SettingsGetValueRequest{Key: "directories.builtin.libraries"})
+	require.Nil(t, res)
+	require.Error(t, err, "Error getting settings value")
+
+	// Set value
+	_, err = srv.SettingsSetValue(ctx, &rpc.SettingsSetValueRequest{
+		Key:          "directories.builtin.libraries",
+		EncodedValue: `"bar"`})
+	require.NoError(t, err)
+
+	// Verifies value is correctly returned
+	res, err = srv.SettingsGetValue(ctx, &rpc.SettingsGetValueRequest{Key: "directories.builtin.libraries"})
+	require.NoError(t, err)
+	require.Equal(t, `"bar"`, res.GetEncodedValue())
+}
+
+func TestGetValueNotFound(t *testing.T) {
+	srv := NewArduinoCoreServer()
+	loadConfig(t, srv, paths.New("testdata", "arduino-cli.yml"))
+
+	key := &rpc.SettingsGetValueRequest{Key: "DOESNTEXIST"}
+	_, err := srv.SettingsGetValue(context.Background(), key)
+	require.Error(t, err)
+}
+
+func TestWrite(t *testing.T) {
+	srv := NewArduinoCoreServer()
+	loadConfig(t, srv, paths.New("testdata", "arduino-cli.yml"))
+
+	// Writes some settings
+	val := &rpc.SettingsSetValueRequest{
+		Key:          "directories.builtin.libraries",
+		EncodedValue: `"bar"`,
+	}
+	_, err := srv.SettingsSetValue(context.Background(), val)
+	require.NoError(t, err)
+
+	resp, err := srv.ConfigurationSave(context.Background(), &rpc.ConfigurationSaveRequest{
+		SettingsFormat: "yaml",
+	})
+	require.NoError(t, err)
+
+	// Verify encoded content
+	require.YAMLEq(t, `
+board_manager:
+  additional_urls:
+    - http://foobar.com
+    - http://example.com
+
+daemon:
+  port: "50051"
+
+directories:
+  data: /home/massi/.arduino15
+  downloads: /home/massi/.arduino15/staging
+  builtin:
+    libraries: bar
+
+logging:
+  file: ""
+  format: text
+  level: info
+
+network:
+  proxy: "123"
+`, resp.GetEncodedSettings())
+}
+
+func TestDelete(t *testing.T) {
+	srv := NewArduinoCoreServer()
+	loadConfig(t, srv, paths.New("testdata", "arduino-cli.yml"))
+
+	_, err := srv.SettingsGetValue(context.Background(), &rpc.SettingsGetValueRequest{Key: "network"})
+	require.NoError(t, err)
+
+	_, err = srv.SettingsSetValue(context.Background(), &rpc.SettingsSetValueRequest{Key: "network", EncodedValue: ""})
+	require.NoError(t, err)
+
+	_, err = srv.SettingsGetValue(context.Background(), &rpc.SettingsGetValueRequest{Key: "network"})
+	require.Error(t, err)
+}
diff --git a/commands/sketch/archive.go b/commands/service_sketch_archive.go
similarity index 93%
rename from commands/sketch/archive.go
rename to commands/service_sketch_archive.go
index 8f843dbca6b..67515406382 100644
--- a/commands/sketch/archive.go
+++ b/commands/service_sketch_archive.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package sketch
+package commands
 
 import (
 	"archive/zip"
@@ -24,15 +24,12 @@ import (
 
 	"github.com/arduino/arduino-cli/commands/cmderrors"
 	"github.com/arduino/arduino-cli/internal/arduino/sketch"
-	"github.com/arduino/arduino-cli/internal/i18n"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	paths "github.com/arduino/go-paths-helper"
 )
 
-var tr = i18n.Tr
-
 // ArchiveSketch FIXMEDOC
-func ArchiveSketch(ctx context.Context, req *rpc.ArchiveSketchRequest) (*rpc.ArchiveSketchResponse, error) {
+func (s *arduinoCoreServerImpl) ArchiveSketch(ctx context.Context, req *rpc.ArchiveSketchRequest) (*rpc.ArchiveSketchResponse, error) {
 	// sketchName is the name of the sketch without extension, for example "MySketch"
 	var sketchName string
 
@@ -41,13 +38,13 @@ func ArchiveSketch(ctx context.Context, req *rpc.ArchiveSketchRequest) (*rpc.Arc
 		sketchPath = paths.New(".")
 	}
 
-	s, err := sketch.New(sketchPath)
+	sk, err := sketch.New(sketchPath)
 	if err != nil {
 		return nil, &cmderrors.CantOpenSketchError{Cause: err}
 	}
 
-	sketchPath = s.FullPath
-	sketchName = s.Name
+	sketchPath = sk.FullPath
+	sketchName = sk.Name
 
 	archivePath := paths.New(req.GetArchivePath())
 	if archivePath == nil {
diff --git a/commands/sketch/load.go b/commands/service_sketch_load.go
similarity index 85%
rename from commands/sketch/load.go
rename to commands/service_sketch_load.go
index 5dadb0113c4..8360ce7adfd 100644
--- a/commands/sketch/load.go
+++ b/commands/service_sketch_load.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package sketch
+package commands
 
 import (
 	"context"
@@ -25,10 +25,10 @@ import (
 )
 
 // LoadSketch collects and returns all information about a sketch
-func LoadSketch(ctx context.Context, req *rpc.LoadSketchRequest) (*rpc.Sketch, error) {
+func (s *arduinoCoreServerImpl) LoadSketch(ctx context.Context, req *rpc.LoadSketchRequest) (*rpc.LoadSketchResponse, error) {
 	sk, err := sketch.New(paths.New(req.GetSketchPath()))
 	if err != nil {
 		return nil, &cmderrors.CantOpenSketchError{Cause: err}
 	}
-	return sk.ToRpc(), nil
+	return &rpc.LoadSketchResponse{Sketch: sk.ToRpc()}, nil
 }
diff --git a/commands/sketch/load_test.go b/commands/service_sketch_load_test.go
similarity index 78%
rename from commands/sketch/load_test.go
rename to commands/service_sketch_load_test.go
index bfae0e6a959..d7dfcdcf8fa 100644
--- a/commands/sketch/load_test.go
+++ b/commands/service_sketch_load_test.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package sketch
+package commands
 
 import (
 	"context"
@@ -24,10 +24,11 @@ import (
 )
 
 func TestLoadSketchProfiles(t *testing.T) {
-	loadResp, err := LoadSketch(context.Background(), &commands.LoadSketchRequest{
+	srv := NewArduinoCoreServer()
+	loadResp, err := srv.LoadSketch(context.Background(), &commands.LoadSketchRequest{
 		SketchPath: "./testdata/sketch_with_profile",
 	})
 	require.NoError(t, err)
-	require.Len(t, loadResp.GetProfiles(), 2)
-	require.Equal(t, loadResp.GetDefaultProfile().GetName(), "nanorp")
+	require.Len(t, loadResp.GetSketch().GetProfiles(), 2)
+	require.Equal(t, loadResp.GetSketch().GetDefaultProfile().GetName(), "nanorp")
 }
diff --git a/commands/sketch/new.go b/commands/service_sketch_new.go
similarity index 93%
rename from commands/sketch/new.go
rename to commands/service_sketch_new.go
index 5a7f6003e29..b0c9c079f73 100644
--- a/commands/sketch/new.go
+++ b/commands/service_sketch_new.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package sketch
+package commands
 
 import (
 	"context"
@@ -22,7 +22,6 @@ import (
 
 	"github.com/arduino/arduino-cli/commands/cmderrors"
 	"github.com/arduino/arduino-cli/internal/arduino/globals"
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	paths "github.com/arduino/go-paths-helper"
 )
@@ -43,12 +42,12 @@ var invalidNames = []string{"CON", "PRN", "AUX", "NUL", "COM0", "COM1", "COM2",
 	"COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}
 
 // NewSketch creates a new sketch via gRPC
-func NewSketch(ctx context.Context, req *rpc.NewSketchRequest) (*rpc.NewSketchResponse, error) {
+func (s *arduinoCoreServerImpl) NewSketch(ctx context.Context, req *rpc.NewSketchRequest) (*rpc.NewSketchResponse, error) {
 	var sketchesDir string
 	if len(req.GetSketchDir()) > 0 {
 		sketchesDir = req.GetSketchDir()
 	} else {
-		sketchesDir = configuration.Settings.GetString("directories.User")
+		sketchesDir = s.settings.GetString("directories.User")
 	}
 
 	if err := validateSketchName(req.GetSketchName()); err != nil {
diff --git a/commands/sketch/new_test.go b/commands/service_sketch_new_test.go
similarity index 82%
rename from commands/sketch/new_test.go
rename to commands/service_sketch_new_test.go
index 00ae7856732..8dfb5a8168e 100644
--- a/commands/sketch/new_test.go
+++ b/commands/service_sketch_new_test.go
@@ -13,14 +13,14 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package sketch
+package commands
 
 import (
 	"context"
 	"fmt"
 	"testing"
 
-	"github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/stretchr/testify/require"
 )
 
@@ -34,8 +34,10 @@ func Test_SketchNameWrongPattern(t *testing.T) {
 		"||||||||||||||",
 		",`hack[}attempt{];",
 	}
+
+	srv := NewArduinoCoreServer()
 	for _, name := range invalidNames {
-		_, err := NewSketch(context.Background(), &commands.NewSketchRequest{
+		_, err := srv.NewSketch(context.Background(), &rpc.NewSketchRequest{
 			SketchName: name,
 			SketchDir:  t.TempDir(),
 		})
@@ -46,9 +48,9 @@ func Test_SketchNameWrongPattern(t *testing.T) {
 }
 
 func Test_SketchNameEmpty(t *testing.T) {
-	emptyName := ""
-	_, err := NewSketch(context.Background(), &commands.NewSketchRequest{
-		SketchName: emptyName,
+	srv := NewArduinoCoreServer()
+	_, err := srv.NewSketch(context.Background(), &rpc.NewSketchRequest{
+		SketchName: "",
 		SketchDir:  t.TempDir(),
 	})
 
@@ -60,7 +62,8 @@ func Test_SketchNameTooLong(t *testing.T) {
 	for i := range tooLongName {
 		tooLongName[i] = 'a'
 	}
-	_, err := NewSketch(context.Background(), &commands.NewSketchRequest{
+	srv := NewArduinoCoreServer()
+	_, err := srv.NewSketch(context.Background(), &rpc.NewSketchRequest{
 		SketchName: string(tooLongName),
 		SketchDir:  t.TempDir(),
 	})
@@ -83,8 +86,9 @@ func Test_SketchNameOk(t *testing.T) {
 		"_hello_world",
 		string(lengthLimitName),
 	}
+	srv := NewArduinoCoreServer()
 	for _, name := range validNames {
-		_, err := NewSketch(context.Background(), &commands.NewSketchRequest{
+		_, err := srv.NewSketch(context.Background(), &rpc.NewSketchRequest{
 			SketchName: name,
 			SketchDir:  t.TempDir(),
 		})
@@ -95,8 +99,9 @@ func Test_SketchNameOk(t *testing.T) {
 func Test_SketchNameReserved(t *testing.T) {
 	invalidNames := []string{"CON", "PRN", "AUX", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5",
 		"COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}
+	srv := NewArduinoCoreServer()
 	for _, name := range invalidNames {
-		_, err := NewSketch(context.Background(), &commands.NewSketchRequest{
+		_, err := srv.NewSketch(context.Background(), &rpc.NewSketchRequest{
 			SketchName: name,
 			SketchDir:  t.TempDir(),
 		})
diff --git a/commands/daemon/stream.go b/commands/service_stream_utility.go
similarity index 99%
rename from commands/daemon/stream.go
rename to commands/service_stream_utility.go
index aa5e70fb770..4b285c443fc 100644
--- a/commands/daemon/stream.go
+++ b/commands/service_stream_utility.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package daemon
+package commands
 
 import (
 	"errors"
diff --git a/commands/upload/upload.go b/commands/service_upload.go
similarity index 90%
rename from commands/upload/upload.go
rename to commands/service_upload.go
index fcc248ce3b9..6dcacf1d3e5 100644
--- a/commands/upload/upload.go
+++ b/commands/service_upload.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package upload
+package commands
 
 import (
 	"context"
@@ -31,7 +31,6 @@ import (
 	"github.com/arduino/arduino-cli/internal/arduino/cores/packagemanager"
 	"github.com/arduino/arduino-cli/internal/arduino/globals"
 	"github.com/arduino/arduino-cli/internal/arduino/sketch"
-	"github.com/arduino/arduino-cli/internal/i18n"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	paths "github.com/arduino/go-paths-helper"
 	properties "github.com/arduino/go-properties-orderedmap"
@@ -40,11 +39,9 @@ import (
 	"github.com/sirupsen/logrus"
 )
 
-var tr = i18n.Tr
-
 // SupportedUserFields returns a SupportedUserFieldsResponse containing all the UserFields supported
 // by the upload tools needed by the board using the protocol specified in SupportedUserFieldsRequest.
-func SupportedUserFields(ctx context.Context, req *rpc.SupportedUserFieldsRequest) (*rpc.SupportedUserFieldsResponse, error) {
+func (s *arduinoCoreServerImpl) SupportedUserFields(ctx context.Context, req *rpc.SupportedUserFieldsRequest) (*rpc.SupportedUserFieldsResponse, error) {
 	if req.GetProtocol() == "" {
 		return nil, &cmderrors.MissingPortProtocolError{}
 	}
@@ -123,8 +120,33 @@ func getUserFields(toolID string, platformRelease *cores.PlatformRelease) []*rpc
 	return userFields
 }
 
-// Upload FIXMEDOC
-func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, errStream io.Writer) (*rpc.UploadResult, error) {
+// UploadToServerStreams return a server stream that forwards the output and error streams to the provided writers.
+// It also returns a function that can be used to retrieve the result of the upload.
+func UploadToServerStreams(ctx context.Context, outStream io.Writer, errStream io.Writer) (rpc.ArduinoCoreService_UploadServer, func() *rpc.UploadResult) {
+	var result *rpc.UploadResult
+	stream := streamResponseToCallback(ctx, func(resp *rpc.UploadResponse) error {
+		if errData := resp.GetErrStream(); len(errData) > 0 {
+			_, err := errStream.Write(errData)
+			return err
+		}
+		if outData := resp.GetOutStream(); len(outData) > 0 {
+			_, err := outStream.Write(outData)
+			return err
+		}
+		if res := resp.GetResult(); res != nil {
+			result = res
+		}
+		return nil
+	})
+	return stream, func() *rpc.UploadResult {
+		return result
+	}
+}
+
+// Upload performs the upload of a sketch to a board.
+func (s *arduinoCoreServerImpl) Upload(req *rpc.UploadRequest, stream rpc.ArduinoCoreService_UploadServer) error {
+	syncSend := NewSynchronizedSend(stream.Send)
+
 	logrus.Tracef("Upload %s on %s started", req.GetSketchPath(), req.GetFqbn())
 
 	// TODO: make a generic function to extract sketch from request
@@ -132,12 +154,12 @@ func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, er
 	sketchPath := paths.New(req.GetSketchPath())
 	sk, err := sketch.New(sketchPath)
 	if err != nil && req.GetImportDir() == "" && req.GetImportFile() == "" {
-		return nil, &cmderrors.CantOpenSketchError{Cause: err}
+		return &cmderrors.CantOpenSketchError{Cause: err}
 	}
 
 	pme, pmeRelease, err := instances.GetPackageManagerExplorer(req.GetInstance())
 	if err != nil {
-		return nil, err
+		return err
 	}
 	defer pmeRelease()
 
@@ -154,7 +176,20 @@ func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, er
 		programmer = sk.GetDefaultProgrammer()
 	}
 
+	outStream := feedStreamTo(func(data []byte) {
+		syncSend.Send(&rpc.UploadResponse{
+			Message: &rpc.UploadResponse_OutStream{OutStream: data},
+		})
+	})
+	defer outStream.Close()
+	errStream := feedStreamTo(func(data []byte) {
+		syncSend.Send(&rpc.UploadResponse{
+			Message: &rpc.UploadResponse_ErrStream{ErrStream: data},
+		})
+	})
+	defer errStream.Close()
 	updatedPort, err := runProgramAction(
+		stream.Context(),
 		pme,
 		sk,
 		req.GetImportFile(),
@@ -171,22 +206,45 @@ func Upload(ctx context.Context, req *rpc.UploadRequest, outStream io.Writer, er
 		req.GetUserFields(),
 	)
 	if err != nil {
-		return nil, err
+		return err
 	}
-
-	return &rpc.UploadResult{
-		UpdatedUploadPort: updatedPort,
-	}, nil
+	return syncSend.Send(&rpc.UploadResponse{
+		Message: &rpc.UploadResponse_Result{
+			Result: &rpc.UploadResult{
+				UpdatedUploadPort: updatedPort,
+			},
+		},
+	})
 }
 
-// UsingProgrammer FIXMEDOC
-func UsingProgrammer(ctx context.Context, req *rpc.UploadUsingProgrammerRequest, outStream io.Writer, errStream io.Writer) error {
+// UploadUsingProgrammer FIXMEDOC
+func (s *arduinoCoreServerImpl) UploadUsingProgrammer(req *rpc.UploadUsingProgrammerRequest, stream rpc.ArduinoCoreService_UploadUsingProgrammerServer) error {
+	syncSend := NewSynchronizedSend(stream.Send)
+	streamAdapter := streamResponseToCallback(stream.Context(), func(resp *rpc.UploadResponse) error {
+		if errData := resp.GetErrStream(); len(errData) > 0 {
+			syncSend.Send(&rpc.UploadUsingProgrammerResponse{
+				Message: &rpc.UploadUsingProgrammerResponse_ErrStream{
+					ErrStream: errData,
+				},
+			})
+		}
+		if outData := resp.GetOutStream(); len(outData) > 0 {
+			syncSend.Send(&rpc.UploadUsingProgrammerResponse{
+				Message: &rpc.UploadUsingProgrammerResponse_OutStream{
+					OutStream: outData,
+				},
+			})
+		}
+		// resp.GetResult() is ignored
+		return nil
+	})
+
 	logrus.Tracef("Upload using programmer %s on %s started", req.GetSketchPath(), req.GetFqbn())
 
 	if req.GetProgrammer() == "" {
 		return &cmderrors.MissingProgrammerError{}
 	}
-	_, err := Upload(ctx, &rpc.UploadRequest{
+	return s.Upload(&rpc.UploadRequest{
 		Instance:   req.GetInstance(),
 		SketchPath: req.GetSketchPath(),
 		ImportFile: req.GetImportFile(),
@@ -197,11 +255,10 @@ func UsingProgrammer(ctx context.Context, req *rpc.UploadUsingProgrammerRequest,
 		Verbose:    req.GetVerbose(),
 		Verify:     req.GetVerify(),
 		UserFields: req.GetUserFields(),
-	}, outStream, errStream)
-	return err
+	}, streamAdapter)
 }
 
-func runProgramAction(pme *packagemanager.Explorer,
+func runProgramAction(ctx context.Context, pme *packagemanager.Explorer,
 	sk *sketch.Sketch,
 	importFile, importDir, fqbnIn string, userPort *rpc.Port,
 	programmerID string,
@@ -374,7 +431,7 @@ func runProgramAction(pme *packagemanager.Explorer,
 	}
 
 	if !burnBootloader {
-		importPath, sketchName, err := determineBuildPathAndSketchName(importFile, importDir, sk, fqbn)
+		importPath, sketchName, err := determineBuildPathAndSketchName(importFile, importDir, sk)
 		if err != nil {
 			return nil, &cmderrors.NotFoundError{Message: tr("Error finding build artifacts"), Cause: err}
 		}
@@ -389,7 +446,7 @@ func runProgramAction(pme *packagemanager.Explorer,
 	}
 
 	// This context is kept alive for the entire duration of the upload
-	uploadCtx, uploadCompleted := context.WithCancel(context.Background())
+	uploadCtx, uploadCompleted := context.WithCancel(ctx)
 	defer uploadCompleted()
 
 	// Start the upload port change detector.
@@ -677,7 +734,7 @@ func runTool(recipeID string, props *properties.Map, outStream, errStream io.Wri
 	return nil
 }
 
-func determineBuildPathAndSketchName(importFile, importDir string, sk *sketch.Sketch, fqbn *cores.FQBN) (*paths.Path, string, error) {
+func determineBuildPathAndSketchName(importFile, importDir string, sk *sketch.Sketch) (*paths.Path, string, error) {
 	// In general, compiling a sketch will produce a set of files that are
 	// named as the sketch but have different extensions, for example Sketch.ino
 	// may produce: Sketch.ino.bin; Sketch.ino.hex; Sketch.ino.zip; etc...
diff --git a/commands/upload/burnbootloader.go b/commands/service_upload_burnbootloader.go
similarity index 52%
rename from commands/upload/burnbootloader.go
rename to commands/service_upload_burnbootloader.go
index b589e573698..448618b164c 100644
--- a/commands/upload/burnbootloader.go
+++ b/commands/service_upload_burnbootloader.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package upload
+package commands
 
 import (
 	"context"
@@ -24,8 +24,42 @@ import (
 	"github.com/sirupsen/logrus"
 )
 
-// BurnBootloader FIXMEDOC
-func BurnBootloader(ctx context.Context, req *rpc.BurnBootloaderRequest, outStream io.Writer, errStream io.Writer) (*rpc.BurnBootloaderResponse, error) {
+// BurnBootloaderToServerStreams return a server stream that forwards the output and error streams to the provided io.Writers
+func BurnBootloaderToServerStreams(ctx context.Context, outStrem, errStream io.Writer) rpc.ArduinoCoreService_BurnBootloaderServer {
+	stream := streamResponseToCallback(ctx, func(resp *rpc.BurnBootloaderResponse) error {
+		if outData := resp.GetOutStream(); len(outData) > 0 {
+			_, err := outStrem.Write(outData)
+			return err
+		}
+		if errData := resp.GetErrStream(); len(errData) > 0 {
+			_, err := errStream.Write(errData)
+			return err
+		}
+		return nil
+	})
+	return stream
+}
+
+// BurnBootloader performs the burn bootloader action
+func (s *arduinoCoreServerImpl) BurnBootloader(req *rpc.BurnBootloaderRequest, stream rpc.ArduinoCoreService_BurnBootloaderServer) error {
+	syncSend := NewSynchronizedSend(stream.Send)
+	outStream := feedStreamTo(func(data []byte) {
+		syncSend.Send(&rpc.BurnBootloaderResponse{
+			Message: &rpc.BurnBootloaderResponse_OutStream{
+				OutStream: data,
+			},
+		})
+	})
+	defer outStream.Close()
+	errStream := feedStreamTo(func(data []byte) {
+		syncSend.Send(&rpc.BurnBootloaderResponse{
+			Message: &rpc.BurnBootloaderResponse_ErrStream{
+				ErrStream: data,
+			},
+		})
+	})
+	defer errStream.Close()
+
 	logrus.
 		WithField("fqbn", req.GetFqbn()).
 		WithField("port", req.GetPort()).
@@ -34,11 +68,12 @@ func BurnBootloader(ctx context.Context, req *rpc.BurnBootloaderRequest, outStre
 
 	pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
 	if err != nil {
-		return nil, err
+		return err
 	}
 	defer release()
 
 	if _, err := runProgramAction(
+		stream.Context(),
 		pme,
 		nil, // sketch
 		"",  // importFile
@@ -54,7 +89,7 @@ func BurnBootloader(ctx context.Context, req *rpc.BurnBootloaderRequest, outStre
 		req.GetDryRun(),
 		map[string]string{}, // User fields
 	); err != nil {
-		return nil, err
+		return err
 	}
-	return &rpc.BurnBootloaderResponse{}, nil
+	return syncSend.Send(&rpc.BurnBootloaderResponse{})
 }
diff --git a/commands/upload/programmers_list.go b/commands/service_upload_list_programmers.go
similarity index 90%
rename from commands/upload/programmers_list.go
rename to commands/service_upload_list_programmers.go
index 76188ac5e2e..f05142cf147 100644
--- a/commands/upload/programmers_list.go
+++ b/commands/service_upload_list_programmers.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package upload
+package commands
 
 import (
 	"context"
@@ -25,7 +25,7 @@ import (
 )
 
 // ListProgrammersAvailableForUpload FIXMEDOC
-func ListProgrammersAvailableForUpload(ctx context.Context, req *rpc.ListProgrammersAvailableForUploadRequest) (*rpc.ListProgrammersAvailableForUploadResponse, error) {
+func (s *arduinoCoreServerImpl) ListProgrammersAvailableForUpload(ctx context.Context, req *rpc.ListProgrammersAvailableForUploadRequest) (*rpc.ListProgrammersAvailableForUploadResponse, error) {
 	pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
 	if err != nil {
 		return nil, err
diff --git a/commands/upload/upload_test.go b/commands/service_upload_test.go
similarity index 82%
rename from commands/upload/upload_test.go
rename to commands/service_upload_test.go
index 95d26de6634..d350b5a6470 100644
--- a/commands/upload/upload_test.go
+++ b/commands/service_upload_test.go
@@ -13,10 +13,11 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package upload
+package commands
 
 import (
 	"bytes"
+	"context"
 	"fmt"
 	"strings"
 	"testing"
@@ -29,26 +30,27 @@ import (
 	properties "github.com/arduino/go-properties-orderedmap"
 	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
+	"go.bug.st/downloader/v2"
 )
 
 func TestDetectSketchNameFromBuildPath(t *testing.T) {
-	sk1, err1 := detectSketchNameFromBuildPath(paths.New("testdata/build_path_1"))
+	sk1, err1 := detectSketchNameFromBuildPath(paths.New("testdata/upload/build_path_1"))
 	require.NoError(t, err1)
 	require.Equal(t, "sketch.ino", sk1)
 
-	sk2, err2 := detectSketchNameFromBuildPath(paths.New("testdata/build_path_2"))
+	sk2, err2 := detectSketchNameFromBuildPath(paths.New("testdata/upload/build_path_2"))
 	require.NoError(t, err2)
 	require.Equal(t, "Blink.ino", sk2)
 
-	sk3, err3 := detectSketchNameFromBuildPath(paths.New("testdata/build_path_3"))
+	sk3, err3 := detectSketchNameFromBuildPath(paths.New("testdata/upload/build_path_3"))
 	require.Error(t, err3)
 	require.Equal(t, "", sk3)
 
-	sk4, err4 := detectSketchNameFromBuildPath(paths.New("testdata/build_path_4"))
+	sk4, err4 := detectSketchNameFromBuildPath(paths.New("testdata/upload/build_path_4"))
 	require.Error(t, err4)
 	require.Equal(t, "", sk4)
 
-	sk5, err5 := detectSketchNameFromBuildPath(paths.New("testdata/build_path_invalid"))
+	sk5, err5 := detectSketchNameFromBuildPath(paths.New("testdata/upload/build_path_invalid"))
 	require.Error(t, err5)
 	require.Equal(t, "", sk5)
 }
@@ -63,7 +65,7 @@ func TestDetermineBuildPathAndSketchName(t *testing.T) {
 		resSketchName string
 	}
 
-	blonk, err := sketch.New(paths.New("testdata/Blonk"))
+	blonk, err := sketch.New(paths.New("testdata/upload/Blonk"))
 	require.NoError(t, err)
 
 	fqbn, err := cores.ParseFQBN("arduino:samd:mkr1000")
@@ -73,43 +75,43 @@ func TestDetermineBuildPathAndSketchName(t *testing.T) {
 		// 00: error: no data passed in
 		{"", "", nil, nil, "<nil>", ""},
 		// 01: use importFile to detect build.path and project_name
-		{"testdata/build_path_2/Blink.ino.hex", "", nil, nil, "testdata/build_path_2", "Blink.ino"},
+		{"testdata/upload/build_path_2/Blink.ino.hex", "", nil, nil, "testdata/upload/build_path_2", "Blink.ino"},
 		// 02: use importPath as build.path and project_name
-		{"", "testdata/build_path_2", nil, nil, "testdata/build_path_2", "Blink.ino"},
+		{"", "testdata/upload/build_path_2", nil, nil, "testdata/upload/build_path_2", "Blink.ino"},
 		// 03: error: used both importPath and importFile
-		{"testdata/build_path_2/Blink.ino.hex", "testdata/build_path_2", nil, nil, "<nil>", ""},
+		{"testdata/upload/build_path_2/Blink.ino.hex", "testdata/upload/build_path_2", nil, nil, "<nil>", ""},
 		// 04: only sketch without FQBN
 		{"", "", blonk, nil, blonk.DefaultBuildPath().String(), "Blonk.ino"},
 		// 05: use importFile to detect build.path and project_name, sketch is ignored.
-		{"testdata/build_path_2/Blink.ino.hex", "", blonk, nil, "testdata/build_path_2", "Blink.ino"},
+		{"testdata/upload/build_path_2/Blink.ino.hex", "", blonk, nil, "testdata/upload/build_path_2", "Blink.ino"},
 		// 06: use importPath as build.path and Blink as project name, ignore the sketch Blonk
-		{"", "testdata/build_path_2", blonk, nil, "testdata/build_path_2", "Blink.ino"},
+		{"", "testdata/upload/build_path_2", blonk, nil, "testdata/upload/build_path_2", "Blink.ino"},
 		// 07: error: used both importPath and importFile
-		{"testdata/build_path_2/Blink.ino.hex", "testdata/build_path_2", blonk, nil, "<nil>", ""},
+		{"testdata/upload/build_path_2/Blink.ino.hex", "testdata/upload/build_path_2", blonk, nil, "<nil>", ""},
 		// 08: error: no data passed in
 		{"", "", nil, fqbn, "<nil>", ""},
 		// 09: use importFile to detect build.path and project_name, fqbn ignored
-		{"testdata/build_path_2/Blink.ino.hex", "", nil, fqbn, "testdata/build_path_2", "Blink.ino"},
+		{"testdata/upload/build_path_2/Blink.ino.hex", "", nil, fqbn, "testdata/upload/build_path_2", "Blink.ino"},
 		// 10: use importPath as build.path and project_name, fqbn ignored
-		{"", "testdata/build_path_2", nil, fqbn, "testdata/build_path_2", "Blink.ino"},
+		{"", "testdata/upload/build_path_2", nil, fqbn, "testdata/upload/build_path_2", "Blink.ino"},
 		// 11: error: used both importPath and importFile
-		{"testdata/build_path_2/Blink.ino.hex", "testdata/build_path_2", nil, fqbn, "<nil>", ""},
+		{"testdata/upload/build_path_2/Blink.ino.hex", "testdata/upload/build_path_2", nil, fqbn, "<nil>", ""},
 		// 12: use sketch to determine project name and sketch+fqbn to determine build path
 		{"", "", blonk, fqbn, blonk.DefaultBuildPath().String(), "Blonk.ino"},
 		// 13: use importFile to detect build.path and project_name, sketch+fqbn is ignored.
-		{"testdata/build_path_2/Blink.ino.hex", "", blonk, fqbn, "testdata/build_path_2", "Blink.ino"},
+		{"testdata/upload/build_path_2/Blink.ino.hex", "", blonk, fqbn, "testdata/upload/build_path_2", "Blink.ino"},
 		// 14: use importPath as build.path and Blink as project name, ignore the sketch Blonk, ignore fqbn
-		{"", "testdata/build_path_2", blonk, fqbn, "testdata/build_path_2", "Blink.ino"},
+		{"", "testdata/upload/build_path_2", blonk, fqbn, "testdata/upload/build_path_2", "Blink.ino"},
 		// 15: error: used both importPath and importFile
-		{"testdata/build_path_2/Blink.ino.hex", "testdata/build_path_2", blonk, fqbn, "<nil>", ""},
+		{"testdata/upload/build_path_2/Blink.ino.hex", "testdata/upload/build_path_2", blonk, fqbn, "<nil>", ""},
 		// 16: importPath containing multiple firmwares, but one has the same name as the containing folder
-		{"", "testdata/firmware", nil, fqbn, "testdata/firmware", "firmware.ino"},
+		{"", "testdata/upload/firmware", nil, fqbn, "testdata/upload/firmware", "firmware.ino"},
 		// 17: importFile among multiple firmwares
-		{"testdata/firmware/another_firmware.ino.bin", "", nil, fqbn, "testdata/firmware", "another_firmware.ino"},
+		{"testdata/upload/firmware/another_firmware.ino.bin", "", nil, fqbn, "testdata/upload/firmware", "another_firmware.ino"},
 	}
 	for i, test := range tests {
 		t.Run(fmt.Sprintf("SubTest%02d", i), func(t *testing.T) {
-			buildPath, sketchName, err := determineBuildPathAndSketchName(test.importFile, test.importDir, test.sketch, test.fqbn)
+			buildPath, sketchName, err := determineBuildPathAndSketchName(test.importFile, test.importDir, test.sketch)
 			if test.resBuildPath == "<nil>" {
 				require.Error(t, err)
 				require.Nil(t, buildPath)
@@ -127,10 +129,10 @@ func TestDetermineBuildPathAndSketchName(t *testing.T) {
 }
 
 func TestUploadPropertiesComposition(t *testing.T) {
-	pmb := packagemanager.NewBuilder(nil, nil, nil, nil, "test")
-	errs := pmb.LoadHardwareFromDirectory(paths.New("testdata", "hardware"))
+	pmb := packagemanager.NewBuilder(nil, nil, nil, nil, nil, "test", downloader.GetDefaultConfig())
+	errs := pmb.LoadHardwareFromDirectory(paths.New("testdata", "upload", "hardware"))
 	require.Len(t, errs, 0)
-	buildPath1 := paths.New("testdata", "build_path_1")
+	buildPath1 := paths.New("testdata", "upload", "build_path_1")
 	logrus.SetLevel(logrus.TraceLevel)
 	type test struct {
 		importDir       *paths.Path
@@ -149,32 +151,32 @@ func TestUploadPropertiesComposition(t *testing.T) {
 
 	tests := []test{
 		// 0: classic upload, requires port
-		{buildPath1, "alice:avr:board1", "port", "serial", "", false, "conf-board1 conf-general conf-upload $$VERBOSE-VERIFY$$ protocol port -bspeed testdata/build_path_1/sketch.ino.hex\n", ""},
+		{buildPath1, "alice:avr:board1", "port", "serial", "", false, "conf-board1 conf-general conf-upload $$VERBOSE-VERIFY$$ protocol port -bspeed testdata/upload/build_path_1/sketch.ino.hex\n", ""},
 		{buildPath1, "alice:avr:board1", "", "", "", false, "FAIL", ""},
 		// 2: classic upload, no port
-		{buildPath1, "alice:avr:board2", "port", "serial", "", false, "conf-board1 conf-general conf-upload $$VERBOSE-VERIFY$$ protocol -bspeed testdata/build_path_1/sketch.ino.hex\n", ""},
-		{buildPath1, "alice:avr:board2", "", "", "", false, "conf-board1 conf-general conf-upload $$VERBOSE-VERIFY$$ protocol -bspeed testdata/build_path_1/sketch.ino.hex\n", ""},
+		{buildPath1, "alice:avr:board2", "port", "serial", "", false, "conf-board1 conf-general conf-upload $$VERBOSE-VERIFY$$ protocol -bspeed testdata/upload/build_path_1/sketch.ino.hex\n", ""},
+		{buildPath1, "alice:avr:board2", "", "", "", false, "conf-board1 conf-general conf-upload $$VERBOSE-VERIFY$$ protocol -bspeed testdata/upload/build_path_1/sketch.ino.hex\n", ""},
 
 		// 4: upload with programmer, requires port
-		{buildPath1, "alice:avr:board1", "port", "serial", "progr1", false, "conf-board1 conf-general conf-program $$VERBOSE-VERIFY$$ progprotocol port -bspeed testdata/build_path_1/sketch.ino.hex\n", ""},
+		{buildPath1, "alice:avr:board1", "port", "serial", "progr1", false, "conf-board1 conf-general conf-program $$VERBOSE-VERIFY$$ progprotocol port -bspeed testdata/upload/build_path_1/sketch.ino.hex\n", ""},
 		{buildPath1, "alice:avr:board1", "", "", "progr1", false, "FAIL", ""},
 		// 6: upload with programmer, no port
-		{buildPath1, "alice:avr:board1", "port", "serial", "progr2", false, "conf-board1 conf-general conf-program $$VERBOSE-VERIFY$$ prog2protocol -bspeed testdata/build_path_1/sketch.ino.hex\n", ""},
-		{buildPath1, "alice:avr:board1", "", "", "progr2", false, "conf-board1 conf-general conf-program $$VERBOSE-VERIFY$$ prog2protocol -bspeed testdata/build_path_1/sketch.ino.hex\n", ""},
+		{buildPath1, "alice:avr:board1", "port", "serial", "progr2", false, "conf-board1 conf-general conf-program $$VERBOSE-VERIFY$$ prog2protocol -bspeed testdata/upload/build_path_1/sketch.ino.hex\n", ""},
+		{buildPath1, "alice:avr:board1", "", "", "progr2", false, "conf-board1 conf-general conf-program $$VERBOSE-VERIFY$$ prog2protocol -bspeed testdata/upload/build_path_1/sketch.ino.hex\n", ""},
 		// 8: upload with programmer, require port through extra params
-		{buildPath1, "alice:avr:board1", "port", "serial", "progr3", false, "conf-board1 conf-general conf-program $$VERBOSE-VERIFY$$ prog3protocol port -bspeed testdata/build_path_1/sketch.ino.hex\n", ""},
+		{buildPath1, "alice:avr:board1", "port", "serial", "progr3", false, "conf-board1 conf-general conf-program $$VERBOSE-VERIFY$$ prog3protocol port -bspeed testdata/upload/build_path_1/sketch.ino.hex\n", ""},
 		{buildPath1, "alice:avr:board1", "", "", "progr3", false, "FAIL", ""},
 
 		// 10: burn bootloader, require port
 		{buildPath1, "alice:avr:board1", "port", "serial", "", true, "FAIL", ""}, // requires programmer
 		{buildPath1, "alice:avr:board1", "port", "serial", "progr1", true,
 			"ERASE conf-board1 conf-general conf-erase $$VERBOSE-VERIFY$$ genprog1protocol port -bspeed\n",
-			"BURN conf-board1 conf-general conf-bootloader $$VERBOSE-VERIFY$$ genprog1protocol port -bspeed -F0xFF " + cwd + "/testdata/hardware/alice/avr/bootloaders/niceboot/niceboot.hex\n"},
+			"BURN conf-board1 conf-general conf-bootloader $$VERBOSE-VERIFY$$ genprog1protocol port -bspeed -F0xFF " + cwd + "/testdata/upload/hardware/alice/avr/bootloaders/niceboot/niceboot.hex\n"},
 
 		// 12: burn bootloader, preferences override from programmers.txt
 		{buildPath1, "alice:avr:board1", "port", "serial", "progr4", true,
 			"ERASE conf-board1 conf-two-general conf-two-erase $$VERBOSE-VERIFY$$ prog4protocol-bootloader port -bspeed\n",
-			"BURN conf-board1 conf-two-general conf-two-bootloader $$VERBOSE-VERIFY$$ prog4protocol-bootloader port -bspeed -F0xFF " + cwd + "/testdata/hardware/alice/avr/bootloaders/niceboot/niceboot.hex\n"},
+			"BURN conf-board1 conf-two-general conf-two-bootloader $$VERBOSE-VERIFY$$ prog4protocol-bootloader port -bspeed -F0xFF " + cwd + "/testdata/upload/hardware/alice/avr/bootloaders/niceboot/niceboot.hex\n"},
 	}
 
 	pm := pmb.Build()
@@ -185,6 +187,7 @@ func TestUploadPropertiesComposition(t *testing.T) {
 		outStream := &bytes.Buffer{}
 		errStream := &bytes.Buffer{}
 		_, err := runProgramAction(
+			context.Background(),
 			pme,
 			nil,                     // sketch
 			"",                      // importFile
diff --git a/commands/daemon/term_example/main.go b/commands/term_example/main.go
similarity index 100%
rename from commands/daemon/term_example/main.go
rename to commands/term_example/main.go
diff --git a/commands/daemon/testdata/arduino-cli.yml b/commands/testdata/arduino-cli.yml
similarity index 100%
rename from commands/daemon/testdata/arduino-cli.yml
rename to commands/testdata/arduino-cli.yml
diff --git a/commands/debug/testdata/custom_hardware/arduino-test/samd/boards.txt b/commands/testdata/debug/custom_hardware/arduino-test/samd/boards.txt
similarity index 100%
rename from commands/debug/testdata/custom_hardware/arduino-test/samd/boards.txt
rename to commands/testdata/debug/custom_hardware/arduino-test/samd/boards.txt
diff --git a/commands/debug/testdata/custom_hardware/arduino-test/samd/platform.txt b/commands/testdata/debug/custom_hardware/arduino-test/samd/platform.txt
similarity index 100%
rename from commands/debug/testdata/custom_hardware/arduino-test/samd/platform.txt
rename to commands/testdata/debug/custom_hardware/arduino-test/samd/platform.txt
diff --git a/commands/debug/testdata/custom_hardware/arduino-test/samd/programmers.txt b/commands/testdata/debug/custom_hardware/arduino-test/samd/programmers.txt
similarity index 100%
rename from commands/debug/testdata/custom_hardware/arduino-test/samd/programmers.txt
rename to commands/testdata/debug/custom_hardware/arduino-test/samd/programmers.txt
diff --git a/commands/debug/testdata/data_dir/packages/arduino-test/tools/arm-none-eabi-gcc/7-2017q4/bin/arm-none-eabi-gdb b/commands/testdata/debug/data_dir/packages/arduino-test/tools/arm-none-eabi-gcc/7-2017q4/bin/arm-none-eabi-gdb
similarity index 100%
rename from commands/debug/testdata/data_dir/packages/arduino-test/tools/arm-none-eabi-gcc/7-2017q4/bin/arm-none-eabi-gdb
rename to commands/testdata/debug/data_dir/packages/arduino-test/tools/arm-none-eabi-gcc/7-2017q4/bin/arm-none-eabi-gdb
diff --git a/commands/debug/testdata/data_dir/packages/arduino-test/tools/openocd/0.10.0-arduino7/bin/openocd b/commands/testdata/debug/data_dir/packages/arduino-test/tools/openocd/0.10.0-arduino7/bin/openocd
similarity index 100%
rename from commands/debug/testdata/data_dir/packages/arduino-test/tools/openocd/0.10.0-arduino7/bin/openocd
rename to commands/testdata/debug/data_dir/packages/arduino-test/tools/openocd/0.10.0-arduino7/bin/openocd
diff --git a/commands/debug/testdata/hello/build/arduino-test.samd.arduino_zero_edbg/hello.ino.bin b/commands/testdata/debug/hello/build/arduino-test.samd.arduino_zero_edbg/hello.ino.bin
similarity index 100%
rename from commands/debug/testdata/hello/build/arduino-test.samd.arduino_zero_edbg/hello.ino.bin
rename to commands/testdata/debug/hello/build/arduino-test.samd.arduino_zero_edbg/hello.ino.bin
diff --git a/commands/debug/testdata/hello/build/arduino-test.samd.mkr1000/hello.ino.bin b/commands/testdata/debug/hello/build/arduino-test.samd.mkr1000/hello.ino.bin
similarity index 100%
rename from commands/debug/testdata/hello/build/arduino-test.samd.mkr1000/hello.ino.bin
rename to commands/testdata/debug/hello/build/arduino-test.samd.mkr1000/hello.ino.bin
diff --git a/commands/debug/testdata/hello/hello.ino b/commands/testdata/debug/hello/hello.ino
similarity index 100%
rename from commands/debug/testdata/hello/hello.ino
rename to commands/testdata/debug/hello/hello.ino
diff --git a/commands/lib/testdata/full/library_index.json b/commands/testdata/libraries/full/library_index.json
similarity index 100%
rename from commands/lib/testdata/full/library_index.json
rename to commands/testdata/libraries/full/library_index.json
diff --git a/commands/lib/testdata/qualified_search/library_index.json b/commands/testdata/libraries/qualified_search/library_index.json
similarity index 100%
rename from commands/lib/testdata/qualified_search/library_index.json
rename to commands/testdata/libraries/qualified_search/library_index.json
diff --git a/commands/lib/testdata/test1/library_index.json b/commands/testdata/libraries/test1/library_index.json
similarity index 100%
rename from commands/lib/testdata/test1/library_index.json
rename to commands/testdata/libraries/test1/library_index.json
diff --git a/commands/core/testdata/package_index.json b/commands/testdata/platform/package_index.json
similarity index 100%
rename from commands/core/testdata/package_index.json
rename to commands/testdata/platform/package_index.json
diff --git a/commands/sketch/testdata/sketch_with_profile/sketch.yml b/commands/testdata/sketch_with_profile/sketch.yml
similarity index 100%
rename from commands/sketch/testdata/sketch_with_profile/sketch.yml
rename to commands/testdata/sketch_with_profile/sketch.yml
diff --git a/commands/sketch/testdata/sketch_with_profile/sketch_with_profile.ino b/commands/testdata/sketch_with_profile/sketch_with_profile.ino
similarity index 100%
rename from commands/sketch/testdata/sketch_with_profile/sketch_with_profile.ino
rename to commands/testdata/sketch_with_profile/sketch_with_profile.ino
diff --git a/commands/upload/testdata/Blonk/Blonk.ino b/commands/testdata/upload/Blonk/Blonk.ino
similarity index 100%
rename from commands/upload/testdata/Blonk/Blonk.ino
rename to commands/testdata/upload/Blonk/Blonk.ino
diff --git a/commands/upload/testdata/Blonk/build/arduino.samd.mkr1000/Blonk.ino.bin b/commands/testdata/upload/Blonk/build/arduino.samd.mkr1000/Blonk.ino.bin
similarity index 100%
rename from commands/upload/testdata/Blonk/build/arduino.samd.mkr1000/Blonk.ino.bin
rename to commands/testdata/upload/Blonk/build/arduino.samd.mkr1000/Blonk.ino.bin
diff --git a/commands/upload/testdata/Blonk/build/arduino.samd.mkr1000/Blonk.ino.elf b/commands/testdata/upload/Blonk/build/arduino.samd.mkr1000/Blonk.ino.elf
similarity index 100%
rename from commands/upload/testdata/Blonk/build/arduino.samd.mkr1000/Blonk.ino.elf
rename to commands/testdata/upload/Blonk/build/arduino.samd.mkr1000/Blonk.ino.elf
diff --git a/commands/upload/testdata/Blonk/build/arduino.samd.mkr1000/Blonk.ino.hex b/commands/testdata/upload/Blonk/build/arduino.samd.mkr1000/Blonk.ino.hex
similarity index 100%
rename from commands/upload/testdata/Blonk/build/arduino.samd.mkr1000/Blonk.ino.hex
rename to commands/testdata/upload/Blonk/build/arduino.samd.mkr1000/Blonk.ino.hex
diff --git a/commands/upload/testdata/Blonk/build/arduino.samd.mkr1000/Blonk.ino.map b/commands/testdata/upload/Blonk/build/arduino.samd.mkr1000/Blonk.ino.map
similarity index 100%
rename from commands/upload/testdata/Blonk/build/arduino.samd.mkr1000/Blonk.ino.map
rename to commands/testdata/upload/Blonk/build/arduino.samd.mkr1000/Blonk.ino.map
diff --git a/commands/upload/testdata/Blonk/build/arduino.samd.mkr1000/Blonk.ino.with_bootloader.bin b/commands/testdata/upload/Blonk/build/arduino.samd.mkr1000/Blonk.ino.with_bootloader.bin
similarity index 100%
rename from commands/upload/testdata/Blonk/build/arduino.samd.mkr1000/Blonk.ino.with_bootloader.bin
rename to commands/testdata/upload/Blonk/build/arduino.samd.mkr1000/Blonk.ino.with_bootloader.bin
diff --git a/commands/upload/testdata/Blonk/build/arduino.samd.mkr1000/Blonk.ino.with_bootloader.hex b/commands/testdata/upload/Blonk/build/arduino.samd.mkr1000/Blonk.ino.with_bootloader.hex
similarity index 100%
rename from commands/upload/testdata/Blonk/build/arduino.samd.mkr1000/Blonk.ino.with_bootloader.hex
rename to commands/testdata/upload/Blonk/build/arduino.samd.mkr1000/Blonk.ino.with_bootloader.hex
diff --git a/commands/upload/testdata/build_path_1/sketch.ino.bin b/commands/testdata/upload/build_path_1/sketch.ino.bin
similarity index 100%
rename from commands/upload/testdata/build_path_1/sketch.ino.bin
rename to commands/testdata/upload/build_path_1/sketch.ino.bin
diff --git a/commands/upload/testdata/build_path_2/Blink.ino.bin b/commands/testdata/upload/build_path_2/Blink.ino.bin
similarity index 100%
rename from commands/upload/testdata/build_path_2/Blink.ino.bin
rename to commands/testdata/upload/build_path_2/Blink.ino.bin
diff --git a/commands/upload/testdata/build_path_2/Blink.ino.elf b/commands/testdata/upload/build_path_2/Blink.ino.elf
similarity index 100%
rename from commands/upload/testdata/build_path_2/Blink.ino.elf
rename to commands/testdata/upload/build_path_2/Blink.ino.elf
diff --git a/commands/upload/testdata/build_path_2/Blink.ino.hex b/commands/testdata/upload/build_path_2/Blink.ino.hex
similarity index 100%
rename from commands/upload/testdata/build_path_2/Blink.ino.hex
rename to commands/testdata/upload/build_path_2/Blink.ino.hex
diff --git a/commands/upload/testdata/build_path_2/Blink.ino.map b/commands/testdata/upload/build_path_2/Blink.ino.map
similarity index 100%
rename from commands/upload/testdata/build_path_2/Blink.ino.map
rename to commands/testdata/upload/build_path_2/Blink.ino.map
diff --git a/commands/upload/testdata/build_path_2/Blink.ino.with_bootloader.bin b/commands/testdata/upload/build_path_2/Blink.ino.with_bootloader.bin
similarity index 100%
rename from commands/upload/testdata/build_path_2/Blink.ino.with_bootloader.bin
rename to commands/testdata/upload/build_path_2/Blink.ino.with_bootloader.bin
diff --git a/commands/upload/testdata/build_path_2/Blink.ino.with_bootloader.hex b/commands/testdata/upload/build_path_2/Blink.ino.with_bootloader.hex
similarity index 100%
rename from commands/upload/testdata/build_path_2/Blink.ino.with_bootloader.hex
rename to commands/testdata/upload/build_path_2/Blink.ino.with_bootloader.hex
diff --git a/commands/upload/testdata/build_path_3/AnotherSketch.ino.bin b/commands/testdata/upload/build_path_3/AnotherSketch.ino.bin
similarity index 100%
rename from commands/upload/testdata/build_path_3/AnotherSketch.ino.bin
rename to commands/testdata/upload/build_path_3/AnotherSketch.ino.bin
diff --git a/commands/upload/testdata/build_path_3/Blink.ino.bin b/commands/testdata/upload/build_path_3/Blink.ino.bin
similarity index 100%
rename from commands/upload/testdata/build_path_3/Blink.ino.bin
rename to commands/testdata/upload/build_path_3/Blink.ino.bin
diff --git a/commands/upload/testdata/build_path_3/Blink.ino.elf b/commands/testdata/upload/build_path_3/Blink.ino.elf
similarity index 100%
rename from commands/upload/testdata/build_path_3/Blink.ino.elf
rename to commands/testdata/upload/build_path_3/Blink.ino.elf
diff --git a/commands/upload/testdata/build_path_3/Blink.ino.hex b/commands/testdata/upload/build_path_3/Blink.ino.hex
similarity index 100%
rename from commands/upload/testdata/build_path_3/Blink.ino.hex
rename to commands/testdata/upload/build_path_3/Blink.ino.hex
diff --git a/commands/upload/testdata/build_path_3/Blink.ino.map b/commands/testdata/upload/build_path_3/Blink.ino.map
similarity index 100%
rename from commands/upload/testdata/build_path_3/Blink.ino.map
rename to commands/testdata/upload/build_path_3/Blink.ino.map
diff --git a/commands/upload/testdata/build_path_3/Blink.ino.with_bootloader.bin b/commands/testdata/upload/build_path_3/Blink.ino.with_bootloader.bin
similarity index 100%
rename from commands/upload/testdata/build_path_3/Blink.ino.with_bootloader.bin
rename to commands/testdata/upload/build_path_3/Blink.ino.with_bootloader.bin
diff --git a/commands/upload/testdata/build_path_3/Blink.ino.with_bootloader.hex b/commands/testdata/upload/build_path_3/Blink.ino.with_bootloader.hex
similarity index 100%
rename from commands/upload/testdata/build_path_3/Blink.ino.with_bootloader.hex
rename to commands/testdata/upload/build_path_3/Blink.ino.with_bootloader.hex
diff --git a/commands/upload/testdata/build_path_4/some_other_files.txt b/commands/testdata/upload/build_path_4/some_other_files.txt
similarity index 100%
rename from commands/upload/testdata/build_path_4/some_other_files.txt
rename to commands/testdata/upload/build_path_4/some_other_files.txt
diff --git a/commands/upload/testdata/firmware/another_firmware.ino.bin b/commands/testdata/upload/firmware/another_firmware.ino.bin
similarity index 100%
rename from commands/upload/testdata/firmware/another_firmware.ino.bin
rename to commands/testdata/upload/firmware/another_firmware.ino.bin
diff --git a/commands/upload/testdata/firmware/firmware.ino.bin b/commands/testdata/upload/firmware/firmware.ino.bin
similarity index 100%
rename from commands/upload/testdata/firmware/firmware.ino.bin
rename to commands/testdata/upload/firmware/firmware.ino.bin
diff --git a/commands/upload/testdata/hardware/alice/avr/boards.txt b/commands/testdata/upload/hardware/alice/avr/boards.txt
similarity index 100%
rename from commands/upload/testdata/hardware/alice/avr/boards.txt
rename to commands/testdata/upload/hardware/alice/avr/boards.txt
diff --git a/commands/upload/testdata/hardware/alice/avr/platform.txt b/commands/testdata/upload/hardware/alice/avr/platform.txt
similarity index 100%
rename from commands/upload/testdata/hardware/alice/avr/platform.txt
rename to commands/testdata/upload/hardware/alice/avr/platform.txt
diff --git a/commands/upload/testdata/hardware/alice/avr/programmers.txt b/commands/testdata/upload/hardware/alice/avr/programmers.txt
similarity index 100%
rename from commands/upload/testdata/hardware/alice/avr/programmers.txt
rename to commands/testdata/upload/hardware/alice/avr/programmers.txt
diff --git a/commands/core.go b/commands/utility_core.go
similarity index 91%
rename from commands/core.go
rename to commands/utility_core.go
index e61078da1b2..b5a57ad509d 100644
--- a/commands/core.go
+++ b/commands/utility_core.go
@@ -20,8 +20,8 @@ import (
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 )
 
-// PlatformToRPCPlatformMetadata converts our internal structure to the RPC structure.
-func PlatformToRPCPlatformMetadata(platform *cores.Platform) *rpc.PlatformMetadata {
+// platformToRPCPlatformMetadata converts our internal structure to the RPC structure.
+func platformToRPCPlatformMetadata(platform *cores.Platform) *rpc.PlatformMetadata {
 	return &rpc.PlatformMetadata{
 		Id:                platform.String(),
 		Maintainer:        platform.Package.Maintainer,
@@ -33,10 +33,10 @@ func PlatformToRPCPlatformMetadata(platform *cores.Platform) *rpc.PlatformMetada
 	}
 }
 
-// PlatformReleaseToRPC converts our internal structure to the RPC structure.
+// platformReleaseToRPC converts our internal structure to the RPC structure.
 // Note: this function does not touch the "Installed" field of rpc.Platform as it's not always clear that the
 // platformRelease we're currently converting is actually installed.
-func PlatformReleaseToRPC(platformRelease *cores.PlatformRelease) *rpc.PlatformRelease {
+func platformReleaseToRPC(platformRelease *cores.PlatformRelease) *rpc.PlatformRelease {
 	// If the boards are not installed yet, the `platformRelease.Boards` will be a zero length slice.
 	// In such case, we have to use the `platformRelease.BoardsManifest` instead.
 	// So that we can retrieve the name of the boards at least.
diff --git a/commands/utility_grpc_streaming.go b/commands/utility_grpc_streaming.go
new file mode 100644
index 00000000000..927e5e40f2d
--- /dev/null
+++ b/commands/utility_grpc_streaming.go
@@ -0,0 +1,120 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package commands
+
+import (
+	"context"
+	"errors"
+	"sync"
+
+	"google.golang.org/grpc/metadata"
+)
+
+type streamingResponseProxyToChan[T any] struct {
+	ctx      context.Context
+	respChan chan<- *T
+	respLock sync.Mutex
+}
+
+func streamResponseToChan[T any](ctx context.Context) (*streamingResponseProxyToChan[T], <-chan *T) {
+	respChan := make(chan *T, 1)
+	w := &streamingResponseProxyToChan[T]{
+		ctx:      ctx,
+		respChan: respChan,
+	}
+	go func() {
+		<-ctx.Done()
+		w.respLock.Lock()
+		close(w.respChan)
+		w.respChan = nil
+		w.respLock.Unlock()
+	}()
+	return w, respChan
+}
+
+func (w *streamingResponseProxyToChan[T]) Send(resp *T) error {
+	w.respLock.Lock()
+	if w.respChan != nil {
+		w.respChan <- resp
+	}
+	w.respLock.Unlock()
+	return nil
+}
+
+func (w *streamingResponseProxyToChan[T]) Context() context.Context {
+	return w.ctx
+}
+
+func (w *streamingResponseProxyToChan[T]) RecvMsg(m any) error {
+	return errors.New("RecvMsg not implemented")
+}
+
+func (w *streamingResponseProxyToChan[T]) SendHeader(metadata.MD) error {
+	return errors.New("SendHeader not implemented")
+}
+
+func (w *streamingResponseProxyToChan[T]) SendMsg(m any) error {
+	return errors.New("SendMsg not implemented")
+}
+
+func (w *streamingResponseProxyToChan[T]) SetHeader(metadata.MD) error {
+	return errors.New("SetHeader not implemented")
+}
+
+func (w *streamingResponseProxyToChan[T]) SetTrailer(tr metadata.MD) {
+}
+
+// streamingResponseProxyToCallback is a streaming response proxy that
+// forwards the responses to a callback function
+type streamingResponseProxyToCallback[T any] struct {
+	ctx context.Context
+	cb  func(*T) error
+}
+
+// creates a streaming response proxy that forwards the responses to a callback function
+func streamResponseToCallback[T any](ctx context.Context, cb func(*T) error) *streamingResponseProxyToCallback[T] {
+	if cb == nil {
+		cb = func(*T) error { return nil }
+	}
+	return &streamingResponseProxyToCallback[T]{ctx: ctx, cb: cb}
+}
+
+func (w *streamingResponseProxyToCallback[T]) Send(resp *T) error {
+	return w.cb(resp)
+}
+
+func (w *streamingResponseProxyToCallback[T]) Context() context.Context {
+	return w.ctx
+}
+
+func (w *streamingResponseProxyToCallback[T]) RecvMsg(m any) error {
+	return errors.New("RecvMsg not implemented")
+}
+
+func (w *streamingResponseProxyToCallback[T]) SendHeader(metadata.MD) error {
+	return errors.New("SendHeader not implemented")
+}
+
+func (w *streamingResponseProxyToCallback[T]) SendMsg(m any) error {
+	return errors.New("SendMsg not implemented")
+}
+
+func (w *streamingResponseProxyToCallback[T]) SetHeader(metadata.MD) error {
+	return errors.New("SetHeader not implemented")
+}
+
+func (w *streamingResponseProxyToCallback[T]) SetTrailer(tr metadata.MD) {
+}
diff --git a/commands/lib/search_matcher.go b/commands/utility_libraries_index_search_matcher.go
similarity index 99%
rename from commands/lib/search_matcher.go
rename to commands/utility_libraries_index_search_matcher.go
index 3eec08b4ad9..f8ed7205c9d 100644
--- a/commands/lib/search_matcher.go
+++ b/commands/utility_libraries_index_search_matcher.go
@@ -13,7 +13,7 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package lib
+package commands
 
 import (
 	"strings"
diff --git a/commands/version.go b/commands/utility_version.go
similarity index 89%
rename from commands/version.go
rename to commands/utility_version.go
index 2fd87215a2f..a7232d6fdc4 100644
--- a/commands/version.go
+++ b/commands/utility_version.go
@@ -20,10 +20,10 @@ import (
 	semver "go.bug.st/relaxed-semver"
 )
 
-// ParseVersion returns the parsed version or nil if the version is
+// parseVersion returns the parsed version or nil if the version is
 // the empty string. An error is returned if the version is not valid
 // semver.
-func ParseVersion(version string) (*semver.Version, error) {
+func parseVersion(version string) (*semver.Version, error) {
 	if version == "" {
 		return nil, nil
 	}
diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md
index ef245d67a75..1273fa0a455 100644
--- a/docs/UPGRADING.md
+++ b/docs/UPGRADING.md
@@ -4,6 +4,173 @@ Here you can find a list of migration guides to handle breaking changes between
 
 ## 0.36.0
 
+### Configuration file now supports only YAML format.
+
+The Arduino CLI configuration file now supports only the YAML format.
+
+### gRPC Setting API important changes
+
+The Settings API has been heavily refactored. Here a quick recap of the new methods:
+
+- `SettingsGetValue` returns the value of a setting given the key. The returned value is a string encoded in JSON, or
+  YAML
+
+  ```proto
+  message SettingsGetValueRequest {
+    // The key to get
+    string key = 1;
+    // The format of the encoded_value (default is
+    // "json", allowed values are "json" and "yaml)
+    string value_format = 2;
+  }
+
+  message SettingsGetValueResponse {
+    // The value of the key (encoded)
+    string encoded_value = 1;
+  }
+  ```
+
+- `SettingsSetValue` change the value of a setting. The value may be specified in JSON, YAML, or as a command-line
+  argument. If `encoded_value` is an empty string the setting is deleted.
+
+  ```proto
+  message SettingsSetValueRequest {
+    // The key to change
+    string key = 1;
+    // The new value (encoded), no objects,
+    // only scalar, or array of scalars are
+    // allowed.
+    string encoded_value = 2;
+    // The format of the encoded_value (default is
+    // "json", allowed values are "json", "yaml",
+    // and "cli")
+    string value_format = 3;
+  }
+  ```
+
+- `SettingsEnumerate` returns all the available keys and their type (`string`, `int`, `[]string`...)
+- `ConfigurationOpen` replaces the current configuration with the one passed as argument. Differently from
+  `SettingsSetValue`, this call replaces the whole configuration.
+- `ConfigurationSave` outputs the current configuration in the specified format. The configuration is not saved in a
+  file, this call returns just the content, it's a duty of the caller to store the content in a file.
+- `ConfigurationGet` return the current configuration in a structured gRPC message `Configuration`.
+
+The previous gRPC Setting rpc call may be replaced as follows:
+
+- The old `SettingsMerge` rpc call can now be done trough `SettingsSetValue`.
+- The old `SettingsDelete` rpc call can now be done trough `SettingsSetValue` passing the `key` to delete with an empty
+  `value`.
+- The old `SettingsGetAll` rpc call has been replaced by `ConfigurationGet` that returns a structured message
+  `Configuration` with all the settings populated.
+- The old `SettingsWrite` rpc call has been removed. It is partially replaced by `ConfigurationSave` but the actual file
+  save must be performed by the caller.
+
+### golang: importing `arduino-cli` as a library now requires the creation of a gRPC service.
+
+Previously the methods implementing the Arduino business logic were available in the global namespace
+`github.com/arduino/arduino-cli/commands/*` and could be called directly.
+
+The above is no more true. All the global `commands.*` functions have been converted to methods of the
+`arduinoCoreServerImpl` struct that implements the gRPC `ArduinoCoreServer` interface. The configuration is now part of
+the server internal state. Developers may create a "daemon-less" server by calling the `commands.NewArduinoCoreServer()`
+function and can use the returned object to call all the needed gRPC functions.
+
+The methods of the `ArduinoCoreServer` are generated through the gRPC protobuf definitions, some of those methods are
+gRPC "streaming" methods and requires a streaming object to be passed as argument, we provided helper methods to create
+these objects.
+
+For example if previously we could call `commands.Init` like this:
+
+```go
+// old Init signature
+func Init(req *rpc.InitRequest, responseCallback func(r *rpc.InitResponse)) error { ... }
+
+// ...
+
+// Initialize instance
+if err := commands.Init(&rpc.InitRequest{Instance: req.GetInstance()}, respCB); err != nil {
+  return err
+}
+```
+
+now the `responseCallback` must be wrapped into an `rpc.ArduinoCoreService_InitServer`, and we provided a method exactly
+for that:
+
+```go
+// new Init method
+func (s *arduinoCoreServerImpl) Init(req *rpc.InitRequest, stream rpc.ArduinoCoreService_InitServer) error { ... }
+
+/// ...
+
+// Initialize instance
+initStream := InitStreamResponseToCallbackFunction(ctx, respCB)
+if err := srv.Init(&rpc.InitRequest{Instance: req.GetInstance()}, initStream); err != nil {
+  return err
+}
+```
+
+Each gRPC method has an helper method to obtain the corresponding `ArduinoCoreService_*Server` parameter. Here a simple,
+but complete, example:
+
+```go
+package main
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"log"
+
+	"github.com/arduino/arduino-cli/commands"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
+	"github.com/sirupsen/logrus"
+)
+
+func main() {
+	// Create a new ArduinoCoreServer
+	srv := commands.NewArduinoCoreServer()
+
+	// Disable logging
+	logrus.SetOutput(io.Discard)
+
+	// Create a new instance in the server
+	ctx := context.Background()
+	resp, err := srv.Create(ctx, &rpc.CreateRequest{})
+	if err != nil {
+		log.Fatal("Error creating instance:", err)
+	}
+	instance := resp.GetInstance()
+
+	// Defer the destruction of the instance
+	defer func() {
+		if _, err := srv.Destroy(ctx, &rpc.DestroyRequest{Instance: instance}); err != nil {
+			log.Fatal("Error destroying instance:", err)
+		}
+		fmt.Println("Instance successfully destroyed")
+	}()
+
+	// Initialize the instance
+	initStream := commands.InitStreamResponseToCallbackFunction(ctx, func(r *rpc.InitResponse) error {
+		fmt.Println("INIT> ", r)
+		return nil
+	})
+	if err := srv.Init(&rpc.InitRequest{Instance: instance}, initStream); err != nil {
+		log.Fatal("Error during initialization:", err)
+	}
+
+	// Search for platforms and output the result
+	searchResp, err := srv.PlatformSearch(ctx, &rpc.PlatformSearchRequest{Instance: instance})
+	if err != nil {
+		log.Fatal("Error searching for platforms:", err)
+	}
+	for _, platformSummary := range searchResp.GetSearchOutput() {
+		installed := platformSummary.GetInstalledRelease()
+		meta := platformSummary.GetMetadata()
+		fmt.Printf("%30s %8s %s\n", meta.GetId(), installed.GetVersion(), installed.GetName())
+	}
+}
+```
+
 ### YAML output format is no more supported
 
 The `yaml` option of the `--format` flag is no more supported. Use `--format json` if machine parsable output is needed.
@@ -1698,7 +1865,7 @@ https://arduino.github.io/arduino-cli/dev/rpc/commands/#monitorresponse
 https://arduino.github.io/arduino-cli/dev/rpc/commands/#enumeratemonitorportsettingsrequest
 https://arduino.github.io/arduino-cli/dev/rpc/commands/#enumeratemonitorportsettingsresponse
 
-https://github.com/arduino/arduino-cli/blob/master/commands/daemon/term_example/main.go
+https://github.com/arduino/arduino-cli/blob/752709af9bf1bf8f6c1e6f689b1e8b86cc4e884e/commands/daemon/term_example/main.go
 
 ## 0.23.0
 
diff --git a/docs/configuration.md b/docs/configuration.md
index 57bda320635..bfde1372dac 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -97,8 +97,7 @@ $ export ARDUINO_BOARD_MANAGER_ADDITIONAL_URLS="https://downloads.arduino.cc/pac
 
 ### Configuration file
 
-[`arduino-cli config init`][arduino-cli config init] creates or updates a configuration file with the current
-configuration settings.
+[`arduino-cli config init`][arduino-cli config init] creates a new empty configuration file.
 
 This allows saving the options set by command line flags or environment variables. For example:
 
@@ -106,29 +105,14 @@ This allows saving the options set by command line flags or environment variable
 arduino-cli config init --additional-urls https://downloads.arduino.cc/packages/package_staging_index.json
 ```
 
-#### File name
-
-The configuration file must be named `arduino-cli`, with the appropriate file extension for the file's format.
-
-#### Supported formats
-
-`arduino-cli config init` creates a YAML file, however a variety of common formats are supported:
-
-- [JSON]
-- [TOML]
-- [YAML]
-- [Java properties file]
-- [HCL]
-- envfile
-- [INI]
-
 #### Locations
 
-Configuration files in the following locations are recognized by Arduino CLI:
+The default configuration file is named `arduino-cli.yaml`. The configuration file is searched in the following
+locations, in order of priority:
 
 1. Location specified by the [`--config-file`][arduino cli command reference] command line flag
 1. Location specified by the `ARDUINO_CONFIG_FILE` environment variable
-1. Arduino CLI data directory (as configured by `directories.data`)
+1. Location specified by the `ARDUINO_DIRECTORIES_DATA` environment variable
 
 If multiple configuration files are present, the one highest on the above list is used. Configuration files are not
 combined.
diff --git a/internal/algorithms/slices.go b/internal/algorithms/slices.go
index ab904a97f94..90ea3d1984b 100644
--- a/internal/algorithms/slices.go
+++ b/internal/algorithms/slices.go
@@ -71,3 +71,17 @@ func NotEquals[T comparable](value T) Matcher[T] {
 		return x != value
 	}
 }
+
+// Uniq return a copy of the input array with all duplicates removed
+func Uniq[T comparable](in []T) []T {
+	have := map[T]bool{}
+	var out []T
+	for _, v := range in {
+		if have[v] {
+			continue
+		}
+		out = append(out, v)
+		have[v] = true
+	}
+	return out
+}
diff --git a/internal/arduino/builder/builder.go b/internal/arduino/builder/builder.go
index 303caaa2063..1727556b846 100644
--- a/internal/arduino/builder/builder.go
+++ b/internal/arduino/builder/builder.go
@@ -16,6 +16,7 @@
 package builder
 
 import (
+	"context"
 	"errors"
 	"fmt"
 	"io"
@@ -43,6 +44,8 @@ var ErrSketchCannotBeLocatedInBuildPath = errors.New("sketch cannot be located i
 
 // Builder is a Sketch builder.
 type Builder struct {
+	ctx context.Context
+
 	sketch          *sketch.Sketch
 	buildProperties *properties.Map
 
@@ -111,6 +114,7 @@ type buildArtifacts struct {
 
 // NewBuilder creates a sketch Builder.
 func NewBuilder(
+	ctx context.Context,
 	sk *sketch.Sketch,
 	boardBuildProperties *properties.Map,
 	buildPath *paths.Path,
@@ -196,6 +200,7 @@ func NewBuilder(
 
 	diagnosticStore := diagnostics.NewStore()
 	b := &Builder{
+		ctx:                           ctx,
 		sketch:                        sk,
 		buildProperties:               buildProperties,
 		buildPath:                     buildPath,
@@ -303,6 +308,7 @@ func (b *Builder) preprocess() error {
 
 	b.logIfVerbose(false, tr("Detecting libraries used..."))
 	err := b.libsDetector.FindIncludes(
+		b.ctx,
 		b.buildPath,
 		b.buildProperties.GetPath("build.core.path"),
 		b.buildProperties.GetPath("build.variant.path"),
diff --git a/internal/arduino/builder/internal/detector/detector.go b/internal/arduino/builder/internal/detector/detector.go
index 46fa991a132..8f9a305cea4 100644
--- a/internal/arduino/builder/internal/detector/detector.go
+++ b/internal/arduino/builder/internal/detector/detector.go
@@ -17,6 +17,7 @@ package detector
 
 import (
 	"bytes"
+	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -196,6 +197,7 @@ func (l *SketchLibrariesDetector) appendIncludeFolder(
 
 // FindIncludes todo
 func (l *SketchLibrariesDetector) FindIncludes(
+	ctx context.Context,
 	buildPath *paths.Path,
 	buildCorePath *paths.Path,
 	buildVariantPath *paths.Path,
@@ -205,7 +207,7 @@ func (l *SketchLibrariesDetector) FindIncludes(
 	buildProperties *properties.Map,
 	platformArch string,
 ) error {
-	err := l.findIncludes(buildPath, buildCorePath, buildVariantPath, sketchBuildPath, sketch, librariesBuildPath, buildProperties, platformArch)
+	err := l.findIncludes(ctx, buildPath, buildCorePath, buildVariantPath, sketchBuildPath, sketch, librariesBuildPath, buildProperties, platformArch)
 	if err != nil && l.onlyUpdateCompilationDatabase {
 		l.logger.Info(
 			fmt.Sprintf(
@@ -220,6 +222,7 @@ func (l *SketchLibrariesDetector) FindIncludes(
 }
 
 func (l *SketchLibrariesDetector) findIncludes(
+	ctx context.Context,
 	buildPath *paths.Path,
 	buildCorePath *paths.Path,
 	buildVariantPath *paths.Path,
@@ -269,7 +272,7 @@ func (l *SketchLibrariesDetector) findIncludes(
 		}
 
 		for !sourceFileQueue.empty() {
-			err := l.findIncludesUntilDone(cache, sourceFileQueue, buildProperties, sketchBuildPath, librariesBuildPath, platformArch)
+			err := l.findIncludesUntilDone(ctx, cache, sourceFileQueue, buildProperties, librariesBuildPath, platformArch)
 			if err != nil {
 				cachePath.Remove()
 				return err
@@ -297,10 +300,10 @@ func (l *SketchLibrariesDetector) findIncludes(
 }
 
 func (l *SketchLibrariesDetector) findIncludesUntilDone(
+	ctx context.Context,
 	cache *includeCache,
 	sourceFileQueue *uniqueSourceFileQueue,
 	buildProperties *properties.Map,
-	sketchBuildPath *paths.Path,
 	librariesBuildPath *paths.Path,
 	platformArch string,
 ) error {
@@ -350,7 +353,7 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone(
 				l.logger.Info(tr("Using cached library dependencies for file: %[1]s", sourcePath))
 			}
 		} else {
-			preprocFirstResult, preprocErr = preprocessor.GCC(sourcePath, targetFilePath, includeFolders, buildProperties)
+			preprocFirstResult, preprocErr = preprocessor.GCC(ctx, sourcePath, targetFilePath, includeFolders, buildProperties)
 			if l.logger.Verbose() {
 				l.logger.WriteStdout(preprocFirstResult.Stdout())
 			}
@@ -381,7 +384,7 @@ func (l *SketchLibrariesDetector) findIncludesUntilDone(
 			// Library could not be resolved, show error
 			if preprocErr == nil || preprocFirstResult.Stderr() == nil {
 				// Filename came from cache, so run preprocessor to obtain error to show
-				result, err := preprocessor.GCC(sourcePath, targetFilePath, includeFolders, buildProperties)
+				result, err := preprocessor.GCC(ctx, sourcePath, targetFilePath, includeFolders, buildProperties)
 				if l.logger.Verbose() {
 					l.logger.WriteStdout(result.Stdout())
 				}
diff --git a/internal/arduino/builder/internal/preprocessor/arduino_preprocessor.go b/internal/arduino/builder/internal/preprocessor/arduino_preprocessor.go
index da8cde2adfc..399ca34f742 100644
--- a/internal/arduino/builder/internal/preprocessor/arduino_preprocessor.go
+++ b/internal/arduino/builder/internal/preprocessor/arduino_preprocessor.go
@@ -31,6 +31,7 @@ import (
 // PreprocessSketchWithArduinoPreprocessor performs preprocessing of the arduino sketch
 // using arduino-preprocessor (https://github.com/arduino/arduino-preprocessor).
 func PreprocessSketchWithArduinoPreprocessor(
+	ctx context.Context,
 	sk *sketch.Sketch, buildPath *paths.Path, includeFolders paths.PathList,
 	lineOffset int, buildProperties *properties.Map, onlyUpdateCompilationDatabase bool,
 ) (*Result, error) {
@@ -42,7 +43,7 @@ func PreprocessSketchWithArduinoPreprocessor(
 
 	sourceFile := buildPath.Join("sketch", sk.MainFile.Base()+".cpp")
 	targetFile := buildPath.Join("preproc", "sketch_merged.cpp")
-	gccResult, err := GCC(sourceFile, targetFile, includeFolders, buildProperties)
+	gccResult, err := GCC(ctx, sourceFile, targetFile, includeFolders, buildProperties)
 	verboseOut.Write(gccResult.Stdout())
 	verboseOut.Write(gccResult.Stderr())
 	if err != nil {
@@ -78,7 +79,7 @@ func PreprocessSketchWithArduinoPreprocessor(
 	}
 
 	verboseOut.WriteString(commandLine)
-	commandStdOut, commandStdErr, err := command.RunAndCaptureOutput(context.Background())
+	commandStdOut, commandStdErr, err := command.RunAndCaptureOutput(ctx)
 	verboseOut.Write(commandStdErr)
 	if err != nil {
 		return &Result{args: gccResult.Args(), stdout: verboseOut.Bytes(), stderr: normalOut.Bytes()}, err
diff --git a/internal/arduino/builder/internal/preprocessor/ctags.go b/internal/arduino/builder/internal/preprocessor/ctags.go
index 4a0cf783b45..73b60f80c49 100644
--- a/internal/arduino/builder/internal/preprocessor/ctags.go
+++ b/internal/arduino/builder/internal/preprocessor/ctags.go
@@ -41,6 +41,7 @@ var DebugPreprocessor bool
 
 // PreprocessSketchWithCtags performs preprocessing of the arduino sketch using CTags.
 func PreprocessSketchWithCtags(
+	ctx context.Context,
 	sketch *sketch.Sketch, buildPath *paths.Path, includes paths.PathList,
 	lineOffset int, buildProperties *properties.Map,
 	onlyUpdateCompilationDatabase, verbose bool,
@@ -57,7 +58,7 @@ func PreprocessSketchWithCtags(
 
 	// Run GCC preprocessor
 	sourceFile := buildPath.Join("sketch", sketch.MainFile.Base()+".cpp")
-	result, err := GCC(sourceFile, ctagsTarget, includes, buildProperties)
+	result, err := GCC(ctx, sourceFile, ctagsTarget, includes, buildProperties)
 	stdout.Write(result.Stdout())
 	stderr.Write(result.Stderr())
 	if err != nil {
@@ -84,7 +85,7 @@ func PreprocessSketchWithCtags(
 	}
 
 	// Run CTags on gcc-preprocessed source
-	ctagsOutput, ctagsStdErr, err := RunCTags(ctagsTarget, buildProperties)
+	ctagsOutput, ctagsStdErr, err := RunCTags(ctx, ctagsTarget, buildProperties)
 	if verbose {
 		stderr.Write(ctagsStdErr)
 	}
@@ -179,7 +180,7 @@ func isFirstFunctionOutsideOfSource(firstFunctionLine int, sourceRows []string)
 }
 
 // RunCTags performs a run of ctags on the given source file. Returns the ctags output and the stderr contents.
-func RunCTags(sourceFile *paths.Path, buildProperties *properties.Map) ([]byte, []byte, error) {
+func RunCTags(ctx context.Context, sourceFile *paths.Path, buildProperties *properties.Map) ([]byte, []byte, error) {
 	ctagsBuildProperties := properties.NewMap()
 	ctagsBuildProperties.Set("tools.ctags.path", "{runtime.tools.ctags.path}")
 	ctagsBuildProperties.Set("tools.ctags.cmd.path", "{path}/ctags")
@@ -202,7 +203,7 @@ func RunCTags(sourceFile *paths.Path, buildProperties *properties.Map) ([]byte,
 	if err != nil {
 		return nil, nil, err
 	}
-	stdout, stderr, err := proc.RunAndCaptureOutput(context.Background())
+	stdout, stderr, err := proc.RunAndCaptureOutput(ctx)
 
 	// Append ctags arguments to stderr
 	args := fmt.Sprintln(strings.Join(parts, " "))
diff --git a/internal/arduino/builder/internal/preprocessor/gcc.go b/internal/arduino/builder/internal/preprocessor/gcc.go
index d9cf1c446ea..f97426c2df8 100644
--- a/internal/arduino/builder/internal/preprocessor/gcc.go
+++ b/internal/arduino/builder/internal/preprocessor/gcc.go
@@ -30,6 +30,7 @@ import (
 // GCC performs a run of the gcc preprocess (macro/includes expansion). The function outputs the result
 // to targetFilePath. Returns the stdout/stderr of gcc if any.
 func GCC(
+	ctx context.Context,
 	sourceFilePath, targetFilePath *paths.Path,
 	includes paths.PathList, buildProperties *properties.Map,
 ) (Result, error) {
@@ -75,7 +76,7 @@ func GCC(
 	if err != nil {
 		return Result{}, err
 	}
-	stdout, stderr, err := proc.RunAndCaptureOutput(context.Background())
+	stdout, stderr, err := proc.RunAndCaptureOutput(ctx)
 
 	// Append gcc arguments to stdout
 	stdout = append([]byte(fmt.Sprintln(strings.Join(args, " "))), stdout...)
diff --git a/internal/arduino/builder/preprocess_sketch.go b/internal/arduino/builder/preprocess_sketch.go
index d7fd6e32e72..86d7bd7e7e9 100644
--- a/internal/arduino/builder/preprocess_sketch.go
+++ b/internal/arduino/builder/preprocess_sketch.go
@@ -24,6 +24,7 @@ import (
 func (b *Builder) preprocessSketch(includes paths.PathList) error {
 	// In the future we might change the preprocessor
 	result, err := preprocessor.PreprocessSketchWithCtags(
+		b.ctx,
 		b.sketch, b.buildPath, includes, b.lineOffset,
 		b.buildProperties, b.onlyUpdateCompilationDatabase, b.logger.Verbose(),
 	)
diff --git a/internal/arduino/cores/packagemanager/download.go b/internal/arduino/cores/packagemanager/download.go
index c4a7cb62d59..4ac06cf19f8 100644
--- a/internal/arduino/cores/packagemanager/download.go
+++ b/internal/arduino/cores/packagemanager/download.go
@@ -22,7 +22,6 @@ import (
 	"github.com/arduino/arduino-cli/commands/cmderrors"
 	"github.com/arduino/arduino-cli/internal/arduino/cores"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
-	"go.bug.st/downloader/v2"
 	semver "go.bug.st/relaxed-semver"
 )
 
@@ -120,21 +119,21 @@ func (pme *Explorer) FindPlatformReleaseDependencies(item *PlatformReference) (*
 
 // DownloadToolRelease downloads a ToolRelease. If the tool is already downloaded a nil Downloader
 // is returned. Uses the given downloader configuration for download, or the default config if nil.
-func (pme *Explorer) DownloadToolRelease(tool *cores.ToolRelease, config *downloader.Config, progressCB rpc.DownloadProgressCB) error {
+func (pme *Explorer) DownloadToolRelease(tool *cores.ToolRelease, progressCB rpc.DownloadProgressCB) error {
 	resource := tool.GetCompatibleFlavour()
 	if resource == nil {
 		return &cmderrors.FailedDownloadError{
 			Message: tr("Error downloading tool %s", tool),
 			Cause:   errors.New(tr("no versions available for the current OS, try contacting %s", tool.Tool.Package.Email))}
 	}
-	return resource.Download(pme.DownloadDir, config, tool.String(), progressCB, "")
+	return resource.Download(pme.DownloadDir, pme.downloaderConfig, tool.String(), progressCB, "")
 }
 
 // DownloadPlatformRelease downloads a PlatformRelease. If the platform is already downloaded a
 // nil Downloader is returned.
-func (pme *Explorer) DownloadPlatformRelease(platform *cores.PlatformRelease, config *downloader.Config, progressCB rpc.DownloadProgressCB) error {
+func (pme *Explorer) DownloadPlatformRelease(platform *cores.PlatformRelease, progressCB rpc.DownloadProgressCB) error {
 	if platform.Resource == nil {
 		return &cmderrors.PlatformNotFoundError{Platform: platform.String()}
 	}
-	return platform.Resource.Download(pme.DownloadDir, config, platform.String(), progressCB, "")
+	return platform.Resource.Download(pme.DownloadDir, pme.downloaderConfig, platform.String(), progressCB, "")
 }
diff --git a/internal/arduino/cores/packagemanager/install_uninstall.go b/internal/arduino/cores/packagemanager/install_uninstall.go
index bc29f08fa43..4b8c1cba9b4 100644
--- a/internal/arduino/cores/packagemanager/install_uninstall.go
+++ b/internal/arduino/cores/packagemanager/install_uninstall.go
@@ -92,11 +92,11 @@ func (pme *Explorer) DownloadAndInstallPlatformAndTools(
 	// Package download
 	taskCB(&rpc.TaskProgress{Name: tr("Downloading packages")})
 	for _, tool := range toolsToInstall {
-		if err := pme.DownloadToolRelease(tool, nil, downloadCB); err != nil {
+		if err := pme.DownloadToolRelease(tool, downloadCB); err != nil {
 			return err
 		}
 	}
-	if err := pme.DownloadPlatformRelease(platformRelease, nil, downloadCB); err != nil {
+	if err := pme.DownloadPlatformRelease(platformRelease, downloadCB); err != nil {
 		return err
 	}
 	taskCB(&rpc.TaskProgress{Completed: true})
diff --git a/internal/arduino/cores/packagemanager/loader.go b/internal/arduino/cores/packagemanager/loader.go
index 68c603044e5..8b42697a6e4 100644
--- a/internal/arduino/cores/packagemanager/loader.go
+++ b/internal/arduino/cores/packagemanager/loader.go
@@ -24,7 +24,6 @@ import (
 
 	"github.com/arduino/arduino-cli/commands/cmderrors"
 	"github.com/arduino/arduino-cli/internal/arduino/cores"
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/arduino/go-paths-helper"
 	properties "github.com/arduino/go-properties-orderedmap"
 	semver "go.bug.st/relaxed-semver"
@@ -32,7 +31,13 @@ import (
 
 // LoadHardware read all plaforms from the configured paths
 func (pm *Builder) LoadHardware() []error {
-	hardwareDirs := configuration.HardwareDirectories(configuration.Settings)
+	hardwareDirs := paths.NewPathList()
+	if pm.PackagesDir.IsDir() {
+		hardwareDirs.Add(pm.PackagesDir)
+	}
+	if pm.userPackagesDir != nil && pm.userPackagesDir.IsDir() {
+		hardwareDirs.Add(pm.userPackagesDir)
+	}
 	return pm.LoadHardwareFromDirectories(hardwareDirs)
 }
 
diff --git a/internal/arduino/cores/packagemanager/loader_test.go b/internal/arduino/cores/packagemanager/loader_test.go
index d0d41992179..5d394280aa8 100644
--- a/internal/arduino/cores/packagemanager/loader_test.go
+++ b/internal/arduino/cores/packagemanager/loader_test.go
@@ -21,6 +21,7 @@ import (
 	"github.com/arduino/go-paths-helper"
 	"github.com/arduino/go-properties-orderedmap"
 	"github.com/stretchr/testify/require"
+	"go.bug.st/downloader/v2"
 	semver "go.bug.st/relaxed-semver"
 )
 
@@ -174,7 +175,7 @@ func TestLoadDiscoveries(t *testing.T) {
 	defer fakePath.RemoveAll()
 
 	createTestPackageManager := func() *PackageManager {
-		pmb := NewBuilder(fakePath, fakePath, fakePath, fakePath, "test")
+		pmb := NewBuilder(fakePath, fakePath, nil, fakePath, fakePath, "test", downloader.GetDefaultConfig())
 		pack := pmb.packages.GetOrCreatePackage("arduino")
 		// ble-discovery tool
 		tool := pack.GetOrCreateTool("ble-discovery")
diff --git a/internal/arduino/cores/packagemanager/package_manager.go b/internal/arduino/cores/packagemanager/package_manager.go
index cea5e799d1b..ffe278a6205 100644
--- a/internal/arduino/cores/packagemanager/package_manager.go
+++ b/internal/arduino/cores/packagemanager/package_manager.go
@@ -33,12 +33,12 @@ import (
 	"github.com/arduino/arduino-cli/internal/arduino/cores/packageindex"
 	"github.com/arduino/arduino-cli/internal/arduino/discovery/discoverymanager"
 	"github.com/arduino/arduino-cli/internal/arduino/sketch"
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/arduino/arduino-cli/internal/i18n"
 	paths "github.com/arduino/go-paths-helper"
 	properties "github.com/arduino/go-properties-orderedmap"
 	"github.com/arduino/go-timeutils"
 	"github.com/sirupsen/logrus"
+	"go.bug.st/downloader/v2"
 	semver "go.bug.st/relaxed-semver"
 )
 
@@ -55,11 +55,13 @@ type PackageManager struct {
 	log              logrus.FieldLogger
 	IndexDir         *paths.Path
 	PackagesDir      *paths.Path
+	userPackagesDir  *paths.Path
 	DownloadDir      *paths.Path
 	tempDir          *paths.Path
 	profile          *sketch.Profile
 	discoveryManager *discoverymanager.DiscoveryManager
 	userAgent        string
+	downloaderConfig downloader.Config
 }
 
 // Builder is used to create a new PackageManager. The builder
@@ -75,17 +77,19 @@ type Explorer PackageManager
 var tr = i18n.Tr
 
 // NewBuilder returns a new Builder
-func NewBuilder(indexDir, packagesDir, downloadDir, tempDir *paths.Path, userAgent string) *Builder {
+func NewBuilder(indexDir, packagesDir, userPackagesDir, downloadDir, tempDir *paths.Path, userAgent string, downloaderConfig downloader.Config) *Builder {
 	return &Builder{
 		log:                            logrus.StandardLogger(),
 		packages:                       cores.NewPackages(),
 		IndexDir:                       indexDir,
 		PackagesDir:                    packagesDir,
+		userPackagesDir:                userPackagesDir,
 		DownloadDir:                    downloadDir,
 		tempDir:                        tempDir,
 		packagesCustomGlobalProperties: properties.NewMap(),
-		discoveryManager:               discoverymanager.New(configuration.UserAgent(configuration.Settings)),
+		discoveryManager:               discoverymanager.New(userAgent),
 		userAgent:                      userAgent,
+		downloaderConfig:               downloaderConfig,
 	}
 }
 
@@ -98,6 +102,7 @@ func (pmb *Builder) BuildIntoExistingPackageManager(target *PackageManager) {
 	target.packages = pmb.packages
 	target.IndexDir = pmb.IndexDir
 	target.PackagesDir = pmb.PackagesDir
+	target.userPackagesDir = pmb.userPackagesDir
 	target.DownloadDir = pmb.DownloadDir
 	target.tempDir = pmb.tempDir
 	target.packagesCustomGlobalProperties = pmb.packagesCustomGlobalProperties
@@ -114,6 +119,7 @@ func (pmb *Builder) Build() *PackageManager {
 		packages:                       pmb.packages,
 		IndexDir:                       pmb.IndexDir,
 		PackagesDir:                    pmb.PackagesDir,
+		userPackagesDir:                pmb.userPackagesDir,
 		DownloadDir:                    pmb.DownloadDir,
 		tempDir:                        pmb.tempDir,
 		packagesCustomGlobalProperties: pmb.packagesCustomGlobalProperties,
@@ -164,7 +170,7 @@ func (pmb *Builder) calculateCompatibleReleases() {
 // this function will make the builder write the new configuration into this
 // PackageManager.
 func (pm *PackageManager) NewBuilder() (builder *Builder, commit func()) {
-	pmb := NewBuilder(pm.IndexDir, pm.PackagesDir, pm.DownloadDir, pm.tempDir, pm.userAgent)
+	pmb := NewBuilder(pm.IndexDir, pm.PackagesDir, pm.userPackagesDir, pm.DownloadDir, pm.tempDir, pm.userAgent, pm.downloaderConfig)
 	return pmb, func() {
 		pmb.calculateCompatibleReleases()
 		pmb.BuildIntoExistingPackageManager(pm)
@@ -188,6 +194,7 @@ func (pm *PackageManager) NewExplorer() (explorer *Explorer, release func()) {
 		profile:                        pm.profile,
 		discoveryManager:               pm.discoveryManager,
 		userAgent:                      pm.userAgent,
+		downloaderConfig:               pm.downloaderConfig,
 	}, pm.packagesLock.RUnlock
 }
 
diff --git a/internal/arduino/cores/packagemanager/package_manager_test.go b/internal/arduino/cores/packagemanager/package_manager_test.go
index 055a6d332d0..595d08d0c21 100644
--- a/internal/arduino/cores/packagemanager/package_manager_test.go
+++ b/internal/arduino/cores/packagemanager/package_manager_test.go
@@ -24,10 +24,10 @@ import (
 	"testing"
 
 	"github.com/arduino/arduino-cli/internal/arduino/cores"
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/arduino/go-paths-helper"
 	"github.com/arduino/go-properties-orderedmap"
 	"github.com/stretchr/testify/require"
+	"go.bug.st/downloader/v2"
 	semver "go.bug.st/relaxed-semver"
 )
 
@@ -38,7 +38,7 @@ var dataDir1 = paths.New("testdata", "data_dir_1")
 var extraHardware = paths.New("testdata", "extra_hardware")
 
 func TestFindBoardWithFQBN(t *testing.T) {
-	pmb := NewBuilder(customHardware, customHardware, customHardware, customHardware, "test")
+	pmb := NewBuilder(customHardware, customHardware, nil, customHardware, customHardware, "test", downloader.GetDefaultConfig())
 	pmb.LoadHardwareFromDirectory(customHardware)
 	pm := pmb.Build()
 	pme, release := pm.NewExplorer()
@@ -56,7 +56,7 @@ func TestFindBoardWithFQBN(t *testing.T) {
 
 func TestResolveFQBN(t *testing.T) {
 	// Pass nil, since these paths are only used for installing
-	pmb := NewBuilder(nil, nil, nil, nil, "test")
+	pmb := NewBuilder(nil, nil, nil, nil, nil, "test", downloader.GetDefaultConfig())
 	// Hardware from main packages directory
 	pmb.LoadHardwareFromDirectory(dataDir1.Join("packages"))
 	// This contains the arduino:avr core
@@ -341,7 +341,7 @@ func TestResolveFQBN(t *testing.T) {
 }
 
 func TestBoardOptionsFunctions(t *testing.T) {
-	pmb := NewBuilder(customHardware, customHardware, customHardware, customHardware, "test")
+	pmb := NewBuilder(customHardware, customHardware, nil, customHardware, customHardware, "test", downloader.GetDefaultConfig())
 	pmb.LoadHardwareFromDirectory(customHardware)
 	pm := pmb.Build()
 	pme, release := pm.NewExplorer()
@@ -381,7 +381,7 @@ func TestBoardOptionsFunctions(t *testing.T) {
 }
 
 func TestBoardOrdering(t *testing.T) {
-	pmb := NewBuilder(dataDir1, dataDir1.Join("packages"), nil, nil, "")
+	pmb := NewBuilder(dataDir1, dataDir1.Join("packages"), nil, nil, nil, "", downloader.GetDefaultConfig())
 	_ = pmb.LoadHardwareFromDirectories(paths.NewPathList(dataDir1.Join("packages").String()))
 	pm := pmb.Build()
 	pme, release := pm.NewExplorer()
@@ -432,13 +432,14 @@ func TestBoardOrdering(t *testing.T) {
 
 func TestFindToolsRequiredForBoard(t *testing.T) {
 	t.Setenv("ARDUINO_DATA_DIR", dataDir1.String())
-	configuration.Settings = configuration.Init("")
 	pmb := NewBuilder(
 		dataDir1,
-		configuration.PackagesDir(configuration.Settings),
-		configuration.DownloadsDir(configuration.Settings),
+		dataDir1.Join("packages"),
+		nil,
+		dataDir1.Join("staging"),
 		dataDir1,
 		"test",
+		downloader.GetDefaultConfig(),
 	)
 
 	loadIndex := func(addr string) {
@@ -567,7 +568,7 @@ func TestFindToolsRequiredForBoard(t *testing.T) {
 }
 
 func TestIdentifyBoard(t *testing.T) {
-	pmb := NewBuilder(customHardware, customHardware, customHardware, customHardware, "test")
+	pmb := NewBuilder(customHardware, customHardware, nil, customHardware, customHardware, "test", downloader.GetDefaultConfig())
 	pmb.LoadHardwareFromDirectory(customHardware)
 	pm := pmb.Build()
 	pme, release := pm.NewExplorer()
@@ -594,12 +595,12 @@ func TestIdentifyBoard(t *testing.T) {
 
 func TestPackageManagerClear(t *testing.T) {
 	// Create a PackageManager and load the harware
-	pmb := NewBuilder(customHardware, customHardware, customHardware, customHardware, "test")
+	pmb := NewBuilder(customHardware, customHardware, nil, customHardware, customHardware, "test", downloader.GetDefaultConfig())
 	pmb.LoadHardwareFromDirectory(customHardware)
 	pm := pmb.Build()
 
 	// Creates another PackageManager but don't load the hardware
-	emptyPmb := NewBuilder(customHardware, customHardware, customHardware, customHardware, "test")
+	emptyPmb := NewBuilder(customHardware, customHardware, nil, customHardware, customHardware, "test", downloader.GetDefaultConfig())
 	emptyPm := emptyPmb.Build()
 
 	// Verifies they're not equal
@@ -621,7 +622,7 @@ func TestFindToolsRequiredFromPlatformRelease(t *testing.T) {
 	require.NoError(t, err)
 	defer fakePath.RemoveAll()
 
-	pmb := NewBuilder(fakePath, fakePath, fakePath, fakePath, "test")
+	pmb := NewBuilder(fakePath, fakePath, nil, fakePath, fakePath, "test", downloader.GetDefaultConfig())
 	pack := pmb.GetOrCreatePackage("arduino")
 
 	{
@@ -742,7 +743,7 @@ func TestFindToolsRequiredFromPlatformRelease(t *testing.T) {
 }
 
 func TestFindPlatformReleaseDependencies(t *testing.T) {
-	pmb := NewBuilder(nil, nil, nil, nil, "test")
+	pmb := NewBuilder(nil, nil, nil, nil, nil, "test", downloader.GetDefaultConfig())
 	pmb.LoadPackageIndexFromFile(paths.New("testdata", "package_tooltest_index.json"))
 	pmb.calculateCompatibleReleases()
 	pm := pmb.Build()
@@ -758,7 +759,7 @@ func TestFindPlatformReleaseDependencies(t *testing.T) {
 
 func TestLegacyPackageConversionToPluggableDiscovery(t *testing.T) {
 	// Pass nil, since these paths are only used for installing
-	pmb := NewBuilder(nil, nil, nil, nil, "test")
+	pmb := NewBuilder(nil, nil, nil, nil, nil, "test", downloader.GetDefaultConfig())
 	// Hardware from main packages directory
 	pmb.LoadHardwareFromDirectory(dataDir1.Join("packages"))
 	pm := pmb.Build()
@@ -828,7 +829,7 @@ func TestLegacyPackageConversionToPluggableDiscovery(t *testing.T) {
 
 func TestVariantAndCoreSelection(t *testing.T) {
 	// Pass nil, since these paths are only used for installing
-	pmb := NewBuilder(nil, nil, nil, nil, "test")
+	pmb := NewBuilder(nil, nil, nil, nil, nil, "test", downloader.GetDefaultConfig())
 	// Hardware from main packages directory
 	pmb.LoadHardwareFromDirectory(dataDir1.Join("packages"))
 	pm := pmb.Build()
@@ -923,7 +924,7 @@ func TestVariantAndCoreSelection(t *testing.T) {
 }
 
 func TestRunScript(t *testing.T) {
-	pmb := NewBuilder(nil, nil, nil, nil, "test")
+	pmb := NewBuilder(nil, nil, nil, nil, nil, "test", downloader.GetDefaultConfig())
 	pm := pmb.Build()
 	pme, release := pm.NewExplorer()
 	defer release()
diff --git a/internal/arduino/cores/packagemanager/profiles.go b/internal/arduino/cores/packagemanager/profiles.go
index 2422766e899..9d3bfba047a 100644
--- a/internal/arduino/cores/packagemanager/profiles.go
+++ b/internal/arduino/cores/packagemanager/profiles.go
@@ -16,6 +16,7 @@
 package packagemanager
 
 import (
+	"context"
 	"fmt"
 	"net/url"
 
@@ -32,7 +33,7 @@ import (
 
 // LoadHardwareForProfile load the hardware platforms for the given profile.
 // If installMissing is true then possibly missing tools and platforms will be downloaded and installed.
-func (pmb *Builder) LoadHardwareForProfile(p *sketch.Profile, installMissing bool, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) []error {
+func (pmb *Builder) LoadHardwareForProfile(ctx context.Context, p *sketch.Profile, installMissing bool, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB, settings *configuration.Settings) []error {
 	pmb.profile = p
 
 	// Load required platforms
@@ -40,7 +41,7 @@ func (pmb *Builder) LoadHardwareForProfile(p *sketch.Profile, installMissing boo
 	var platformReleases []*cores.PlatformRelease
 	indexURLs := map[string]*url.URL{}
 	for _, platformRef := range p.Platforms {
-		if platformRelease, err := pmb.loadProfilePlatform(platformRef, installMissing, downloadCB, taskCB); err != nil {
+		if platformRelease, err := pmb.loadProfilePlatform(ctx, platformRef, installMissing, downloadCB, taskCB, settings); err != nil {
 			merr = append(merr, fmt.Errorf("%s: %w", tr("loading required platform %s", platformRef), err))
 			logrus.WithField("platform", platformRef).WithError(err).Debugf("Error loading platform for profile")
 		} else {
@@ -56,7 +57,7 @@ func (pmb *Builder) LoadHardwareForProfile(p *sketch.Profile, installMissing boo
 
 		for _, toolDep := range platformRelease.ToolDependencies {
 			indexURL := indexURLs[toolDep.ToolPackager]
-			if err := pmb.loadProfileTool(toolDep, indexURL, installMissing, downloadCB, taskCB); err != nil {
+			if err := pmb.loadProfileTool(toolDep, indexURL, installMissing, downloadCB, taskCB, settings); err != nil {
 				merr = append(merr, fmt.Errorf("%s: %w", tr("loading required tool %s", toolDep), err))
 				logrus.WithField("tool", toolDep).WithField("index_url", indexURL).WithError(err).Debugf("Error loading tool for profile")
 			} else {
@@ -68,30 +69,30 @@ func (pmb *Builder) LoadHardwareForProfile(p *sketch.Profile, installMissing boo
 	return merr
 }
 
-func (pmb *Builder) loadProfilePlatform(platformRef *sketch.ProfilePlatformReference, installMissing bool, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) (*cores.PlatformRelease, error) {
+func (pmb *Builder) loadProfilePlatform(ctx context.Context, platformRef *sketch.ProfilePlatformReference, installMissing bool, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB, settings *configuration.Settings) (*cores.PlatformRelease, error) {
 	targetPackage := pmb.packages.GetOrCreatePackage(platformRef.Packager)
 	platform := targetPackage.GetOrCreatePlatform(platformRef.Architecture)
 	release := platform.GetOrCreateRelease(platformRef.Version)
 
 	uid := platformRef.InternalUniqueIdentifier()
-	destDir := configuration.ProfilesCacheDir(configuration.Settings).Join(uid)
+	destDir := settings.ProfilesCacheDir().Join(uid)
 	if !destDir.IsDir() && installMissing {
 		// Try installing the missing platform
-		if err := pmb.installMissingProfilePlatform(platformRef, destDir, downloadCB, taskCB); err != nil {
+		if err := pmb.installMissingProfilePlatform(ctx, platformRef, destDir, downloadCB, taskCB); err != nil {
 			return nil, err
 		}
 	}
 	return release, pmb.loadPlatformRelease(release, destDir)
 }
 
-func (pmb *Builder) installMissingProfilePlatform(platformRef *sketch.ProfilePlatformReference, destDir *paths.Path, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) error {
+func (pmb *Builder) installMissingProfilePlatform(ctx context.Context, platformRef *sketch.ProfilePlatformReference, destDir *paths.Path, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) error {
 	// Instantiate a temporary package manager only for platform installation
 	_ = pmb.tempDir.MkdirAll()
 	tmp, err := paths.MkTempDir(pmb.tempDir.String(), "")
 	if err != nil {
 		return fmt.Errorf("installing missing platform: could not create temp dir %s", err)
 	}
-	tmpPmb := NewBuilder(tmp, tmp, pmb.DownloadDir, tmp, pmb.userAgent)
+	tmpPmb := NewBuilder(tmp, tmp, nil, pmb.DownloadDir, tmp, pmb.userAgent, pmb.downloaderConfig)
 	defer tmp.RemoveAll()
 
 	// Download the main index and parse it
@@ -103,7 +104,7 @@ func (pmb *Builder) installMissingProfilePlatform(platformRef *sketch.ProfilePla
 	}
 	for _, indexURL := range indexesToDownload {
 		indexResource := resources.IndexResource{URL: indexURL}
-		if err := indexResource.Download(tmpPmb.IndexDir, downloadCB); err != nil {
+		if err := indexResource.Download(ctx, tmpPmb.IndexDir, downloadCB, pmb.downloaderConfig); err != nil {
 			taskCB(&rpc.TaskProgress{Name: tr("Error downloading %s", indexURL)})
 			return &cmderrors.FailedDownloadError{Message: tr("Error downloading %s", indexURL), Cause: err}
 		}
@@ -121,7 +122,7 @@ func (pmb *Builder) installMissingProfilePlatform(platformRef *sketch.ProfilePla
 	tmpPme, tmpRelease := tmpPm.NewExplorer()
 	defer tmpRelease()
 
-	if err := tmpPme.DownloadPlatformRelease(tmpPlatformRelease, nil, downloadCB); err != nil {
+	if err := tmpPme.DownloadPlatformRelease(tmpPlatformRelease, downloadCB); err != nil {
 		taskCB(&rpc.TaskProgress{Name: tr("Error downloading platform %s", tmpPlatformRelease)})
 		return &cmderrors.FailedInstallError{Message: tr("Error downloading platform %s", tmpPlatformRelease), Cause: err}
 	}
@@ -137,12 +138,12 @@ func (pmb *Builder) installMissingProfilePlatform(platformRef *sketch.ProfilePla
 	return nil
 }
 
-func (pmb *Builder) loadProfileTool(toolRef *cores.ToolDependency, indexURL *url.URL, installMissing bool, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) error {
+func (pmb *Builder) loadProfileTool(toolRef *cores.ToolDependency, indexURL *url.URL, installMissing bool, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB, settings *configuration.Settings) error {
 	targetPackage := pmb.packages.GetOrCreatePackage(toolRef.ToolPackager)
 	tool := targetPackage.GetOrCreateTool(toolRef.ToolName)
 
 	uid := toolRef.InternalUniqueIdentifier(indexURL)
-	destDir := configuration.ProfilesCacheDir(configuration.Settings).Join(uid)
+	destDir := settings.ProfilesCacheDir().Join(uid)
 
 	if !destDir.IsDir() && installMissing {
 		// Try installing the missing tool
@@ -172,7 +173,7 @@ func (pmb *Builder) installMissingProfileTool(toolRelease *cores.ToolRelease, de
 		return &cmderrors.InvalidVersionError{Cause: fmt.Errorf(tr("version %s not available for this operating system", toolRelease))}
 	}
 	taskCB(&rpc.TaskProgress{Name: tr("Downloading tool %s", toolRelease)})
-	if err := toolResource.Download(pmb.DownloadDir, nil, toolRelease.String(), downloadCB, ""); err != nil {
+	if err := toolResource.Download(pmb.DownloadDir, pmb.downloaderConfig, toolRelease.String(), downloadCB, ""); err != nil {
 		taskCB(&rpc.TaskProgress{Name: tr("Error downloading tool %s", toolRelease)})
 		return &cmderrors.FailedInstallError{Message: tr("Error installing tool %s", toolRelease), Cause: err}
 	}
diff --git a/internal/arduino/httpclient/httpclient.go b/internal/arduino/httpclient/httpclient.go
index ec4b4acc4a6..0e904dddf8f 100644
--- a/internal/arduino/httpclient/httpclient.go
+++ b/internal/arduino/httpclient/httpclient.go
@@ -16,12 +16,9 @@
 package httpclient
 
 import (
-	"net/http"
-	"net/url"
 	"time"
 
 	"github.com/arduino/arduino-cli/commands/cmderrors"
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/arduino/arduino-cli/internal/i18n"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/arduino/go-paths-helper"
@@ -34,7 +31,7 @@ var tr = i18n.Tr
 // DownloadFile downloads a file from a URL into the specified path. An optional config and options may be passed (or nil to use the defaults).
 // A DownloadProgressCB callback function must be passed to monitor download progress.
 // If a not empty queryParameter is passed, it is appended to the URL for analysis purposes.
-func DownloadFile(path *paths.Path, URL string, queryParameter string, label string, downloadCB rpc.DownloadProgressCB, config *downloader.Config, options ...downloader.DownloadOptions) (returnedError error) {
+func DownloadFile(path *paths.Path, URL string, queryParameter string, label string, downloadCB rpc.DownloadProgressCB, config downloader.Config, options ...downloader.DownloadOptions) (returnedError error) {
 	if queryParameter != "" {
 		URL = URL + "?query=" + queryParameter
 	}
@@ -48,15 +45,7 @@ func DownloadFile(path *paths.Path, URL string, queryParameter string, label str
 		}
 	}()
 
-	if config == nil {
-		c, err := GetDownloaderConfig()
-		if err != nil {
-			return err
-		}
-		config = c
-	}
-
-	d, err := downloader.DownloadWithConfig(path.String(), URL, *config, options...)
+	d, err := downloader.DownloadWithConfig(path.String(), URL, config, options...)
 	if err != nil {
 		return err
 	}
@@ -76,52 +65,3 @@ func DownloadFile(path *paths.Path, URL string, queryParameter string, label str
 
 	return nil
 }
-
-// Config is the configuration of the http client
-type Config struct {
-	UserAgent string
-	Proxy     *url.URL
-}
-
-// New returns a default http client for use in the arduino-cli
-func New() (*http.Client, error) {
-	userAgent := configuration.UserAgent(configuration.Settings)
-	proxy, err := configuration.NetworkProxy(configuration.Settings)
-	if err != nil {
-		return nil, err
-	}
-	return NewWithConfig(&Config{UserAgent: userAgent, Proxy: proxy}), nil
-}
-
-// NewWithConfig creates a http client for use in the arduino-cli, with a given configuration
-func NewWithConfig(config *Config) *http.Client {
-	return &http.Client{
-		Transport: &httpClientRoundTripper{
-			transport: &http.Transport{
-				Proxy: http.ProxyURL(config.Proxy),
-			},
-			userAgent: config.UserAgent,
-		},
-	}
-}
-
-// GetDownloaderConfig returns the downloader configuration based on current settings.
-func GetDownloaderConfig() (*downloader.Config, error) {
-	httpClient, err := New()
-	if err != nil {
-		return nil, &cmderrors.InvalidArgumentError{Message: tr("Could not connect via HTTP"), Cause: err}
-	}
-	return &downloader.Config{
-		HttpClient: *httpClient,
-	}, nil
-}
-
-type httpClientRoundTripper struct {
-	transport http.RoundTripper
-	userAgent string
-}
-
-func (h *httpClientRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
-	req.Header.Add("User-Agent", h.userAgent)
-	return h.transport.RoundTrip(req)
-}
diff --git a/internal/arduino/resources/download.go b/internal/arduino/resources/download.go
index 4f1df1ad5b3..19b37df48ac 100644
--- a/internal/arduino/resources/download.go
+++ b/internal/arduino/resources/download.go
@@ -28,7 +28,7 @@ import (
 // Download performs a download loop using the provided downloader.Config.
 // Messages are passed back to the DownloadProgressCB using label as text for the File field.
 // queryParameter is passed for analysis purposes.
-func (r *DownloadResource) Download(downloadDir *paths.Path, config *downloader.Config, label string, downloadCB rpc.DownloadProgressCB, queryParameter string) error {
+func (r *DownloadResource) Download(downloadDir *paths.Path, config downloader.Config, label string, downloadCB rpc.DownloadProgressCB, queryParameter string) error {
 	path, err := r.ArchivePath(downloadDir)
 	if err != nil {
 		return fmt.Errorf(tr("getting archive path: %s"), err)
diff --git a/internal/arduino/resources/helpers_test.go b/internal/arduino/resources/helpers_test.go
index e56747d8b40..75961bec26f 100644
--- a/internal/arduino/resources/helpers_test.go
+++ b/internal/arduino/resources/helpers_test.go
@@ -22,11 +22,10 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/arduino/arduino-cli/internal/arduino/httpclient"
+	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/arduino/go-paths-helper"
 	"github.com/stretchr/testify/require"
-	"go.bug.st/downloader/v2"
 )
 
 type EchoHandler struct{}
@@ -37,8 +36,7 @@ func (h *EchoHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reques
 }
 
 func TestDownloadApplyUserAgentHeaderUsingConfig(t *testing.T) {
-	goldUserAgentValue := "arduino-cli/0.0.0-test.preview (amd64; linux; go1.12.4) Commit:deadbeef/Build:2019-06-12 11:11:11.111"
-	goldUserAgentString := "User-Agent: " + goldUserAgentValue
+	goldUserAgentValue := "arduino-cli/0.0.0-test.preview"
 
 	tmp, err := paths.MkTempDir("", "")
 	require.NoError(t, err)
@@ -54,9 +52,11 @@ func TestDownloadApplyUserAgentHeaderUsingConfig(t *testing.T) {
 		URL:             srv.URL,
 	}
 
-	httpClient := httpclient.NewWithConfig(&httpclient.Config{UserAgent: goldUserAgentValue})
-
-	err = r.Download(tmp, &downloader.Config{HttpClient: *httpClient}, "", func(progress *rpc.DownloadProgress) {}, "")
+	settings := configuration.NewSettings()
+	settings.Set("network.user_agent_ext", goldUserAgentValue)
+	config, err := settings.DownloaderConfig()
+	require.NoError(t, err)
+	err = r.Download(tmp, config, "", func(progress *rpc.DownloadProgress) {}, "")
 	require.NoError(t, err)
 
 	// leverage the download helper to download the echo for the request made by the downloader itself
@@ -71,12 +71,11 @@ func TestDownloadApplyUserAgentHeaderUsingConfig(t *testing.T) {
 	require.NoError(t, err)
 
 	requestLines := strings.Split(string(b), "\r\n")
-	userAgentHeaderString := ""
+	userAgentHeader := ""
 	for _, line := range requestLines {
 		if strings.Contains(line, "User-Agent: ") {
-			userAgentHeaderString = line
+			userAgentHeader = line
 		}
 	}
-	require.Equal(t, goldUserAgentString, userAgentHeaderString)
-
+	require.Contains(t, userAgentHeader, goldUserAgentValue)
 }
diff --git a/internal/arduino/resources/index.go b/internal/arduino/resources/index.go
index 4740c6f12f0..e9198905e5e 100644
--- a/internal/arduino/resources/index.go
+++ b/internal/arduino/resources/index.go
@@ -58,7 +58,7 @@ func (res *IndexResource) IndexFileName() (string, error) {
 
 // Download will download the index and possibly check the signature using the Arduino's public key.
 // If the file is in .gz format it will be unpacked first.
-func (res *IndexResource) Download(destDir *paths.Path, downloadCB rpc.DownloadProgressCB) error {
+func (res *IndexResource) Download(ctx context.Context, destDir *paths.Path, downloadCB rpc.DownloadProgressCB, config downloader.Config) error {
 	// Create destination directory
 	if err := destDir.MkdirAll(); err != nil {
 		return &cmderrors.PermissionDeniedError{Message: tr("Can't create data directory %s", destDir), Cause: err}
@@ -78,7 +78,7 @@ func (res *IndexResource) Download(destDir *paths.Path, downloadCB rpc.DownloadP
 		return err
 	}
 	tmpIndexPath := tmp.Join(downloadFileName)
-	if err := httpclient.DownloadFile(tmpIndexPath, res.URL.String(), "", tr("Downloading index: %s", downloadFileName), downloadCB, nil, downloader.NoResume); err != nil {
+	if err := httpclient.DownloadFile(tmpIndexPath, res.URL.String(), "", tr("Downloading index: %s", downloadFileName), downloadCB, config, downloader.NoResume); err != nil {
 		return &cmderrors.FailedDownloadError{Message: tr("Error downloading index '%s'", res.URL), Cause: err}
 	}
 
@@ -100,7 +100,7 @@ func (res *IndexResource) Download(destDir *paths.Path, downloadCB rpc.DownloadP
 		defer f.Close()
 		tmpArchivePath := tmp.Join("archive")
 		_ = tmpArchivePath.MkdirAll()
-		if err := extract.Bz2(context.Background(), f, tmpArchivePath.String(), nil); err != nil {
+		if err := extract.Bz2(ctx, f, tmpArchivePath.String(), nil); err != nil {
 			return &cmderrors.PermissionDeniedError{Message: tr("Error extracting %s", tmpIndexPath), Cause: err}
 		}
 
@@ -133,7 +133,7 @@ func (res *IndexResource) Download(destDir *paths.Path, downloadCB rpc.DownloadP
 		// Download signature
 		signaturePath = destDir.Join(signatureFileName)
 		tmpSignaturePath = tmp.Join(signatureFileName)
-		if err := httpclient.DownloadFile(tmpSignaturePath, res.SignatureURL.String(), "", tr("Downloading index signature: %s", signatureFileName), downloadCB, nil, downloader.NoResume); err != nil {
+		if err := httpclient.DownloadFile(tmpSignaturePath, res.SignatureURL.String(), "", tr("Downloading index signature: %s", signatureFileName), downloadCB, config, downloader.NoResume); err != nil {
 			return &cmderrors.FailedDownloadError{Message: tr("Error downloading index signature '%s'", res.SignatureURL), Cause: err}
 		}
 
diff --git a/internal/arduino/resources/resources_test.go b/internal/arduino/resources/resources_test.go
index 9cf8ae54509..3e218c1f89d 100644
--- a/internal/arduino/resources/resources_test.go
+++ b/internal/arduino/resources/resources_test.go
@@ -16,6 +16,7 @@
 package resources
 
 import (
+	"context"
 	"crypto"
 	"encoding/hex"
 	"fmt"
@@ -49,7 +50,7 @@ func TestDownloadAndChecksums(t *testing.T) {
 	require.NoError(t, err)
 
 	downloadAndTestChecksum := func() {
-		err := r.Download(tmp, &downloader.Config{}, "", func(*rpc.DownloadProgress) {}, "")
+		err := r.Download(tmp, downloader.Config{}, "", func(*rpc.DownloadProgress) {}, "")
 		require.NoError(t, err)
 
 		data, err := testFile.ReadFile()
@@ -63,7 +64,7 @@ func TestDownloadAndChecksums(t *testing.T) {
 	downloadAndTestChecksum()
 
 	// Download with cached file
-	err = r.Download(tmp, &downloader.Config{}, "", func(*rpc.DownloadProgress) {}, "")
+	err = r.Download(tmp, downloader.Config{}, "", func(*rpc.DownloadProgress) {}, "")
 	require.NoError(t, err)
 
 	// Download if cached file has data in excess (redownload)
@@ -116,6 +117,7 @@ func TestDownloadAndChecksums(t *testing.T) {
 }
 
 func TestIndexDownloadAndSignatureWithinArchive(t *testing.T) {
+	ctx := context.Background()
 	// Spawn test webserver
 	mux := http.NewServeMux()
 	fs := http.FileServer(http.Dir("testdata"))
@@ -132,7 +134,7 @@ func TestIndexDownloadAndSignatureWithinArchive(t *testing.T) {
 	destDir, err := paths.MkTempDir("", "")
 	require.NoError(t, err)
 	defer destDir.RemoveAll()
-	err = idxResource.Download(destDir, func(curr *rpc.DownloadProgress) {})
+	err = idxResource.Download(ctx, destDir, func(curr *rpc.DownloadProgress) {}, downloader.GetDefaultConfig())
 	require.NoError(t, err)
 	require.True(t, destDir.Join("package_index.json").Exist())
 	require.True(t, destDir.Join("package_index.json.sig").Exist())
@@ -143,7 +145,7 @@ func TestIndexDownloadAndSignatureWithinArchive(t *testing.T) {
 	invDestDir, err := paths.MkTempDir("", "")
 	require.NoError(t, err)
 	defer invDestDir.RemoveAll()
-	err = invIdxResource.Download(invDestDir, func(curr *rpc.DownloadProgress) {})
+	err = invIdxResource.Download(ctx, invDestDir, func(curr *rpc.DownloadProgress) {}, downloader.GetDefaultConfig())
 	require.Error(t, err)
 	require.Contains(t, err.Error(), "invalid signature")
 	require.False(t, invDestDir.Join("package_index.json").Exist())
diff --git a/internal/cli/arguments/completion.go b/internal/cli/arguments/completion.go
index 4243a0fd409..a8867c51cf2 100644
--- a/internal/cli/arguments/completion.go
+++ b/internal/cli/arguments/completion.go
@@ -18,10 +18,7 @@ package arguments
 import (
 	"context"
 
-	"github.com/arduino/arduino-cli/commands/board"
-	"github.com/arduino/arduino-cli/commands/core"
-	"github.com/arduino/arduino-cli/commands/lib"
-	"github.com/arduino/arduino-cli/commands/upload"
+	f "github.com/arduino/arduino-cli/internal/algorithms"
 	"github.com/arduino/arduino-cli/internal/cli/instance"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 )
@@ -29,10 +26,10 @@ import (
 // GetInstalledBoards is an helper function useful to autocomplete.
 // It returns a list of fqbn
 // it's taken from cli/board/listall.go
-func GetInstalledBoards() []string {
-	inst := instance.CreateAndInit()
+func GetInstalledBoards(ctx context.Context, srv rpc.ArduinoCoreServiceServer) []string {
+	inst := instance.CreateAndInit(ctx, srv)
 
-	list, _ := board.ListAll(context.Background(), &rpc.BoardListAllRequest{
+	list, _ := srv.BoardListAll(ctx, &rpc.BoardListAllRequest{
 		Instance:            inst,
 		SearchArgs:          nil,
 		IncludeHiddenBoards: false,
@@ -47,8 +44,8 @@ func GetInstalledBoards() []string {
 
 // GetInstalledProgrammers is an helper function useful to autocomplete.
 // It returns a list of programmers available based on the installed boards
-func GetInstalledProgrammers() []string {
-	inst := instance.CreateAndInit()
+func GetInstalledProgrammers(ctx context.Context, srv rpc.ArduinoCoreServiceServer) []string {
+	inst := instance.CreateAndInit(ctx, srv)
 
 	// we need the list of the available fqbn in order to get the list of the programmers
 	listAllReq := &rpc.BoardListAllRequest{
@@ -56,11 +53,11 @@ func GetInstalledProgrammers() []string {
 		SearchArgs:          nil,
 		IncludeHiddenBoards: false,
 	}
-	list, _ := board.ListAll(context.Background(), listAllReq)
+	list, _ := srv.BoardListAll(ctx, listAllReq)
 
 	installedProgrammers := make(map[string]string)
 	for _, board := range list.GetBoards() {
-		programmers, _ := upload.ListProgrammersAvailableForUpload(context.Background(), &rpc.ListProgrammersAvailableForUploadRequest{
+		programmers, _ := srv.ListProgrammersAvailableForUpload(ctx, &rpc.ListProgrammersAvailableForUploadRequest{
 			Instance: inst,
 			Fqbn:     board.GetFqbn(),
 		})
@@ -80,10 +77,10 @@ func GetInstalledProgrammers() []string {
 
 // GetUninstallableCores is an helper function useful to autocomplete.
 // It returns a list of cores which can be uninstalled
-func GetUninstallableCores() []string {
-	inst := instance.CreateAndInit()
+func GetUninstallableCores(ctx context.Context, srv rpc.ArduinoCoreServiceServer) []string {
+	inst := instance.CreateAndInit(ctx, srv)
 
-	platforms, _ := core.PlatformSearch(&rpc.PlatformSearchRequest{
+	platforms, _ := srv.PlatformSearch(ctx, &rpc.PlatformSearchRequest{
 		Instance:          inst,
 		ManuallyInstalled: true,
 	})
@@ -101,10 +98,10 @@ func GetUninstallableCores() []string {
 
 // GetInstallableCores is an helper function useful to autocomplete.
 // It returns a list of cores which can be installed/downloaded
-func GetInstallableCores() []string {
-	inst := instance.CreateAndInit()
+func GetInstallableCores(ctx context.Context, srv rpc.ArduinoCoreServiceServer) []string {
+	inst := instance.CreateAndInit(ctx, srv)
 
-	platforms, _ := core.PlatformSearch(&rpc.PlatformSearchRequest{
+	platforms, _ := srv.PlatformSearch(ctx, &rpc.PlatformSearchRequest{
 		Instance:   inst,
 		SearchArgs: "",
 	})
@@ -120,19 +117,19 @@ func GetInstallableCores() []string {
 
 // GetInstalledLibraries is an helper function useful to autocomplete.
 // It returns a list of libs which are currently installed, including the builtin ones
-func GetInstalledLibraries() []string {
-	return getLibraries(true)
+func GetInstalledLibraries(ctx context.Context, srv rpc.ArduinoCoreServiceServer) []string {
+	return getLibraries(ctx, srv, true)
 }
 
 // GetUninstallableLibraries is an helper function useful to autocomplete.
 // It returns a list of libs which can be uninstalled
-func GetUninstallableLibraries() []string {
-	return getLibraries(false)
+func GetUninstallableLibraries(ctx context.Context, srv rpc.ArduinoCoreServiceServer) []string {
+	return getLibraries(ctx, srv, false)
 }
 
-func getLibraries(all bool) []string {
-	inst := instance.CreateAndInit()
-	libs, _ := lib.LibraryList(context.Background(), &rpc.LibraryListRequest{
+func getLibraries(ctx context.Context, srv rpc.ArduinoCoreServiceServer, all bool) []string {
+	inst := instance.CreateAndInit(ctx, srv)
+	libs, _ := srv.LibraryList(ctx, &rpc.LibraryListRequest{
 		Instance:  inst,
 		All:       all,
 		Updatable: false,
@@ -149,10 +146,10 @@ func getLibraries(all bool) []string {
 
 // GetInstallableLibs is an helper function useful to autocomplete.
 // It returns a list of libs which can be installed/downloaded
-func GetInstallableLibs() []string {
-	inst := instance.CreateAndInit()
+func GetInstallableLibs(ctx context.Context, srv rpc.ArduinoCoreServiceServer) []string {
+	inst := instance.CreateAndInit(ctx, srv)
 
-	libs, _ := lib.LibrarySearch(context.Background(), &rpc.LibrarySearchRequest{
+	libs, _ := srv.LibrarySearch(ctx, &rpc.LibrarySearchRequest{
 		Instance:   inst,
 		SearchArgs: "", // if no query is specified all the libs are returned
 	})
@@ -167,16 +164,11 @@ func GetInstallableLibs() []string {
 // GetAvailablePorts is an helper function useful to autocomplete.
 // It returns a list of upload port of the boards which are currently connected.
 // It will not suggests network ports because the timeout is not set.
-func GetAvailablePorts() []*rpc.Port {
-	inst := instance.CreateAndInit()
+func GetAvailablePorts(ctx context.Context, srv rpc.ArduinoCoreServiceServer) []*rpc.Port {
+	// Get the port list
+	inst := instance.CreateAndInit(ctx, srv)
+	list, _ := srv.BoardList(ctx, &rpc.BoardListRequest{Instance: inst})
 
-	list, _, _ := board.List(&rpc.BoardListRequest{
-		Instance: inst,
-	})
-	var res []*rpc.Port
-	// transform the data structure for the completion
-	for _, i := range list {
-		res = append(res, i.GetPort())
-	}
-	return res
+	// Transform the data structure for the completion (DetectedPort -> Port)
+	return f.Map(list.GetPorts(), (*rpc.DetectedPort).GetPort)
 }
diff --git a/internal/cli/arguments/fqbn.go b/internal/cli/arguments/fqbn.go
index 01e1079d8ab..e1cb338c77d 100644
--- a/internal/cli/arguments/fqbn.go
+++ b/internal/cli/arguments/fqbn.go
@@ -16,6 +16,7 @@
 package arguments
 
 import (
+	"context"
 	"strings"
 
 	"github.com/arduino/arduino-cli/commands/cmderrors"
@@ -33,10 +34,10 @@ type Fqbn struct {
 }
 
 // AddToCommand adds the flags used to set fqbn to the specified Command
-func (f *Fqbn) AddToCommand(cmd *cobra.Command) {
+func (f *Fqbn) AddToCommand(cmd *cobra.Command, srv rpc.ArduinoCoreServiceServer) {
 	cmd.Flags().StringVarP(&f.fqbn, "fqbn", "b", "", tr("Fully Qualified Board Name, e.g.: arduino:avr:uno"))
 	cmd.RegisterFlagCompletionFunc("fqbn", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-		return GetInstalledBoards(), cobra.ShellCompDirectiveDefault
+		return GetInstalledBoards(cmd.Context(), srv), cobra.ShellCompDirectiveDefault
 	})
 	cmd.Flags().StringSliceVar(&f.boardOptions, "board-options", []string{},
 		tr("List of board options separated by commas. Or can be used multiple times for multiple options."))
@@ -69,7 +70,7 @@ func (f *Fqbn) Set(fqbn string) {
 //   - the port is not found, in this case nil is returned
 //   - the FQBN autodetection fail, in this case the function prints an error and
 //     terminates the execution
-func CalculateFQBNAndPort(portArgs *Port, fqbnArg *Fqbn, instance *rpc.Instance, defaultFQBN, defaultAddress, defaultProtocol string) (string, *rpc.Port) {
+func CalculateFQBNAndPort(ctx context.Context, portArgs *Port, fqbnArg *Fqbn, instance *rpc.Instance, srv rpc.ArduinoCoreServiceServer, defaultFQBN, defaultAddress, defaultProtocol string) (string, *rpc.Port) {
 	fqbn := fqbnArg.String()
 	if fqbn == "" {
 		fqbn = defaultFQBN
@@ -78,14 +79,14 @@ func CalculateFQBNAndPort(portArgs *Port, fqbnArg *Fqbn, instance *rpc.Instance,
 		if portArgs == nil || portArgs.address == "" {
 			feedback.FatalError(&cmderrors.MissingFQBNError{}, feedback.ErrGeneric)
 		}
-		fqbn, port := portArgs.DetectFQBN(instance)
+		fqbn, port := portArgs.DetectFQBN(ctx, instance, srv)
 		if fqbn == "" {
 			feedback.FatalError(&cmderrors.MissingFQBNError{}, feedback.ErrGeneric)
 		}
 		return fqbn, port
 	}
 
-	port, err := portArgs.GetPort(instance, defaultAddress, defaultProtocol)
+	port, err := portArgs.GetPort(ctx, instance, srv, defaultAddress, defaultProtocol)
 	if err != nil {
 		feedback.Fatal(tr("Error getting port metadata: %v", err), feedback.ErrGeneric)
 	}
diff --git a/internal/cli/arguments/port.go b/internal/cli/arguments/port.go
index f10c6bf70a0..1d138043fbc 100644
--- a/internal/cli/arguments/port.go
+++ b/internal/cli/arguments/port.go
@@ -20,7 +20,7 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/arduino/arduino-cli/commands/board"
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/commands/cmderrors"
 	f "github.com/arduino/arduino-cli/internal/algorithms"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
@@ -39,14 +39,14 @@ type Port struct {
 }
 
 // AddToCommand adds the flags used to set port and protocol to the specified Command
-func (p *Port) AddToCommand(cmd *cobra.Command) {
+func (p *Port) AddToCommand(cmd *cobra.Command, srv rpc.ArduinoCoreServiceServer) {
 	cmd.Flags().StringVarP(&p.address, "port", "p", "", tr("Upload port address, e.g.: COM3 or /dev/ttyACM2"))
 	cmd.RegisterFlagCompletionFunc("port", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-		return f.Map(GetAvailablePorts(), (*rpc.Port).GetAddress), cobra.ShellCompDirectiveDefault
+		return f.Map(GetAvailablePorts(cmd.Context(), srv), (*rpc.Port).GetAddress), cobra.ShellCompDirectiveDefault
 	})
 	cmd.Flags().StringVarP(&p.protocol, "protocol", "l", "", tr("Upload port protocol, e.g: serial"))
 	cmd.RegisterFlagCompletionFunc("protocol", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-		return f.Map(GetAvailablePorts(), (*rpc.Port).GetProtocol), cobra.ShellCompDirectiveDefault
+		return f.Map(GetAvailablePorts(cmd.Context(), srv), (*rpc.Port).GetProtocol), cobra.ShellCompDirectiveDefault
 	})
 	p.timeout.AddToCommand(cmd)
 }
@@ -56,12 +56,12 @@ func (p *Port) AddToCommand(cmd *cobra.Command) {
 // This method allows will bypass the discoveries if:
 // - a nil instance is passed: in this case the plain port and protocol arguments are returned (even if empty)
 // - a protocol is specified: in this case the discoveries are not needed to autodetect the protocol.
-func (p *Port) GetPortAddressAndProtocol(instance *rpc.Instance, defaultAddress, defaultProtocol string) (string, string, error) {
+func (p *Port) GetPortAddressAndProtocol(ctx context.Context, instance *rpc.Instance, srv rpc.ArduinoCoreServiceServer, defaultAddress, defaultProtocol string) (string, string, error) {
 	if p.protocol != "" || instance == nil {
 		return p.address, p.protocol, nil
 	}
 
-	port, err := p.GetPort(instance, defaultAddress, defaultProtocol)
+	port, err := p.GetPort(ctx, instance, srv, defaultAddress, defaultProtocol)
 	if err != nil {
 		return "", "", err
 	}
@@ -70,8 +70,7 @@ func (p *Port) GetPortAddressAndProtocol(instance *rpc.Instance, defaultAddress,
 
 // GetPort returns the Port obtained by parsing command line arguments.
 // The extra metadata for the ports is obtained using the pluggable discoveries.
-func (p *Port) GetPort(instance *rpc.Instance, defaultAddress, defaultProtocol string) (*rpc.Port, error) {
-
+func (p *Port) GetPort(ctx context.Context, instance *rpc.Instance, srv rpc.ArduinoCoreServiceServer, defaultAddress, defaultProtocol string) (*rpc.Port, error) {
 	address := p.address
 	protocol := p.protocol
 	if address == "" && (defaultAddress != "" || defaultProtocol != "") {
@@ -89,9 +88,12 @@ func (p *Port) GetPort(instance *rpc.Instance, defaultAddress, defaultProtocol s
 	}
 	logrus.WithField("port", address).Tracef("Upload port")
 
-	ctx, cancel := context.WithCancel(context.Background())
+	ctx, cancel := context.WithCancel(ctx)
 	defer cancel()
-	watcher, err := board.Watch(ctx, &rpc.BoardListWatchRequest{Instance: instance})
+
+	stream, watcher := commands.BoardListWatchProxyToChan(ctx)
+	err := srv.BoardListWatch(&rpc.BoardListWatchRequest{Instance: instance}, stream)
+
 	if err != nil {
 		return nil, err
 	}
@@ -129,15 +131,15 @@ func (p *Port) GetSearchTimeout() time.Duration {
 // DetectFQBN tries to identify the board connected to the port and returns the
 // discovered Port object together with the FQBN. If the port does not match
 // exactly 1 board,
-func (p *Port) DetectFQBN(inst *rpc.Instance) (string, *rpc.Port) {
-	detectedPorts, _, err := board.List(&rpc.BoardListRequest{
+func (p *Port) DetectFQBN(ctx context.Context, inst *rpc.Instance, srv rpc.ArduinoCoreServiceServer) (string, *rpc.Port) {
+	detectedPorts, err := srv.BoardList(ctx, &rpc.BoardListRequest{
 		Instance: inst,
 		Timeout:  p.timeout.Get().Milliseconds(),
 	})
 	if err != nil {
 		feedback.Fatal(tr("Error during FQBN detection: %v", err), feedback.ErrGeneric)
 	}
-	for _, detectedPort := range detectedPorts {
+	for _, detectedPort := range detectedPorts.GetPorts() {
 		port := detectedPort.GetPort()
 		if p.address != port.GetAddress() {
 			continue
diff --git a/internal/cli/arguments/profiles.go b/internal/cli/arguments/profiles.go
index 6516cbdec08..59d56656fa6 100644
--- a/internal/cli/arguments/profiles.go
+++ b/internal/cli/arguments/profiles.go
@@ -15,7 +15,10 @@
 
 package arguments
 
-import "github.com/spf13/cobra"
+import (
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
+	"github.com/spf13/cobra"
+)
 
 // Profile contains the profile flag data.
 // This is useful so all flags used by commands that need
@@ -25,14 +28,14 @@ type Profile struct {
 }
 
 // AddToCommand adds the flags used to set fqbn to the specified Command
-func (f *Profile) AddToCommand(cmd *cobra.Command) {
+func (f *Profile) AddToCommand(cmd *cobra.Command, srv rpc.ArduinoCoreServiceServer) {
 	cmd.Flags().StringVarP(&f.profile, "profile", "m", "", tr("Sketch profile to use"))
 	cmd.RegisterFlagCompletionFunc("profile", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
 		var sketchProfile string
 		if len(args) > 0 {
 			sketchProfile = args[0]
 		}
-		return GetSketchProfiles(sketchProfile), cobra.ShellCompDirectiveDefault
+		return GetSketchProfiles(cmd.Context(), srv, sketchProfile), cobra.ShellCompDirectiveDefault
 	})
 }
 
diff --git a/internal/cli/arguments/programmer.go b/internal/cli/arguments/programmer.go
index a5267ef804e..6f590b85164 100644
--- a/internal/cli/arguments/programmer.go
+++ b/internal/cli/arguments/programmer.go
@@ -18,8 +18,7 @@ package arguments
 import (
 	"context"
 
-	"github.com/arduino/arduino-cli/commands/board"
-	"github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/spf13/cobra"
 )
 
@@ -31,23 +30,23 @@ type Programmer struct {
 }
 
 // AddToCommand adds the flags used to set the programmer to the specified Command
-func (p *Programmer) AddToCommand(cmd *cobra.Command) {
+func (p *Programmer) AddToCommand(cmd *cobra.Command, srv rpc.ArduinoCoreServiceServer) {
 	cmd.Flags().StringVarP(&p.programmer, "programmer", "P", "", tr("Programmer to use, e.g: atmel_ice"))
 	cmd.RegisterFlagCompletionFunc("programmer", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-		return GetInstalledProgrammers(), cobra.ShellCompDirectiveDefault
+		return GetInstalledProgrammers(cmd.Context(), srv), cobra.ShellCompDirectiveDefault
 	})
 }
 
 // String returns the programmer specified by the user, or the default programmer
 // for the given board if defined.
-func (p *Programmer) String(inst *commands.Instance, fqbn string) string {
+func (p *Programmer) String(ctx context.Context, inst *rpc.Instance, srv rpc.ArduinoCoreServiceServer, fqbn string) string {
 	if p.programmer != "" {
 		return p.programmer
 	}
 	if inst == nil || fqbn == "" {
 		return ""
 	}
-	details, err := board.Details(context.Background(), &commands.BoardDetailsRequest{
+	details, err := srv.BoardDetails(ctx, &rpc.BoardDetailsRequest{
 		Instance: inst,
 		Fqbn:     fqbn,
 	})
diff --git a/internal/cli/arguments/reference.go b/internal/cli/arguments/reference.go
index bf4e327fed1..613a16912fa 100644
--- a/internal/cli/arguments/reference.go
+++ b/internal/cli/arguments/reference.go
@@ -16,11 +16,11 @@
 package arguments
 
 import (
+	"context"
 	"fmt"
 	"strings"
 
 	"github.com/arduino/arduino-cli/commands/cmderrors"
-	"github.com/arduino/arduino-cli/commands/core"
 	"github.com/arduino/arduino-cli/internal/cli/instance"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/sirupsen/logrus"
@@ -43,10 +43,11 @@ func (r *Reference) String() string {
 
 // ParseReferences is a convenient wrapper that operates on a slice of strings and
 // calls ParseReference for each of them. It returns at the first invalid argument.
-func ParseReferences(args []string) ([]*Reference, error) {
+func ParseReferences(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string) ([]*Reference, error) {
 	ret := []*Reference{}
 	for _, arg := range args {
-		reference, err := ParseReference(arg)
+		// TODO: This is quite resource consuming (since it creates a new instance for each call)
+		reference, err := ParseReference(ctx, srv, arg)
 		if err != nil {
 			return nil, err
 		}
@@ -60,7 +61,7 @@ func ParseReferences(args []string) ([]*Reference, error) {
 // To achieve that, it tries to use github.com/arduino/arduino-cli/commands/core.GetPlatform
 // Note that the Reference is returned rightaway if the arg inserted by the user matches perfectly one in the response of core.GetPlatform
 // A MultiplePlatformsError is returned if the platform searched by the user matches multiple platforms
-func ParseReference(arg string) (*Reference, error) {
+func ParseReference(ctx context.Context, srv rpc.ArduinoCoreServiceServer, arg string) (*Reference, error) {
 	logrus.Infof("Parsing reference %s", arg)
 	ret := &Reference{}
 	if arg == "" {
@@ -95,8 +96,8 @@ func ParseReference(arg string) (*Reference, error) {
 	// Now that we have the required informations in `ret` we can
 	// try to use core.PlatformList to optimize what the user typed
 	// (by replacing the PackageName and Architecture in ret with the content of core.GetPlatform())
-	platforms, _ := core.PlatformSearch(&rpc.PlatformSearchRequest{
-		Instance: instance.CreateAndInit(),
+	platforms, _ := srv.PlatformSearch(ctx, &rpc.PlatformSearchRequest{
+		Instance: instance.CreateAndInit(ctx, srv),
 	})
 	foundPlatforms := []string{}
 	for _, platform := range platforms.GetSearchOutput() {
diff --git a/internal/cli/arguments/reference_test.go b/internal/cli/arguments/reference_test.go
index 047d3fc9e5c..e55051cc768 100644
--- a/internal/cli/arguments/reference_test.go
+++ b/internal/cli/arguments/reference_test.go
@@ -16,10 +16,11 @@
 package arguments_test
 
 import (
+	"context"
 	"testing"
 
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -46,10 +47,6 @@ var badCores = []struct {
 	{"", nil},
 }
 
-func init() {
-	configuration.Settings = configuration.Init("")
-}
-
 func TestArgsStringify(t *testing.T) {
 	for _, core := range goodCores {
 		require.Equal(t, core.in, core.expected.String())
@@ -57,14 +54,16 @@ func TestArgsStringify(t *testing.T) {
 }
 
 func TestParseReferenceCores(t *testing.T) {
+	srv := commands.NewArduinoCoreServer()
+	ctx := context.Background()
 	for _, tt := range goodCores {
-		actual, err := arguments.ParseReference(tt.in)
+		actual, err := arguments.ParseReference(ctx, srv, tt.in)
 		assert.Nil(t, err)
 		assert.Equal(t, tt.expected, actual)
 	}
 
 	for _, tt := range badCores {
-		actual, err := arguments.ParseReference(tt.in)
+		actual, err := arguments.ParseReference(ctx, srv, tt.in)
 		require.NotNil(t, err, "Testing bad core '%s'", tt.in)
 		require.Equal(t, tt.expected, actual, "Testing bad core '%s'", tt.in)
 	}
@@ -76,7 +75,8 @@ func TestParseArgs(t *testing.T) {
 		input = append(input, tt.in)
 	}
 
-	refs, err := arguments.ParseReferences(input)
+	srv := commands.NewArduinoCoreServer()
+	refs, err := arguments.ParseReferences(context.Background(), srv, input)
 	assert.Nil(t, err)
 	assert.Equal(t, len(goodCores), len(refs))
 
diff --git a/internal/cli/arguments/sketch.go b/internal/cli/arguments/sketch.go
index 3b6d9d2c26d..e946c4fd6a2 100644
--- a/internal/cli/arguments/sketch.go
+++ b/internal/cli/arguments/sketch.go
@@ -18,7 +18,6 @@ package arguments
 import (
 	"context"
 
-	"github.com/arduino/arduino-cli/commands/sketch"
 	f "github.com/arduino/arduino-cli/internal/algorithms"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
@@ -44,7 +43,7 @@ func InitSketchPath(path string) (sketchPath *paths.Path) {
 
 // GetSketchProfiles is an helper function useful to autocomplete.
 // It returns the profile names set in the sketch.yaml
-func GetSketchProfiles(sketchPath string) []string {
+func GetSketchProfiles(ctx context.Context, srv rpc.ArduinoCoreServiceServer, sketchPath string) []string {
 	if sketchPath == "" {
 		if wd, _ := paths.Getwd(); wd != nil && wd.String() != "" {
 			sketchPath = wd.String()
@@ -52,10 +51,10 @@ func GetSketchProfiles(sketchPath string) []string {
 			return nil
 		}
 	}
-	sk, err := sketch.LoadSketch(context.Background(), &rpc.LoadSketchRequest{SketchPath: sketchPath})
+	resp, err := srv.LoadSketch(ctx, &rpc.LoadSketchRequest{SketchPath: sketchPath})
 	if err != nil {
 		return nil
 	}
-	profiles := sk.GetProfiles()
+	profiles := resp.GetSketch().GetProfiles()
 	return f.Map(profiles, (*rpc.SketchProfile).GetName)
 }
diff --git a/internal/cli/board/attach.go b/internal/cli/board/attach.go
index b340193e609..c8b8dca3e28 100644
--- a/internal/cli/board/attach.go
+++ b/internal/cli/board/attach.go
@@ -20,14 +20,13 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/arduino/arduino-cli/commands/sketch"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/spf13/cobra"
 )
 
-func initAttachCommand() *cobra.Command {
+func initAttachCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var port arguments.Port
 	var fqbn arguments.Fqbn
 	var programmer arguments.Programmer
@@ -41,25 +40,26 @@ func initAttachCommand() *cobra.Command {
 			"  " + os.Args[0] + " board attach -P atmel_ice",
 		Args: cobra.MaximumNArgs(1),
 		Run: func(cmd *cobra.Command, args []string) {
+			ctx := cmd.Context()
 			sketchPath := ""
 			if len(args) > 0 {
 				sketchPath = args[0]
 			}
-			runAttachCommand(sketchPath, &port, fqbn.String(), &programmer)
+			runAttachCommand(ctx, srv, sketchPath, &port, fqbn.String(), &programmer)
 		},
 	}
-	fqbn.AddToCommand(attachCommand)
-	port.AddToCommand(attachCommand)
-	programmer.AddToCommand(attachCommand)
+	fqbn.AddToCommand(attachCommand, srv)
+	port.AddToCommand(attachCommand, srv)
+	programmer.AddToCommand(attachCommand, srv)
 
 	return attachCommand
 }
 
-func runAttachCommand(path string, port *arguments.Port, fqbn string, programmer *arguments.Programmer) {
+func runAttachCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, path string, port *arguments.Port, fqbn string, programmer *arguments.Programmer) {
 	sketchPath := arguments.InitSketchPath(path)
 
-	portAddress, portProtocol, _ := port.GetPortAddressAndProtocol(nil, "", "")
-	newDefaults, err := sketch.SetSketchDefaults(context.Background(), &rpc.SetSketchDefaultsRequest{
+	portAddress, portProtocol, _ := port.GetPortAddressAndProtocol(ctx, nil, srv, "", "")
+	newDefaults, err := srv.SetSketchDefaults(ctx, &rpc.SetSketchDefaultsRequest{
 		SketchPath:          sketchPath.String(),
 		DefaultFqbn:         fqbn,
 		DefaultProgrammer:   programmer.GetProgrammer(),
diff --git a/internal/cli/board/board.go b/internal/cli/board/board.go
index 8cdba86743c..f07ff8c0ac3 100644
--- a/internal/cli/board/board.go
+++ b/internal/cli/board/board.go
@@ -19,13 +19,14 @@ import (
 	"os"
 
 	"github.com/arduino/arduino-cli/internal/i18n"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/spf13/cobra"
 )
 
 var tr = i18n.Tr
 
 // NewCommand created a new `board` command
-func NewCommand() *cobra.Command {
+func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	boardCommand := &cobra.Command{
 		Use:   "board",
 		Short: tr("Arduino board commands."),
@@ -34,11 +35,11 @@ func NewCommand() *cobra.Command {
 			"  " + os.Args[0] + " board list",
 	}
 
-	boardCommand.AddCommand(initAttachCommand())
-	boardCommand.AddCommand(initDetailsCommand())
-	boardCommand.AddCommand(initListCommand())
-	boardCommand.AddCommand(initListAllCommand())
-	boardCommand.AddCommand(initSearchCommand())
+	boardCommand.AddCommand(initAttachCommand(srv))
+	boardCommand.AddCommand(initDetailsCommand(srv))
+	boardCommand.AddCommand(initListCommand(srv))
+	boardCommand.AddCommand(initListAllCommand(srv))
+	boardCommand.AddCommand(initSearchCommand(srv))
 
 	return boardCommand
 }
diff --git a/internal/cli/board/details.go b/internal/cli/board/details.go
index 14f48414ea9..4e7d8579f19 100644
--- a/internal/cli/board/details.go
+++ b/internal/cli/board/details.go
@@ -20,7 +20,6 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/arduino/arduino-cli/commands/board"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/result"
@@ -32,7 +31,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func initDetailsCommand() *cobra.Command {
+func initDetailsCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var showFullDetails bool
 	var listProgrammers bool
 	var fqbn arguments.Fqbn
@@ -44,11 +43,11 @@ func initDetailsCommand() *cobra.Command {
 		Example: "  " + os.Args[0] + " board details -b arduino:avr:nano",
 		Args:    cobra.NoArgs,
 		Run: func(cmd *cobra.Command, args []string) {
-			runDetailsCommand(fqbn.String(), showFullDetails, listProgrammers, showProperties)
+			runDetailsCommand(cmd.Context(), srv, fqbn.String(), showFullDetails, listProgrammers, showProperties)
 		},
 	}
 
-	fqbn.AddToCommand(detailsCommand)
+	fqbn.AddToCommand(detailsCommand, srv)
 	detailsCommand.Flags().BoolVarP(&showFullDetails, "full", "f", false, tr("Show full board details"))
 	detailsCommand.Flags().BoolVarP(&listProgrammers, "list-programmers", "", false, tr("Show list of available programmers"))
 	detailsCommand.MarkFlagRequired("fqbn")
@@ -56,8 +55,8 @@ func initDetailsCommand() *cobra.Command {
 	return detailsCommand
 }
 
-func runDetailsCommand(fqbn string, showFullDetails, listProgrammers bool, showProperties arguments.ShowProperties) {
-	inst := instance.CreateAndInit()
+func runDetailsCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, fqbn string, showFullDetails, listProgrammers bool, showProperties arguments.ShowProperties) {
+	inst := instance.CreateAndInit(ctx, srv)
 
 	logrus.Info("Executing `arduino-cli board details`")
 
@@ -65,7 +64,7 @@ func runDetailsCommand(fqbn string, showFullDetails, listProgrammers bool, showP
 	if err != nil {
 		feedback.Fatal(err.Error(), feedback.ErrBadArgument)
 	}
-	res, err := board.Details(context.Background(), &rpc.BoardDetailsRequest{
+	res, err := srv.BoardDetails(ctx, &rpc.BoardDetailsRequest{
 		Instance:                   inst,
 		Fqbn:                       fqbn,
 		DoNotExpandBuildProperties: showPropertiesMode == arguments.ShowPropertiesUnexpanded,
diff --git a/internal/cli/board/list.go b/internal/cli/board/list.go
index f864768fc8d..20001f2ad82 100644
--- a/internal/cli/board/list.go
+++ b/internal/cli/board/list.go
@@ -22,7 +22,7 @@ import (
 	"os"
 	"sort"
 
-	"github.com/arduino/arduino-cli/commands/board"
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/commands/cmderrors"
 	"github.com/arduino/arduino-cli/internal/arduino/cores"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
@@ -35,7 +35,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func initListCommand() *cobra.Command {
+func initListCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var timeoutArg arguments.DiscoveryTimeout
 	var watch bool
 	var fqbn arguments.Fqbn
@@ -46,32 +46,34 @@ func initListCommand() *cobra.Command {
 		Example: "  " + os.Args[0] + " board list --discovery-timeout 10s",
 		Args:    cobra.NoArgs,
 		Run: func(cmd *cobra.Command, args []string) {
-			runListCommand(watch, timeoutArg.Get().Milliseconds(), fqbn.String())
+			runListCommand(cmd.Context(), srv, watch, timeoutArg.Get().Milliseconds(), fqbn.String())
 		},
 	}
 
 	timeoutArg.AddToCommand(listCommand)
-	fqbn.AddToCommand(listCommand)
+	fqbn.AddToCommand(listCommand, srv)
 	listCommand.Flags().BoolVarP(&watch, "watch", "w", false, tr("Command keeps running and prints list of connected boards whenever there is a change."))
 	return listCommand
 }
 
 // runListCommand detects and lists the connected arduino boards
-func runListCommand(watch bool, timeout int64, fqbn string) {
-	inst := instance.CreateAndInit()
+func runListCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, watch bool, timeout int64, fqbn string) {
+	inst := instance.CreateAndInit(ctx, srv)
 
 	logrus.Info("Executing `arduino-cli board list`")
 
 	if watch {
-		watchList(inst)
+		watchList(ctx, inst, srv)
 		return
 	}
 
-	ports, discoveryErrors, err := board.List(&rpc.BoardListRequest{
+	list, err := srv.BoardList(ctx, &rpc.BoardListRequest{
 		Instance: inst,
 		Timeout:  timeout,
 		Fqbn:     fqbn,
 	})
+	ports := list.GetPorts()
+	discoveryErrors := list.GetWarnings()
 	var invalidFQBNErr *cmderrors.InvalidFQBNError
 	if errors.As(err, &invalidFQBNErr) {
 		feedback.Fatal(tr(err.Error()), feedback.ErrBadArgument)
@@ -86,8 +88,9 @@ func runListCommand(watch bool, timeout int64, fqbn string) {
 	feedback.PrintResult(listResult{result.NewDetectedPorts(ports)})
 }
 
-func watchList(inst *rpc.Instance) {
-	eventsChan, err := board.Watch(context.Background(), &rpc.BoardListWatchRequest{Instance: inst})
+func watchList(ctx context.Context, inst *rpc.Instance, srv rpc.ArduinoCoreServiceServer) {
+	stream, eventsChan := commands.BoardListWatchProxyToChan(ctx)
+	err := srv.BoardListWatch(&rpc.BoardListWatchRequest{Instance: inst}, stream)
 	if err != nil {
 		feedback.Fatal(tr("Error detecting boards: %v", err), feedback.ErrNetwork)
 	}
diff --git a/internal/cli/board/listall.go b/internal/cli/board/listall.go
index a4782b41d3c..55381ba2ffc 100644
--- a/internal/cli/board/listall.go
+++ b/internal/cli/board/listall.go
@@ -21,7 +21,6 @@ import (
 	"os"
 	"sort"
 
-	"github.com/arduino/arduino-cli/commands/board"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/result"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/table"
@@ -33,7 +32,7 @@ import (
 
 var showHiddenBoard bool
 
-func initListAllCommand() *cobra.Command {
+func initListAllCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var listAllCommand = &cobra.Command{
 		Use:   fmt.Sprintf("listall [%s]", tr("boardname")),
 		Short: tr("List all known boards and their corresponding FQBN."),
@@ -43,19 +42,21 @@ for a specific board if you specify the board name`),
 			"  " + os.Args[0] + " board listall\n" +
 			"  " + os.Args[0] + " board listall zero",
 		Args: cobra.ArbitraryArgs,
-		Run:  runListAllCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			runListAllCommand(cmd.Context(), args, srv)
+		},
 	}
 	listAllCommand.Flags().BoolVarP(&showHiddenBoard, "show-hidden", "a", false, tr("Show also boards marked as 'hidden' in the platform"))
 	return listAllCommand
 }
 
 // runListAllCommand list all installed boards
-func runListAllCommand(cmd *cobra.Command, args []string) {
-	inst := instance.CreateAndInit()
+func runListAllCommand(ctx context.Context, args []string, srv rpc.ArduinoCoreServiceServer) {
+	inst := instance.CreateAndInit(ctx, srv)
 
 	logrus.Info("Executing `arduino-cli board listall`")
 
-	list, err := board.ListAll(context.Background(), &rpc.BoardListAllRequest{
+	list, err := srv.BoardListAll(ctx, &rpc.BoardListAllRequest{
 		Instance:            inst,
 		SearchArgs:          args,
 		IncludeHiddenBoards: showHiddenBoard,
diff --git a/internal/cli/board/search.go b/internal/cli/board/search.go
index b590ddb6e15..42b85785788 100644
--- a/internal/cli/board/search.go
+++ b/internal/cli/board/search.go
@@ -22,7 +22,6 @@ import (
 	"sort"
 	"strings"
 
-	"github.com/arduino/arduino-cli/commands/board"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/result"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/table"
@@ -32,7 +31,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func initSearchCommand() *cobra.Command {
+func initSearchCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var searchCommand = &cobra.Command{
 		Use:   fmt.Sprintf("search [%s]", tr("boardname")),
 		Short: tr("Search for a board in the Boards Manager."),
@@ -41,18 +40,20 @@ func initSearchCommand() *cobra.Command {
 			"  " + os.Args[0] + " board search\n" +
 			"  " + os.Args[0] + " board search zero",
 		Args: cobra.ArbitraryArgs,
-		Run:  runSearchCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			runSearchCommand(cmd.Context(), srv, args)
+		},
 	}
 	searchCommand.Flags().BoolVarP(&showHiddenBoard, "show-hidden", "a", false, tr("Show also boards marked as 'hidden' in the platform"))
 	return searchCommand
 }
 
-func runSearchCommand(cmd *cobra.Command, args []string) {
-	inst := instance.CreateAndInit()
+func runSearchCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string) {
+	inst := instance.CreateAndInit(ctx, srv)
 
 	logrus.Info("Executing `arduino-cli board search`")
 
-	res, err := board.Search(context.Background(), &rpc.BoardSearchRequest{
+	res, err := srv.BoardSearch(ctx, &rpc.BoardSearchRequest{
 		Instance:            inst,
 		SearchArgs:          strings.Join(args, " "),
 		IncludeHiddenBoards: showHiddenBoard,
diff --git a/internal/cli/burnbootloader/burnbootloader.go b/internal/cli/burnbootloader/burnbootloader.go
index c14c63eeb42..7c8f4c16896 100644
--- a/internal/cli/burnbootloader/burnbootloader.go
+++ b/internal/cli/burnbootloader/burnbootloader.go
@@ -20,8 +20,8 @@ import (
 	"errors"
 	"os"
 
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/commands/cmderrors"
-	"github.com/arduino/arduino-cli/commands/upload"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/instance"
@@ -42,19 +42,21 @@ var (
 )
 
 // NewCommand created a new `burn-bootloader` command
-func NewCommand() *cobra.Command {
+func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	burnBootloaderCommand := &cobra.Command{
 		Use:     "burn-bootloader",
 		Short:   tr("Upload the bootloader."),
 		Long:    tr("Upload the bootloader on the board using an external programmer."),
 		Example: "  " + os.Args[0] + " burn-bootloader -b arduino:avr:uno -P atmel_ice",
-		Args:    cobra.MaximumNArgs(1),
-		Run:     runBootloaderCommand,
+		Args:    cobra.NoArgs,
+		Run: func(cmd *cobra.Command, args []string) {
+			runBootloaderCommand(cmd.Context(), srv)
+		},
 	}
 
-	fqbn.AddToCommand(burnBootloaderCommand)
-	port.AddToCommand(burnBootloaderCommand)
-	programmer.AddToCommand(burnBootloaderCommand)
+	fqbn.AddToCommand(burnBootloaderCommand, srv)
+	port.AddToCommand(burnBootloaderCommand, srv)
+	programmer.AddToCommand(burnBootloaderCommand, srv)
 	burnBootloaderCommand.Flags().BoolVarP(&verify, "verify", "t", false, tr("Verify uploaded binary after the upload."))
 	burnBootloaderCommand.Flags().BoolVarP(&verbose, "verbose", "v", false, tr("Turns on verbose mode."))
 	burnBootloaderCommand.Flags().BoolVar(&dryRun, "dry-run", false, tr("Do not perform the actual upload, just log out actions"))
@@ -63,27 +65,28 @@ func NewCommand() *cobra.Command {
 	return burnBootloaderCommand
 }
 
-func runBootloaderCommand(command *cobra.Command, args []string) {
-	instance := instance.CreateAndInit()
+func runBootloaderCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer) {
+	instance := instance.CreateAndInit(ctx, srv)
 
 	logrus.Info("Executing `arduino-cli burn-bootloader`")
 
 	// We don't need a Sketch to upload a board's bootloader
-	discoveryPort, err := port.GetPort(instance, "", "")
+	discoveryPort, err := port.GetPort(ctx, instance, srv, "", "")
 	if err != nil {
 		feedback.Fatal(tr("Error during Upload: %v", err), feedback.ErrGeneric)
 	}
 
 	stdOut, stdErr, res := feedback.OutputStreams()
-	if _, err := upload.BurnBootloader(context.Background(), &rpc.BurnBootloaderRequest{
+	stream := commands.BurnBootloaderToServerStreams(ctx, stdOut, stdErr)
+	if err := srv.BurnBootloader(&rpc.BurnBootloaderRequest{
 		Instance:   instance,
 		Fqbn:       fqbn.String(),
 		Port:       discoveryPort,
 		Verbose:    verbose,
 		Verify:     verify,
-		Programmer: programmer.String(instance, fqbn.String()),
+		Programmer: programmer.String(ctx, instance, srv, fqbn.String()),
 		DryRun:     dryRun,
-	}, stdOut, stdErr); err != nil {
+	}, stream); err != nil {
 		errcode := feedback.ErrGeneric
 		if errors.Is(err, &cmderrors.ProgrammerRequiredForUploadError{}) {
 			errcode = feedback.ErrMissingProgrammer
diff --git a/internal/cli/cache/cache.go b/internal/cli/cache/cache.go
index 43c9d775234..e6b14ebe90d 100644
--- a/internal/cli/cache/cache.go
+++ b/internal/cli/cache/cache.go
@@ -19,13 +19,14 @@ import (
 	"os"
 
 	"github.com/arduino/arduino-cli/internal/i18n"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/spf13/cobra"
 )
 
 var tr = i18n.Tr
 
 // NewCommand created a new `cache` command
-func NewCommand() *cobra.Command {
+func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	cacheCommand := &cobra.Command{
 		Use:   "cache",
 		Short: tr("Arduino cache commands."),
@@ -34,7 +35,7 @@ func NewCommand() *cobra.Command {
 			" " + os.Args[0] + " cache clean\n\n",
 	}
 
-	cacheCommand.AddCommand(initCleanCommand())
+	cacheCommand.AddCommand(initCleanCommand(srv))
 
 	return cacheCommand
 }
diff --git a/internal/cli/cache/clean.go b/internal/cli/cache/clean.go
index 32f788e87ed..184d2e65b90 100644
--- a/internal/cli/cache/clean.go
+++ b/internal/cli/cache/clean.go
@@ -19,29 +19,30 @@ import (
 	"context"
 	"os"
 
-	"github.com/arduino/arduino-cli/commands/cache"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 )
 
-func initCleanCommand() *cobra.Command {
+func initCleanCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	cleanCommand := &cobra.Command{
 		Use:     "clean",
 		Short:   tr("Delete Boards/Library Manager download cache."),
-		Long:    tr("Delete contents of the `directories.downloads` folder, where archive files are staged during installation of libraries and boards platforms."),
+		Long:    tr("Delete contents of the downloads cache folder, where archive files are staged during installation of libraries and boards platforms."),
 		Example: "  " + os.Args[0] + " cache clean",
 		Args:    cobra.NoArgs,
-		Run:     runCleanCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			runCleanCommand(cmd.Context(), srv)
+		},
 	}
 	return cleanCommand
 }
 
-func runCleanCommand(cmd *cobra.Command, args []string) {
+func runCleanCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer) {
 	logrus.Info("Executing `arduino-cli cache clean`")
 
-	_, err := cache.CleanDownloadCacheDirectory(context.Background(), &rpc.CleanDownloadCacheDirectoryRequest{})
+	_, err := srv.CleanDownloadCacheDirectory(ctx, &rpc.CleanDownloadCacheDirectoryRequest{})
 	if err != nil {
 		feedback.Fatal(tr("Error cleaning caches: %v", err), feedback.ErrGeneric)
 	}
diff --git a/internal/cli/cli.go b/internal/cli/cli.go
index da265d46f59..c2333c1827b 100644
--- a/internal/cli/cli.go
+++ b/internal/cli/cli.go
@@ -22,14 +22,12 @@ import (
 	"os"
 	"strings"
 
-	"github.com/arduino/arduino-cli/commands/updatecheck"
 	"github.com/arduino/arduino-cli/internal/cli/board"
 	"github.com/arduino/arduino-cli/internal/cli/burnbootloader"
 	"github.com/arduino/arduino-cli/internal/cli/cache"
 	"github.com/arduino/arduino-cli/internal/cli/compile"
 	"github.com/arduino/arduino-cli/internal/cli/completion"
 	"github.com/arduino/arduino-cli/internal/cli/config"
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/arduino/arduino-cli/internal/cli/core"
 	"github.com/arduino/arduino-cli/internal/cli/daemon"
 	"github.com/arduino/arduino-cli/internal/cli/debug"
@@ -56,36 +54,68 @@ import (
 	semver "go.bug.st/relaxed-semver"
 )
 
-var (
-	verbose      bool
-	jsonOutput   bool
-	outputFormat string
-	configFile   string
-)
-
 // NewCommand creates a new ArduinoCli command root
-func NewCommand() *cobra.Command {
+func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	cobra.AddTemplateFunc("tr", i18n.Tr)
 
 	var updaterMessageChan chan *semver.Version
 
-	// ArduinoCli is the root command
-	arduinoCli := &cobra.Command{
+	var (
+		verbose        bool
+		noColor        bool
+		logLevel       string
+		logFile        string
+		logFormat      string
+		jsonOutput     bool
+		outputFormat   string
+		configFile     string
+		additionalUrls []string
+	)
+
+	resp, err := srv.ConfigurationGet(context.Background(), &rpc.ConfigurationGetRequest{})
+	if err != nil {
+		panic("Error creating configuration: " + err.Error())
+	}
+	settings := resp.GetConfiguration()
+
+	defaultLogFile := settings.GetLogging().GetFile()
+	defaultLogFormat := settings.GetLogging().GetFormat()
+	defaultLogLevel := settings.GetLogging().GetLevel()
+	defaultAdditionalURLs := settings.GetBoardManager().GetAdditionalUrls()
+	defaultOutputNoColor := settings.GetOutput().GetNoColor()
+
+	cmd := &cobra.Command{
 		Use:     "arduino-cli",
 		Short:   tr("Arduino CLI."),
 		Long:    tr("Arduino Command Line Interface (arduino-cli)."),
 		Example: fmt.Sprintf("  %s <%s> [%s...]", os.Args[0], tr("command"), tr("flags")),
 		PersistentPreRun: func(cmd *cobra.Command, args []string) {
+			ctx := cmd.Context()
+
+			config.ApplyGlobalFlagsToConfiguration(ctx, cmd, srv)
+
 			if jsonOutput {
 				outputFormat = "json"
 			}
+			if outputFormat != "text" {
+				cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
+					feedback.Fatal(tr("Should show help message, but it is available only in TEXT mode."), feedback.ErrBadArgument)
+				})
+			}
+
+			preRun(verbose, outputFormat, logLevel, logFile, logFormat, noColor, settings)
 
-			preRun(cmd, args)
+			// Log the configuration file used
+			if configFile := config.GetConfigFile(ctx); configFile != "" {
+				logrus.Infof("Using config file: %s", configFile)
+			} else {
+				logrus.Info("Config file not found, using default values")
+			}
 
 			if cmd.Name() != "version" {
 				updaterMessageChan = make(chan *semver.Version)
 				go func() {
-					res, err := updatecheck.CheckForArduinoCLIUpdates(context.Background(), &rpc.CheckForArduinoCLIUpdatesRequest{})
+					res, err := srv.CheckForArduinoCLIUpdates(ctx, &rpc.CheckForArduinoCLIUpdatesRequest{})
 					if err != nil {
 						logrus.Warnf("Error checking for updates: %v", err)
 						updaterMessageChan <- nil
@@ -112,60 +142,48 @@ func NewCommand() *cobra.Command {
 		},
 	}
 
-	arduinoCli.SetUsageTemplate(getUsageTemplate())
-
-	createCliCommandTree(arduinoCli)
+	cmd.SetUsageTemplate(getUsageTemplate())
 
-	return arduinoCli
-}
-
-// this is here only for testing
-func createCliCommandTree(cmd *cobra.Command) {
-	cmd.AddCommand(board.NewCommand())
-	cmd.AddCommand(cache.NewCommand())
-	cmd.AddCommand(compile.NewCommand())
+	cmd.AddCommand(board.NewCommand(srv))
+	cmd.AddCommand(cache.NewCommand(srv))
+	cmd.AddCommand(compile.NewCommand(srv, settings))
 	cmd.AddCommand(completion.NewCommand())
-	cmd.AddCommand(config.NewCommand())
-	cmd.AddCommand(core.NewCommand())
-	cmd.AddCommand(daemon.NewCommand())
+	cmd.AddCommand(config.NewCommand(srv, settings))
+	cmd.AddCommand(core.NewCommand(srv))
+	cmd.AddCommand(daemon.NewCommand(srv, settings))
 	cmd.AddCommand(generatedocs.NewCommand())
-	cmd.AddCommand(lib.NewCommand())
-	cmd.AddCommand(monitor.NewCommand())
-	cmd.AddCommand(outdated.NewCommand())
-	cmd.AddCommand(sketch.NewCommand())
-	cmd.AddCommand(update.NewCommand())
-	cmd.AddCommand(upgrade.NewCommand())
-	cmd.AddCommand(upload.NewCommand())
-	cmd.AddCommand(debug.NewCommand())
-	cmd.AddCommand(burnbootloader.NewCommand())
-	cmd.AddCommand(version.NewCommand())
+	cmd.AddCommand(lib.NewCommand(srv, settings))
+	cmd.AddCommand(monitor.NewCommand(srv))
+	cmd.AddCommand(outdated.NewCommand(srv))
+	cmd.AddCommand(sketch.NewCommand(srv))
+	cmd.AddCommand(update.NewCommand(srv))
+	cmd.AddCommand(upgrade.NewCommand(srv))
+	cmd.AddCommand(upload.NewCommand(srv))
+	cmd.AddCommand(debug.NewCommand(srv))
+	cmd.AddCommand(burnbootloader.NewCommand(srv))
+	cmd.AddCommand(version.NewCommand(srv))
 	cmd.AddCommand(feedback.NewCommand())
 
 	cmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, tr("Print the logs on the standard output."))
 	cmd.Flag("verbose").Hidden = true
 	cmd.PersistentFlags().BoolVar(&verbose, "log", false, tr("Print the logs on the standard output."))
 	validLogLevels := []string{"trace", "debug", "info", "warn", "error", "fatal", "panic"}
-	cmd.PersistentFlags().String("log-level", "", tr("Messages with this level and above will be logged. Valid levels are: %s", strings.Join(validLogLevels, ", ")))
-	cmd.RegisterFlagCompletionFunc("log-level", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-		return validLogLevels, cobra.ShellCompDirectiveDefault
-	})
-	cmd.PersistentFlags().String("log-file", "", tr("Path to the file where logs will be written."))
+	cmd.PersistentFlags().StringVar(&logLevel, "log-level", defaultLogLevel, tr("Messages with this level and above will be logged. Valid levels are: %s", strings.Join(validLogLevels, ", ")))
+	cmd.RegisterFlagCompletionFunc("log-level", cobra.FixedCompletions(validLogLevels, cobra.ShellCompDirectiveDefault))
+	cmd.PersistentFlags().StringVar(&logFile, "log-file", defaultLogFile, tr("Path to the file where logs will be written."))
 	validLogFormats := []string{"text", "json"}
-	cmd.PersistentFlags().String("log-format", "", tr("The output format for the logs, can be: %s", strings.Join(validLogFormats, ", ")))
-	cmd.RegisterFlagCompletionFunc("log-format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-		return validLogFormats, cobra.ShellCompDirectiveDefault
-	})
+	cmd.PersistentFlags().StringVar(&logFormat, "log-format", defaultLogFormat, tr("The output format for the logs, can be: %s", strings.Join(validLogFormats, ", ")))
+	cmd.RegisterFlagCompletionFunc("log-format", cobra.FixedCompletions(validLogFormats, cobra.ShellCompDirectiveDefault))
 	validOutputFormats := []string{"text", "json", "jsonmini"}
 	cmd.PersistentFlags().StringVar(&outputFormat, "format", "text", tr("The command output format, can be: %s", strings.Join(validOutputFormats, ", ")))
-	cmd.RegisterFlagCompletionFunc("format", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-		return validOutputFormats, cobra.ShellCompDirectiveDefault
-	})
+	cmd.RegisterFlagCompletionFunc("format", cobra.FixedCompletions(validOutputFormats, cobra.ShellCompDirectiveDefault))
 	cmd.Flag("format").Hidden = true
 	cmd.PersistentFlags().BoolVar(&jsonOutput, "json", false, tr("Print the output in JSON format."))
 	cmd.PersistentFlags().StringVar(&configFile, "config-file", "", tr("The custom config file (if not specified the default will be used)."))
-	cmd.PersistentFlags().StringSlice("additional-urls", []string{}, tr("Comma-separated list of additional URLs for the Boards Manager."))
-	cmd.PersistentFlags().Bool("no-color", false, "Disable colored output.")
-	configuration.BindFlags(cmd, configuration.Settings)
+	cmd.PersistentFlags().StringSliceVar(&additionalUrls, "additional-urls", defaultAdditionalURLs, tr("Comma-separated list of additional URLs for the Boards Manager."))
+	cmd.PersistentFlags().BoolVar(&noColor, "no-color", defaultOutputNoColor, "Disable colored output.")
+
+	return cmd
 }
 
 // convert the string passed to the `--log-level` option to the corresponding
@@ -184,22 +202,23 @@ func toLogLevel(s string) (t logrus.Level, found bool) {
 	return
 }
 
-func preRun(cmd *cobra.Command, args []string) {
-	configFile := configuration.Settings.ConfigFileUsed()
-
-	// initialize inventory
-	err := inventory.Init(configuration.DataDir(configuration.Settings).String())
-	if err != nil {
-		feedback.Fatal(fmt.Sprintf("Error: %v", err), feedback.ErrInitializingInventory)
-	}
-
-	// https://no-color.org/
-	color.NoColor = configuration.Settings.GetBool("output.no_color") || os.Getenv("NO_COLOR") != ""
+func preRun(verbose bool, outputFormat string, logLevel, logFile, logFormat string, noColor bool, settings *rpc.Configuration) {
+	//
+	// Prepare the Feedback system
+	//
 
 	// Set default feedback output to colorable
+	color.NoColor = noColor || os.Getenv("NO_COLOR") != "" // https://no-color.org/
 	feedback.SetOut(colorable.NewColorableStdout())
 	feedback.SetErr(colorable.NewColorableStderr())
 
+	// use the output format to configure the Feedback
+	format, ok := feedback.ParseOutputFormat(outputFormat)
+	if !ok {
+		feedback.Fatal(tr("Invalid output format: %s", outputFormat), feedback.ErrBadArgument)
+	}
+	feedback.SetFormat(format)
+
 	//
 	// Prepare logging
 	//
@@ -217,13 +236,12 @@ func preRun(cmd *cobra.Command, args []string) {
 	}
 
 	// set the Logger format
-	logFormat := strings.ToLower(configuration.Settings.GetString("logging.format"))
+	logFormat = strings.ToLower(logFormat)
 	if logFormat == "json" {
 		logrus.SetFormatter(&logrus.JSONFormatter{})
 	}
 
 	// should we log to file?
-	logFile := configuration.Settings.GetString("logging.file")
 	if logFile != "" {
 		file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
 		if err != nil {
@@ -239,41 +257,20 @@ func preRun(cmd *cobra.Command, args []string) {
 	}
 
 	// configure logging filter
-	if lvl, found := toLogLevel(configuration.Settings.GetString("logging.level")); !found {
-		feedback.Fatal(tr("Invalid option for --log-level: %s", configuration.Settings.GetString("logging.level")), feedback.ErrBadArgument)
+	if logrusLevel, found := toLogLevel(logLevel); !found {
+		feedback.Fatal(tr("Invalid logging level: %s", logLevel), feedback.ErrBadArgument)
 	} else {
-		logrus.SetLevel(lvl)
-	}
-
-	//
-	// Prepare the Feedback system
-	//
-
-	// check the right output format was passed
-	format, found := feedback.ParseOutputFormat(outputFormat)
-	if !found {
-		feedback.Fatal(tr("Invalid output format: %s", outputFormat), feedback.ErrBadArgument)
+		logrus.SetLevel(logrusLevel)
 	}
 
-	// use the output format to configure the Feedback
-	feedback.SetFormat(format)
-
-	//
 	// Print some status info and check command is consistent
-	//
-
-	if configFile != "" {
-		logrus.Infof("Using config file: %s", configFile)
-	} else {
-		logrus.Info("Config file not found, using default values")
-	}
-
 	logrus.Info(versioninfo.VersionInfo.Application + " version " + versioninfo.VersionInfo.VersionString)
 
-	if outputFormat != "text" {
-		cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
-			logrus.Warn("Calling help on JSON format")
-			feedback.Fatal(tr("Invalid Call : should show Help, but it is available only in TEXT mode."), feedback.ErrBadArgument)
-		})
+	//
+	// Initialize inventory
+	//
+	err := inventory.Init(settings.GetDirectories().GetData())
+	if err != nil {
+		feedback.Fatal(fmt.Sprintf("Error: %v", err), feedback.ErrInitializingInventory)
 	}
 }
diff --git a/internal/cli/compile/compile.go b/internal/cli/compile/compile.go
index 2b607b78536..f04f01e8fc7 100644
--- a/internal/cli/compile/compile.go
+++ b/internal/cli/compile/compile.go
@@ -16,7 +16,6 @@
 package compile
 
 import (
-	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -24,13 +23,9 @@ import (
 	"os"
 	"strings"
 
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/commands/cmderrors"
-	"github.com/arduino/arduino-cli/commands/compile"
-	"github.com/arduino/arduino-cli/commands/core"
-	"github.com/arduino/arduino-cli/commands/sketch"
-	"github.com/arduino/arduino-cli/commands/upload"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/result"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/table"
@@ -61,7 +56,7 @@ var (
 	uploadAfterCompile      bool                     // Upload the binary after the compilation.
 	portArgs                arguments.Port           // Upload port, e.g.: COM10 or /dev/ttyACM0.
 	verify                  bool                     // Upload, verify uploaded binary after the upload.
-	exportBinaries          bool                     //
+	exportBinaries          bool                     // If set built binaries will be exported to the sketch folder
 	exportDir               string                   // The compiled binary is written to this file
 	optimizeForDebug        bool                     // Optimize compile output for debug, not for release
 	programmer              arguments.Programmer     // Use the specified programmer to upload
@@ -80,7 +75,7 @@ var (
 )
 
 // NewCommand created a new `compile` command
-func NewCommand() *cobra.Command {
+func NewCommand(srv rpc.ArduinoCoreServiceServer, settings *rpc.Configuration) *cobra.Command {
 	compileCommand := &cobra.Command{
 		Use:   "compile",
 		Short: tr("Compiles Arduino sketches."),
@@ -91,16 +86,18 @@ func NewCommand() *cobra.Command {
 			"  " + os.Args[0] + ` compile -b arduino:avr:uno --build-property "build.extra_flags=-DPIN=2 \"-DMY_DEFINE=\"hello world\"\"" /home/user/Arduino/MySketch` + "\n" +
 			"  " + os.Args[0] + ` compile -b arduino:avr:uno --build-property build.extra_flags=-DPIN=2 --build-property "compiler.cpp.extra_flags=\"-DSSID=\"hello world\"\"" /home/user/Arduino/MySketch` + "\n",
 		Args: cobra.MaximumNArgs(1),
-		Run:  runCompileCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			runCompileCommand(cmd, args, srv)
+		},
 	}
 
-	fqbnArg.AddToCommand(compileCommand)
-	profileArg.AddToCommand(compileCommand)
+	fqbnArg.AddToCommand(compileCommand, srv)
+	profileArg.AddToCommand(compileCommand, srv)
 	compileCommand.Flags().BoolVar(&dumpProfile, "dump-profile", false, tr("Create and print a profile configuration from the build."))
 	showPropertiesArg.AddToCommand(compileCommand)
 	compileCommand.Flags().BoolVar(&preprocess, "preprocess", false, tr("Print preprocessed code to stdout instead of compiling."))
 	compileCommand.Flags().StringVar(&buildCachePath, "build-cache-path", "", tr("Builds of 'core.a' are saved into this path to be cached and reused."))
-	compileCommand.Flags().StringVarP(&exportDir, "output-dir", "", "", tr("Save build artifacts in this directory."))
+	compileCommand.Flags().StringVar(&exportDir, "output-dir", "", tr("Save build artifacts in this directory."))
 	compileCommand.Flags().StringVar(&buildPath, "build-path", "",
 		tr("Path where to save compiled files. If omitted, a directory will be created in the default temporary path of your OS."))
 	compileCommand.Flags().StringSliceVar(&buildProperties, "build-properties", []string{},
@@ -118,31 +115,32 @@ func NewCommand() *cobra.Command {
 	compileCommand.Flags().BoolVarP(&verbose, "verbose", "v", false, tr("Optional, turns on verbose mode."))
 	compileCommand.Flags().BoolVar(&quiet, "quiet", false, tr("Optional, suppresses almost every output."))
 	compileCommand.Flags().BoolVarP(&uploadAfterCompile, "upload", "u", false, tr("Upload the binary after the compilation."))
-	portArgs.AddToCommand(compileCommand)
+	portArgs.AddToCommand(compileCommand, srv)
 	compileCommand.Flags().BoolVarP(&verify, "verify", "t", false, tr("Verify uploaded binary after the upload."))
 	compileCommand.Flags().StringSliceVar(&library, "library", []string{},
 		tr("Path to a single library’s root folder. Can be used multiple times or entries can be comma separated."))
 	compileCommand.Flags().StringSliceVar(&libraries, "libraries", []string{},
 		tr("Path to a collection of libraries. Can be used multiple times or entries can be comma separated."))
 	compileCommand.Flags().BoolVar(&optimizeForDebug, "optimize-for-debug", false, tr("Optional, optimize compile output for debugging, rather than for release."))
-	programmer.AddToCommand(compileCommand)
+	programmer.AddToCommand(compileCommand, srv)
 	compileCommand.Flags().BoolVar(&compilationDatabaseOnly, "only-compilation-database", false, tr("Just produce the compilation database, without actually compiling. All build commands are skipped except pre* hooks."))
 	compileCommand.Flags().BoolVar(&clean, "clean", false, tr("Optional, cleanup the build folder and do not use any cached build."))
-	compileCommand.Flags().BoolVarP(&exportBinaries, "export-binaries", "e", false, tr("If set built binaries will be exported to the sketch folder."))
+	compileCommand.Flags().BoolVarP(&exportBinaries, "export-binaries", "e", settings.GetSketch().GetAlwaysExportBinaries(),
+		tr("If set built binaries will be exported to the sketch folder."))
 	compileCommand.Flags().StringVar(&sourceOverrides, "source-override", "", tr("Optional. Path to a .json file that contains a set of replacements of the sketch source code."))
 	compileCommand.Flag("source-override").Hidden = true
 	compileCommand.Flags().BoolVar(&skipLibrariesDiscovery, "skip-libraries-discovery", false, "Skip libraries discovery. This flag is provided only for use in language server and other, very specific, use cases. Do not use for normal compiles")
 	compileCommand.Flag("skip-libraries-discovery").Hidden = true
 	compileCommand.Flags().Int32VarP(&jobs, "jobs", "j", 0, tr("Max number of parallel compiles. If set to 0 the number of available CPUs cores will be used."))
-	configuration.Settings.BindPFlag("sketch.always_export_binaries", compileCommand.Flags().Lookup("export-binaries"))
 
 	compileCommand.Flags().MarkDeprecated("build-properties", tr("please use --build-property instead."))
 
 	return compileCommand
 }
 
-func runCompileCommand(cmd *cobra.Command, args []string) {
+func runCompileCommand(cmd *cobra.Command, args []string, srv rpc.ArduinoCoreServiceServer) {
 	logrus.Info("Executing `arduino-cli compile`")
+	ctx := cmd.Context()
 
 	if profileArg.Get() != "" {
 		if len(libraries) > 0 {
@@ -159,26 +157,27 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
 	}
 
 	sketchPath := arguments.InitSketchPath(path)
-	sk, err := sketch.LoadSketch(context.Background(), &rpc.LoadSketchRequest{SketchPath: sketchPath.String()})
+	resp, err := srv.LoadSketch(ctx, &rpc.LoadSketchRequest{SketchPath: sketchPath.String()})
 	if err != nil {
 		feedback.FatalError(err, feedback.ErrGeneric)
 	}
+	sk := resp.GetSketch()
 	feedback.WarnAboutDeprecatedFiles(sk)
 
 	var inst *rpc.Instance
 	var profile *rpc.SketchProfile
 
 	if profileArg.Get() == "" {
-		inst, profile = instance.CreateAndInitWithProfile(sk.GetDefaultProfile().GetName(), sketchPath)
+		inst, profile = instance.CreateAndInitWithProfile(ctx, srv, sk.GetDefaultProfile().GetName(), sketchPath)
 	} else {
-		inst, profile = instance.CreateAndInitWithProfile(profileArg.Get(), sketchPath)
+		inst, profile = instance.CreateAndInitWithProfile(ctx, srv, profileArg.Get(), sketchPath)
 	}
 
 	if fqbnArg.String() == "" {
 		fqbnArg.Set(profile.GetFqbn())
 	}
 
-	fqbn, port := arguments.CalculateFQBNAndPort(&portArgs, &fqbnArg, inst, sk.GetDefaultFqbn(), sk.GetDefaultPort(), sk.GetDefaultProtocol())
+	fqbn, port := arguments.CalculateFQBNAndPort(ctx, &portArgs, &fqbnArg, inst, srv, sk.GetDefaultFqbn(), sk.GetDefaultPort(), sk.GetDefaultProtocol())
 
 	if keysKeychain != "" || signKey != "" || encryptKey != "" {
 		arguments.CheckFlagsMandatory(cmd, "keys-keychain", "sign-key", "encrypt-key")
@@ -232,6 +231,7 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
 		Warnings:                      warnings,
 		Verbose:                       verbose,
 		Quiet:                         quiet,
+		ExportBinaries:                &exportBinaries,
 		ExportDir:                     exportDir,
 		Libraries:                     libraries,
 		OptimizeForDebug:              optimizeForDebug,
@@ -246,11 +246,13 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
 		DoNotExpandBuildProperties:    showProperties == arguments.ShowPropertiesUnexpanded,
 		Jobs:                          jobs,
 	}
-	builderRes, compileError := compile.Compile(context.Background(), compileRequest, stdOut, stdErr, nil)
+	server, builderResCB := commands.CompilerServerToStreams(ctx, stdOut, stdErr)
+	compileError := srv.Compile(compileRequest, server)
+	builderRes := builderResCB()
 
 	var uploadRes *rpc.UploadResult
 	if compileError == nil && uploadAfterCompile {
-		userFieldRes, err := upload.SupportedUserFields(context.Background(), &rpc.SupportedUserFieldsRequest{
+		userFieldRes, err := srv.SupportedUserFields(ctx, &rpc.SupportedUserFieldsRequest{
 			Instance: inst,
 			Fqbn:     fqbn,
 			Protocol: port.GetProtocol(),
@@ -271,7 +273,7 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
 
 		prog := profile.GetProgrammer()
 		if prog == "" || programmer.GetProgrammer() != "" {
-			prog = programmer.String(inst, fqbn)
+			prog = programmer.String(ctx, inst, srv, fqbn)
 		}
 		if prog == "" {
 			prog = sk.GetDefaultProgrammer()
@@ -289,7 +291,8 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
 			UserFields: fields,
 		}
 
-		if res, err := upload.Upload(context.Background(), uploadRequest, stdOut, stdErr); err != nil {
+		stream, streamRes := commands.UploadToServerStreams(ctx, stdOut, stdErr)
+		if err := srv.Upload(uploadRequest, stream); err != nil {
 			errcode := feedback.ErrGeneric
 			if errors.Is(err, &cmderrors.ProgrammerRequiredForUploadError{}) {
 				errcode = feedback.ErrMissingProgrammer
@@ -299,7 +302,7 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
 			}
 			feedback.Fatal(tr("Error during Upload: %v", err), errcode)
 		} else {
-			uploadRes = res
+			uploadRes = streamRes()
 		}
 	}
 
@@ -384,7 +387,7 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
 			if profileArg.String() == "" {
 				res.Error += fmt.Sprintln()
 
-				if platform, err := core.PlatformSearch(&rpc.PlatformSearchRequest{
+				if platform, err := srv.PlatformSearch(ctx, &rpc.PlatformSearchRequest{
 					Instance:   inst,
 					SearchArgs: platformErr.Platform,
 				}); err != nil {
diff --git a/internal/cli/config/add.go b/internal/cli/config/add.go
index 5018b0440de..059ecad07c1 100644
--- a/internal/cli/config/add.go
+++ b/internal/cli/config/add.go
@@ -16,35 +16,18 @@
 package config
 
 import (
+	"context"
+	"encoding/json"
 	"os"
-	"reflect"
+	"slices"
 
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 )
 
-func uniquify[T comparable](s []T) []T {
-	// use a map, which enforces unique keys
-	inResult := make(map[T]bool)
-	var result []T
-	// loop through input slice **in order**,
-	// to ensure output retains that order
-	// (except that it removes duplicates)
-	for i := 0; i < len(s); i++ {
-		// attempt to use the element as a key
-		if _, ok := inResult[s[i]]; !ok {
-			// if key didn't exist in map,
-			// add to map and append to result
-			inResult[s[i]] = true
-			result = append(result, s[i])
-		}
-	}
-	return result
-}
-
-func initAddCommand() *cobra.Command {
+func initAddCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	addCommand := &cobra.Command{
 		Use:   "add",
 		Short: tr("Adds one or more values to a setting."),
@@ -53,30 +36,45 @@ func initAddCommand() *cobra.Command {
 			"  " + os.Args[0] + " config add board_manager.additional_urls https://example.com/package_example_index.json\n" +
 			"  " + os.Args[0] + " config add board_manager.additional_urls https://example.com/package_example_index.json https://another-url.com/package_another_index.json\n",
 		Args: cobra.MinimumNArgs(2),
-		Run:  runAddCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			ctx := cmd.Context()
+			runAddCommand(ctx, srv, args)
+		},
 		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			return GetConfigurationKeys(), cobra.ShellCompDirectiveDefault
+			ctx := cmd.Context()
+			return getAllArraySettingsKeys(ctx, srv), cobra.ShellCompDirectiveDefault
 		},
 	}
 	return addCommand
 }
 
-func runAddCommand(cmd *cobra.Command, args []string) {
+func runAddCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string) {
 	logrus.Info("Executing `arduino-cli config add`")
 	key := args[0]
-	kind := validateKey(key)
 
-	if kind != reflect.Slice {
+	if !slices.Contains(getAllArraySettingsKeys(ctx, srv), key) {
 		msg := tr("The key '%[1]v' is not a list of items, can't add to it.\nMaybe use '%[2]s'?", key, "config set")
 		feedback.Fatal(msg, feedback.ErrGeneric)
 	}
 
-	v := configuration.Settings.GetStringSlice(key)
-	v = append(v, args[1:]...)
-	v = uniquify(v)
-	configuration.Settings.Set(key, v)
+	var currentValues []string
+	if resp, err := srv.SettingsGetValue(ctx, &rpc.SettingsGetValueRequest{Key: key}); err != nil {
+		feedback.Fatal(tr("Cannot get the configuration key %[1]s: %[2]v", key, err), feedback.ErrGeneric)
+	} else if err := json.Unmarshal([]byte(resp.GetEncodedValue()), &currentValues); err != nil {
+		feedback.Fatal(tr("Cannot get the configuration key %[1]s: %[2]v", key, err), feedback.ErrGeneric)
+	}
 
-	if err := configuration.Settings.WriteConfig(); err != nil {
-		feedback.Fatal(tr("Can't write config file: %v", err), feedback.ErrGeneric)
+	for _, arg := range args[1:] {
+		if !slices.Contains(currentValues, arg) {
+			currentValues = append(currentValues, arg)
+		}
 	}
+
+	if newValuesJSON, err := json.Marshal(currentValues); err != nil {
+		feedback.Fatal(tr("Cannot remove the configuration key %[1]s: %[2]v", key, err), feedback.ErrGeneric)
+	} else if _, err := srv.SettingsSetValue(ctx, &rpc.SettingsSetValueRequest{Key: key, EncodedValue: string(newValuesJSON)}); err != nil {
+		feedback.Fatal(tr("Cannot remove the configuration key %[1]s: %[2]v", key, err), feedback.ErrGeneric)
+	}
+
+	saveConfiguration(ctx, srv)
 }
diff --git a/internal/cli/config/config.go b/internal/cli/config/config.go
index c59714478d3..2662befe322 100644
--- a/internal/cli/config/config.go
+++ b/internal/cli/config/config.go
@@ -16,45 +16,80 @@
 package config
 
 import (
+	"context"
 	"os"
-	"reflect"
+	"strings"
 
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
+	f "github.com/arduino/arduino-cli/internal/algorithms"
+	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/i18n"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
+	"github.com/arduino/go-paths-helper"
 	"github.com/spf13/cobra"
 )
 
 var tr = i18n.Tr
 
 // NewCommand created a new `config` command
-func NewCommand() *cobra.Command {
+func NewCommand(srv rpc.ArduinoCoreServiceServer, settings *rpc.Configuration) *cobra.Command {
 	configCommand := &cobra.Command{
 		Use:     "config",
 		Short:   tr("Arduino configuration commands."),
 		Example: "  " + os.Args[0] + " config init",
 	}
 
-	configCommand.AddCommand(initAddCommand())
-	configCommand.AddCommand(initDeleteCommand())
-	configCommand.AddCommand(initDumpCommand())
-	configCommand.AddCommand(initGetCommand())
+	configCommand.AddCommand(initAddCommand(srv))
+	configCommand.AddCommand(initDeleteCommand(srv))
+	configCommand.AddCommand(initDumpCommand(srv))
+	configCommand.AddCommand(initGetCommand(srv))
 	configCommand.AddCommand(initInitCommand())
-	configCommand.AddCommand(initRemoveCommand())
-	configCommand.AddCommand(initSetCommand())
+	configCommand.AddCommand(initRemoveCommand(srv))
+	configCommand.AddCommand(initSetCommand(srv))
 
 	return configCommand
 }
 
-// GetConfigurationKeys is an helper function useful to autocomplete.
-// It returns a list of configuration keys which can be changed
-func GetConfigurationKeys() []string {
-	var res []string
-	keys := configuration.Settings.AllKeys()
-	for _, key := range keys {
-		kind, _ := typeOf(key)
-		if kind == reflect.Slice {
-			res = append(res, key)
-		}
+func getAllSettingsKeys(ctx context.Context, srv rpc.ArduinoCoreServiceServer) []string {
+	res, _ := srv.SettingsEnumerate(ctx, &rpc.SettingsEnumerateRequest{})
+	allKeys := f.Map(res.GetEntries(), (*rpc.SettingsEnumerateResponse_Entry).GetKey)
+	return allKeys
+}
+
+func getAllArraySettingsKeys(ctx context.Context, srv rpc.ArduinoCoreServiceServer) []string {
+	res, _ := srv.SettingsEnumerate(ctx, &rpc.SettingsEnumerateRequest{})
+	arrayEntries := f.Filter(res.GetEntries(), func(e *rpc.SettingsEnumerateResponse_Entry) bool {
+		return strings.HasPrefix(e.GetType(), "[]")
+	})
+	arrayKeys := f.Map(arrayEntries, (*rpc.SettingsEnumerateResponse_Entry).GetKey)
+	return arrayKeys
+}
+
+type ctxValue string
+
+// GetConfigFile returns the configuration file path from the context
+func GetConfigFile(ctx context.Context) string {
+	res := ctx.Value(ctxValue("config_file"))
+	if res == nil {
+		return ""
+	}
+	return res.(string)
+}
+
+// SetConfigFile sets the configuration file path in the context
+func SetConfigFile(ctx context.Context, configFile string) context.Context {
+	return context.WithValue(ctx, ctxValue("config_file"), configFile)
+}
+
+func saveConfiguration(ctx context.Context, srv rpc.ArduinoCoreServiceServer) {
+	var outConfig []byte
+	if res, err := srv.ConfigurationSave(ctx, &rpc.ConfigurationSaveRequest{SettingsFormat: "yaml"}); err != nil {
+		feedback.Fatal(tr("Error writing to file: %v", err), feedback.ErrGeneric)
+	} else {
+		outConfig = []byte(res.GetEncodedSettings())
+	}
+
+	configFile := GetConfigFile(ctx)
+	if err := paths.New(configFile).WriteFile(outConfig); err != nil {
+		feedback.Fatal(tr("Error writing to file: %v", err), feedback.ErrGeneric)
 	}
-	return res
 }
diff --git a/internal/cli/config/delete.go b/internal/cli/config/delete.go
index ae5bd7976b5..7ba5437cda2 100644
--- a/internal/cli/config/delete.go
+++ b/internal/cli/config/delete.go
@@ -16,17 +16,16 @@
 package config
 
 import (
+	"context"
 	"os"
 
-	"github.com/arduino/arduino-cli/commands/daemon"
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 )
 
-func initDeleteCommand() *cobra.Command {
+func initDeleteCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	deleteCommand := &cobra.Command{
 		Use:   "delete",
 		Short: tr("Deletes a settings key and all its sub keys."),
@@ -35,25 +34,25 @@ func initDeleteCommand() *cobra.Command {
 			"  " + os.Args[0] + " config delete board_manager\n" +
 			"  " + os.Args[0] + " config delete board_manager.additional_urls",
 		Args: cobra.ExactArgs(1),
-		Run:  runDeleteCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			ctx := cmd.Context()
+			runDeleteCommand(ctx, srv, args)
+		},
 		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			return configuration.Settings.AllKeys(), cobra.ShellCompDirectiveDefault
+			ctx := cmd.Context()
+			return getAllSettingsKeys(ctx, srv), cobra.ShellCompDirectiveDefault
 		},
 	}
 	return deleteCommand
 }
 
-func runDeleteCommand(cmd *cobra.Command, args []string) {
+func runDeleteCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string) {
 	logrus.Info("Executing `arduino-cli config delete`")
-	toDelete := args[0]
 
-	svc := daemon.ArduinoCoreServerImpl{}
-	_, err := svc.SettingsDelete(cmd.Context(), &rpc.SettingsDeleteRequest{Key: toDelete})
-	if err != nil {
-		feedback.Fatal(tr("Cannot delete the key %[1]s: %[2]v", toDelete, err), feedback.ErrGeneric)
-	}
-	_, err = svc.SettingsWrite(cmd.Context(), &rpc.SettingsWriteRequest{FilePath: configuration.Settings.ConfigFileUsed()})
-	if err != nil {
-		feedback.Fatal(tr("Cannot write the file %[1]s: %[2]v", configuration.Settings.ConfigFileUsed(), err), feedback.ErrGeneric)
+	key := args[0]
+	if _, err := srv.SettingsSetValue(ctx, &rpc.SettingsSetValueRequest{Key: key, EncodedValue: ""}); err != nil {
+		feedback.Fatal(tr("Cannot delete the key %[1]s: %[2]v", key, err), feedback.ErrGeneric)
 	}
+
+	saveConfiguration(ctx, srv)
 }
diff --git a/internal/cli/config/dump.go b/internal/cli/config/dump.go
index 9e10bf34f9b..5212ec9d1fa 100644
--- a/internal/cli/config/dump.go
+++ b/internal/cli/config/dump.go
@@ -18,34 +18,56 @@ package config
 import (
 	"os"
 
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
-	"gopkg.in/yaml.v3"
 )
 
-func initDumpCommand() *cobra.Command {
+func initDumpCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var dumpCommand = &cobra.Command{
 		Use:     "dump",
 		Short:   tr("Prints the current configuration"),
 		Long:    tr("Prints the current configuration."),
 		Example: "  " + os.Args[0] + " config dump",
 		Args:    cobra.NoArgs,
-		Run:     runDumpCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			logrus.Info("Executing `arduino-cli config dump`")
+			res := &rawResult{}
+			switch feedback.GetFormat() {
+			case feedback.JSON, feedback.MinifiedJSON:
+				resp, err := srv.ConfigurationSave(cmd.Context(), &rpc.ConfigurationSaveRequest{SettingsFormat: "json"})
+				if err != nil {
+					logrus.Fatalf("Error creating configuration: %v", err)
+				}
+				res.rawJSON = []byte(resp.GetEncodedSettings())
+			case feedback.Text:
+				resp, err := srv.ConfigurationSave(cmd.Context(), &rpc.ConfigurationSaveRequest{SettingsFormat: "yaml"})
+				if err != nil {
+					logrus.Fatalf("Error creating configuration: %v", err)
+				}
+				res.rawYAML = []byte(resp.GetEncodedSettings())
+			default:
+				logrus.Fatalf("Unsupported format: %v", feedback.GetFormat())
+			}
+			feedback.PrintResult(dumpResult{Config: res})
+		},
 	}
 	return dumpCommand
 }
 
-func runDumpCommand(cmd *cobra.Command, args []string) {
-	logrus.Info("Executing `arduino-cli config dump`")
-	feedback.PrintResult(dumpResult{configuration.Settings.AllSettings()})
+type rawResult struct {
+	rawJSON []byte
+	rawYAML []byte
+}
+
+func (r *rawResult) MarshalJSON() ([]byte, error) {
+	// it is already encoded in rawJSON field
+	return r.rawJSON, nil
 }
 
-// output from this command requires special formatting, let's create a dedicated
-// feedback.Result implementation
 type dumpResult struct {
-	Config map[string]interface{} `json:"config"`
+	Config *rawResult `json:"config"`
 }
 
 func (dr dumpResult) Data() interface{} {
@@ -53,10 +75,6 @@ func (dr dumpResult) Data() interface{} {
 }
 
 func (dr dumpResult) String() string {
-	bs, err := yaml.Marshal(dr.Config)
-	if err != nil {
-		// Should never happen
-		panic(tr("unable to marshal config to YAML: %v", err))
-	}
-	return string(bs)
+	// In case of text output do not wrap the output in outer JSON or YAML structure
+	return string(dr.Config.rawYAML)
 }
diff --git a/internal/cli/config/get.go b/internal/cli/config/get.go
index cb2a5cb0072..3dfb5345c20 100644
--- a/internal/cli/config/get.go
+++ b/internal/cli/config/get.go
@@ -16,12 +16,11 @@
 package config
 
 import (
+	"context"
 	"encoding/json"
 	"fmt"
 	"os"
 
-	"github.com/arduino/arduino-cli/commands/daemon"
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/sirupsen/logrus"
@@ -29,7 +28,7 @@ import (
 	"gopkg.in/yaml.v3"
 )
 
-func initGetCommand() *cobra.Command {
+func initGetCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	getCommand := &cobra.Command{
 		Use:   "get",
 		Short: tr("Gets a settings key value."),
@@ -39,26 +38,27 @@ func initGetCommand() *cobra.Command {
 			"  " + os.Args[0] + " config get daemon.port\n" +
 			"  " + os.Args[0] + " config get board_manager.additional_urls",
 		Args: cobra.MinimumNArgs(1),
-		Run:  runGetCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			runGetCommand(cmd.Context(), srv, args)
+		},
 		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			return configuration.Settings.AllKeys(), cobra.ShellCompDirectiveDefault
+			ctx := cmd.Context()
+			return getAllSettingsKeys(ctx, srv), cobra.ShellCompDirectiveDefault
 		},
 	}
 	return getCommand
 }
 
-func runGetCommand(cmd *cobra.Command, args []string) {
+func runGetCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string) {
 	logrus.Info("Executing `arduino-cli config get`")
 
-	svc := daemon.ArduinoCoreServerImpl{}
 	for _, toGet := range args {
-		resp, err := svc.SettingsGetValue(cmd.Context(), &rpc.SettingsGetValueRequest{Key: toGet})
+		resp, err := srv.SettingsGetValue(ctx, &rpc.SettingsGetValueRequest{Key: toGet})
 		if err != nil {
 			feedback.Fatal(tr("Cannot get the configuration key %[1]s: %[2]v", toGet, err), feedback.ErrGeneric)
 		}
 		var result getResult
-		err = json.Unmarshal([]byte(resp.GetJsonData()), &result.resp)
-		if err != nil {
+		if err := json.Unmarshal([]byte(resp.GetEncodedValue()), &result.resp); err != nil {
 			// Should never happen...
 			panic(fmt.Sprintf("Cannot parse JSON for key %[1]s: %[2]v", toGet, err))
 		}
diff --git a/internal/cli/config/init.go b/internal/cli/config/init.go
index afb05f74279..4e297fda7c5 100644
--- a/internal/cli/config/init.go
+++ b/internal/cli/config/init.go
@@ -16,16 +16,18 @@
 package config
 
 import (
+	"context"
+	"encoding/json"
 	"os"
 	"strings"
 
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/arduino/go-paths-helper"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
-	"github.com/spf13/viper"
 )
 
 var (
@@ -50,7 +52,9 @@ func initInitCommand() *cobra.Command {
 		PreRun: func(cmd *cobra.Command, args []string) {
 			arguments.CheckFlagsConflicts(cmd, "dest-file", "dest-dir")
 		},
-		Run: runInitCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			runInitCommand(cmd.Context(), cmd)
+		},
 	}
 	initCommand.Flags().StringVar(&destDir, "dest-dir", "", tr("Sets where to save the configuration file."))
 	initCommand.Flags().StringVar(&destFile, "dest-file", "", tr("Sets where to save the configuration file."))
@@ -58,11 +62,11 @@ func initInitCommand() *cobra.Command {
 	return initCommand
 }
 
-func runInitCommand(cmd *cobra.Command, args []string) {
+func runInitCommand(ctx context.Context, cmd *cobra.Command) {
 	logrus.Info("Executing `arduino-cli config init`")
 
 	var configFileAbsPath *paths.Path
-	var absPath *paths.Path
+	var configFileDir *paths.Path
 	var err error
 
 	switch {
@@ -71,46 +75,95 @@ func runInitCommand(cmd *cobra.Command, args []string) {
 		if err != nil {
 			feedback.Fatal(tr("Cannot find absolute path: %v", err), feedback.ErrGeneric)
 		}
+		configFileDir = configFileAbsPath.Parent()
 
-		absPath = configFileAbsPath.Parent()
-	case destDir == "":
-		destDir = configuration.Settings.GetString("directories.Data")
-		fallthrough
-	default:
-		absPath, err = paths.New(destDir).Abs()
+	case destDir != "":
+		configFileDir, err = paths.New(destDir).Abs()
 		if err != nil {
 			feedback.Fatal(tr("Cannot find absolute path: %v", err), feedback.ErrGeneric)
 		}
-		configFileAbsPath = absPath.Join(defaultFileName)
+		configFileAbsPath = configFileDir.Join(defaultFileName)
+
+	default:
+		configFileAbsPath = paths.New(GetConfigFile(ctx))
+		configFileDir = configFileAbsPath.Parent()
 	}
 
 	if !overwrite && configFileAbsPath.Exist() {
 		feedback.Fatal(tr("Config file already exists, use --overwrite to discard the existing one."), feedback.ErrGeneric)
 	}
 
-	logrus.Infof("Writing config file to: %s", absPath)
+	logrus.Infof("Writing config file to: %s", configFileDir)
 
-	if err := absPath.MkdirAll(); err != nil {
+	if err := configFileDir.MkdirAll(); err != nil {
 		feedback.Fatal(tr("Cannot create config file directory: %v", err), feedback.ErrGeneric)
 	}
 
-	newSettings := viper.New()
-	configuration.SetDefaults(newSettings)
-	configuration.BindFlags(cmd, newSettings)
+	tmpSrv := commands.NewArduinoCoreServer()
 
-	for _, url := range newSettings.GetStringSlice("board_manager.additional_urls") {
-		if strings.Contains(url, ",") {
-			feedback.Fatal(tr("Urls cannot contain commas. Separate multiple urls exported as env var with a space:\n%s", url),
-				feedback.ErrGeneric)
-		}
+	if _, err := tmpSrv.ConfigurationOpen(ctx, &rpc.ConfigurationOpenRequest{SettingsFormat: "yaml", EncodedSettings: ""}); err != nil {
+		feedback.Fatal(tr("Error creating configuration: %v", err), feedback.ErrGeneric)
 	}
 
-	if err := newSettings.WriteConfigAs(configFileAbsPath.String()); err != nil {
+	// Ensure to always output an empty array for additional urls
+	if _, err := tmpSrv.SettingsSetValue(ctx, &rpc.SettingsSetValueRequest{
+		Key: "board_manager.additional_urls", EncodedValue: "[]",
+	}); err != nil {
+		feedback.Fatal(tr("Error creating configuration: %v", err), feedback.ErrGeneric)
+	}
+
+	ApplyGlobalFlagsToConfiguration(ctx, cmd, tmpSrv)
+
+	resp, err := tmpSrv.ConfigurationSave(ctx, &rpc.ConfigurationSaveRequest{SettingsFormat: "yaml"})
+	if err != nil {
+		feedback.Fatal(tr("Error creating configuration: %v", err), feedback.ErrGeneric)
+	}
+
+	if err := configFileAbsPath.WriteFile([]byte(resp.GetEncodedSettings())); err != nil {
 		feedback.Fatal(tr("Cannot create config file: %v", err), feedback.ErrGeneric)
 	}
+
 	feedback.PrintResult(initResult{ConfigFileAbsPath: configFileAbsPath})
 }
 
+// ApplyGlobalFlagsToConfiguration overrides server settings with the flags from the command line
+func ApplyGlobalFlagsToConfiguration(ctx context.Context, cmd *cobra.Command, srv rpc.ArduinoCoreServiceServer) {
+	set := func(k string, v any) {
+		if jsonValue, err := json.Marshal(v); err != nil {
+			feedback.Fatal(tr("Error creating configuration: %v", err), feedback.ErrGeneric)
+		} else if _, err := srv.SettingsSetValue(ctx, &rpc.SettingsSetValueRequest{
+			Key: k, EncodedValue: string(jsonValue),
+		}); err != nil {
+			feedback.Fatal(tr("Error creating configuration: %v", err), feedback.ErrGeneric)
+		}
+
+	}
+
+	if f := cmd.Flags().Lookup("log-level"); f.Changed {
+		logLevel, _ := cmd.Flags().GetString("log-level")
+		set("logging.level", logLevel)
+	}
+	if f := cmd.Flags().Lookup("log-file"); f.Changed {
+		logFile, _ := cmd.Flags().GetString("log-file")
+		set("logging.file", logFile)
+	}
+	if f := cmd.Flags().Lookup("no-color"); f.Changed {
+		noColor, _ := cmd.Flags().GetBool("no-color")
+		set("output.no_color", noColor)
+	}
+	if f := cmd.Flags().Lookup("additional-urls"); f.Changed {
+		urls, _ := cmd.Flags().GetStringSlice("additional-urls")
+		for _, url := range urls {
+			if strings.Contains(url, ",") {
+				feedback.Fatal(
+					tr("Urls cannot contain commas. Separate multiple urls exported as env var with a space:\n%s", url),
+					feedback.ErrBadArgument)
+			}
+		}
+		set("board_manager.additional_urls", urls)
+	}
+}
+
 // output from this command requires special formatting, let's create a dedicated
 // feedback.Result implementation
 type initResult struct {
diff --git a/internal/cli/config/remove.go b/internal/cli/config/remove.go
index d7870172b0c..4114a146a45 100644
--- a/internal/cli/config/remove.go
+++ b/internal/cli/config/remove.go
@@ -16,16 +16,18 @@
 package config
 
 import (
+	"context"
+	"encoding/json"
 	"os"
-	"reflect"
+	"slices"
 
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 )
 
-func initRemoveCommand() *cobra.Command {
+func initRemoveCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	removeCommand := &cobra.Command{
 		Use:   "remove",
 		Short: tr("Removes one or more values from a setting."),
@@ -34,38 +36,43 @@ func initRemoveCommand() *cobra.Command {
 			"  " + os.Args[0] + " config remove board_manager.additional_urls https://example.com/package_example_index.json\n" +
 			"  " + os.Args[0] + " config remove board_manager.additional_urls https://example.com/package_example_index.json https://another-url.com/package_another_index.json\n",
 		Args: cobra.MinimumNArgs(2),
-		Run:  runRemoveCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			ctx := cmd.Context()
+			runRemoveCommand(ctx, srv, args)
+		},
 		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			return GetConfigurationKeys(), cobra.ShellCompDirectiveDefault
+			ctx := cmd.Context()
+			return getAllArraySettingsKeys(ctx, srv), cobra.ShellCompDirectiveDefault
 		},
 	}
 	return removeCommand
 }
 
-func runRemoveCommand(cmd *cobra.Command, args []string) {
+func runRemoveCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string) {
 	logrus.Info("Executing `arduino-cli config remove`")
 	key := args[0]
-	kind := validateKey(key)
 
-	if kind != reflect.Slice {
+	if !slices.Contains(getAllArraySettingsKeys(ctx, srv), key) {
 		msg := tr("The key '%[1]v' is not a list of items, can't remove from it.\nMaybe use '%[2]s'?", key, "config delete")
 		feedback.Fatal(msg, feedback.ErrGeneric)
 	}
 
-	mappedValues := map[string]bool{}
-	for _, v := range configuration.Settings.GetStringSlice(key) {
-		mappedValues[v] = true
+	var currentValues []string
+	if resp, err := srv.SettingsGetValue(ctx, &rpc.SettingsGetValueRequest{Key: key}); err != nil {
+		feedback.Fatal(tr("Cannot get the configuration key %[1]s: %[2]v", key, err), feedback.ErrGeneric)
+	} else if err := json.Unmarshal([]byte(resp.GetEncodedValue()), &currentValues); err != nil {
+		feedback.Fatal(tr("Cannot get the configuration key %[1]s: %[2]v", key, err), feedback.ErrGeneric)
 	}
+
 	for _, arg := range args[1:] {
-		delete(mappedValues, arg)
+		currentValues = slices.DeleteFunc(currentValues, func(in string) bool { return in == arg })
 	}
-	values := []string{}
-	for k := range mappedValues {
-		values = append(values, k)
-	}
-	configuration.Settings.Set(key, values)
 
-	if err := configuration.Settings.WriteConfig(); err != nil {
-		feedback.Fatal(tr("Can't write config file: %v", err), feedback.ErrGeneric)
+	if newValuesJSON, err := json.Marshal(currentValues); err != nil {
+		feedback.Fatal(tr("Cannot remove the configuration key %[1]s: %[2]v", key, err), feedback.ErrGeneric)
+	} else if _, err := srv.SettingsSetValue(ctx, &rpc.SettingsSetValueRequest{Key: key, EncodedValue: string(newValuesJSON)}); err != nil {
+		feedback.Fatal(tr("Cannot remove the configuration key %[1]s: %[2]v", key, err), feedback.ErrGeneric)
 	}
+
+	saveConfiguration(ctx, srv)
 }
diff --git a/internal/cli/config/set.go b/internal/cli/config/set.go
index 8fd002277da..a722f3f176b 100644
--- a/internal/cli/config/set.go
+++ b/internal/cli/config/set.go
@@ -16,17 +16,18 @@
 package config
 
 import (
+	"context"
+	"encoding/json"
 	"os"
-	"reflect"
-	"strconv"
 
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
+	f "github.com/arduino/arduino-cli/internal/algorithms"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 )
 
-func initSetCommand() *cobra.Command {
+func initSetCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	setCommand := &cobra.Command{
 		Use:   "set",
 		Short: tr("Sets a setting value."),
@@ -37,40 +38,40 @@ func initSetCommand() *cobra.Command {
 			"  " + os.Args[0] + " config set sketch.always_export_binaries true\n" +
 			"  " + os.Args[0] + " config set board_manager.additional_urls https://example.com/package_example_index.json https://another-url.com/package_another_index.json",
 		Args: cobra.MinimumNArgs(2),
-		Run:  runSetCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			runSetCommand(cmd.Context(), srv, args)
+		},
 		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			return configuration.Settings.AllKeys(), cobra.ShellCompDirectiveDefault
+			ctx := cmd.Context()
+			return getAllSettingsKeys(ctx, srv), cobra.ShellCompDirectiveDefault
 		},
 	}
 	return setCommand
 }
 
-func runSetCommand(cmd *cobra.Command, args []string) {
+func runSetCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string) {
 	logrus.Info("Executing `arduino-cli config set`")
-	key := args[0]
-	kind := validateKey(key)
 
-	if kind != reflect.Slice && len(args) > 2 {
-		feedback.Fatal(tr("Can't set multiple values in key %v", key), feedback.ErrGeneric)
+	req := &rpc.SettingsSetValueRequest{
+		Key: args[0],
 	}
-
-	var value interface{}
-	switch kind {
-	case reflect.Slice:
-		value = uniquify(args[1:])
-	case reflect.String:
-		value = args[1]
-	case reflect.Bool:
-		var err error
-		value, err = strconv.ParseBool(args[1])
+	if len(args) == 2 {
+		// Single value
+		req.EncodedValue = args[1]
+		req.ValueFormat = "cli"
+	} else {
+		// Uniq Array
+		jsonValues, err := json.Marshal(f.Uniq(args[1:]))
 		if err != nil {
-			feedback.Fatal(tr("error parsing value: %v", err), feedback.ErrGeneric)
+			feedback.Fatal(tr("Error setting value: %v", err), feedback.ErrGeneric)
 		}
+		req.EncodedValue = string(jsonValues)
+		req.ValueFormat = "json"
 	}
 
-	configuration.Settings.Set(key, value)
-
-	if err := configuration.Settings.WriteConfig(); err != nil {
-		feedback.Fatal(tr("Writing config file: %v", err), feedback.ErrGeneric)
+	if _, err := srv.SettingsSetValue(ctx, req); err != nil {
+		feedback.Fatal(tr("Error setting value: %v", err), feedback.ErrGeneric)
 	}
+
+	saveConfiguration(ctx, srv)
 }
diff --git a/internal/cli/config/validate.go b/internal/cli/config/validate.go
deleted file mode 100644
index f494bfaac0b..00000000000
--- a/internal/cli/config/validate.go
+++ /dev/null
@@ -1,61 +0,0 @@
-// This file is part of arduino-cli.
-//
-// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
-//
-// This software is released under the GNU General Public License version 3,
-// which covers the main part of arduino-cli.
-// The terms of this license can be found at:
-// https://www.gnu.org/licenses/gpl-3.0.en.html
-//
-// You can be released from the requirements of the above licenses by purchasing
-// a commercial license. Buying such a license is mandatory if you want to
-// modify or otherwise use the software for commercial activities involving the
-// Arduino software without disclosing the source code of your own applications.
-// To purchase a commercial license, send an email to license@arduino.cc.
-
-package config
-
-import (
-	"fmt"
-	"reflect"
-
-	"github.com/arduino/arduino-cli/internal/cli/feedback"
-)
-
-var validMap = map[string]reflect.Kind{
-	"board_manager.additional_urls": reflect.Slice,
-	"daemon.port":                   reflect.String,
-	"directories.data":              reflect.String,
-	"directories.downloads":         reflect.String,
-	"directories.user":              reflect.String,
-	"directories.builtin.tools":     reflect.String,
-	"directories.builtin.libraries": reflect.String,
-	"library.enable_unsafe_install": reflect.Bool,
-	"locale":                        reflect.String,
-	"logging.file":                  reflect.String,
-	"logging.format":                reflect.String,
-	"logging.level":                 reflect.String,
-	"sketch.always_export_binaries": reflect.Bool,
-	"metrics.addr":                  reflect.String,
-	"metrics.enabled":               reflect.Bool,
-	"network.proxy":                 reflect.String,
-	"network.user_agent_ext":        reflect.String,
-	"output.no_color":               reflect.Bool,
-	"updater.enable_notification":   reflect.Bool,
-}
-
-func typeOf(key string) (reflect.Kind, error) {
-	t, ok := validMap[key]
-	if !ok {
-		return reflect.Invalid, fmt.Errorf(tr("Settings key doesn't exist"))
-	}
-	return t, nil
-}
-
-func validateKey(key string) reflect.Kind {
-	kind, err := typeOf(key)
-	if err != nil {
-		feedback.FatalError(err, feedback.ErrGeneric)
-	}
-	return kind
-}
diff --git a/internal/cli/configuration/board_manager.go b/internal/cli/configuration/board_manager.go
new file mode 100644
index 00000000000..22f982f5404
--- /dev/null
+++ b/internal/cli/configuration/board_manager.go
@@ -0,0 +1,23 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package configuration
+
+func (settings *Settings) BoardManagerAdditionalUrls() []string {
+	if urls, ok, _ := settings.GetStringSliceOk("board_manager.additional_urls"); ok {
+		return urls
+	}
+	return settings.Defaults.GetStringSlice("board_manager.additional_urls")
+}
diff --git a/internal/cli/configuration/build_cache.go b/internal/cli/configuration/build_cache.go
new file mode 100644
index 00000000000..a4bca322307
--- /dev/null
+++ b/internal/cli/configuration/build_cache.go
@@ -0,0 +1,34 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package configuration
+
+import "time"
+
+// GetCompilationsBeforeBuildCachePurge returns the number of compilations before the build cache is purged.
+func (s *Settings) GetCompilationsBeforeBuildCachePurge() uint {
+	if res, ok, _ := s.GetUintOk("build_cache.compilations_before_purge"); ok {
+		return res
+	}
+	return s.Defaults.GetUint("build_cache.compilations_before_purge")
+}
+
+// GetBuildCacheTTL returns the time-to-live of the build cache (i.e. the minimum age to wait before purging the cache).
+func (s *Settings) GetBuildCacheTTL() time.Duration {
+	if res, ok, _ := s.GetDurationOk("build_cache.ttl"); ok {
+		return res
+	}
+	return s.Defaults.GetDuration("build_cache.ttl")
+}
diff --git a/internal/cli/configuration/configuration.go b/internal/cli/configuration/configuration.go
index e251b80c514..5176acc9a28 100644
--- a/internal/cli/configuration/configuration.go
+++ b/internal/cli/configuration/configuration.go
@@ -19,63 +19,29 @@ import (
 	"os"
 	"path/filepath"
 	"runtime"
-	"strings"
 
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
+	"github.com/arduino/arduino-cli/internal/go-configmap"
 	"github.com/arduino/arduino-cli/internal/i18n"
-	paths "github.com/arduino/go-paths-helper"
 	"github.com/arduino/go-win32-utils"
-	"github.com/spf13/cobra"
-	"github.com/spf13/viper"
 )
 
-// Settings is a global instance of viper holding configurations for the CLI and the gRPC consumers
-var Settings *viper.Viper
-
 var tr = i18n.Tr
 
-// Init initialize defaults and read the configuration file.
-// Please note the logging system hasn't been configured yet,
-// so logging shouldn't be used here.
-func Init(configFile string) *viper.Viper {
-	// Create a new viper instance with default values for all the settings
-	settings := viper.New()
-	SetDefaults(settings)
-
-	// Set config name and config path
-	if configFilePath := paths.New(configFile); configFilePath != nil {
-		settings.SetConfigName(strings.TrimSuffix(configFilePath.Base(), configFilePath.Ext()))
-		settings.AddConfigPath(configFilePath.Parent().String())
-	} else {
-		configDir := settings.GetString("directories.Data")
-		// Get default data path if none was provided
-		if configDir == "" {
-			configDir = getDefaultArduinoDataDir()
-		}
-
-		settings.SetConfigName("arduino-cli")
-		settings.AddConfigPath(configDir)
-	}
-
-	// Attempt to read config file
-	if err := settings.ReadInConfig(); err != nil {
-		// ConfigFileNotFoundError is acceptable, anything else
-		// should be reported to the user
-		if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
-			feedback.Warning(tr("Error reading config file: %v", err))
-		}
-	}
-
-	return settings
+// Settings contains the configuration of the Arduino CLI core service
+type Settings struct {
+	*configmap.Map
+	Defaults *configmap.Map
 }
 
-// BindFlags creates all the flags binding between the cobra Command and the instance of viper
-func BindFlags(cmd *cobra.Command, settings *viper.Viper) {
-	settings.BindPFlag("logging.level", cmd.Flag("log-level"))
-	settings.BindPFlag("logging.file", cmd.Flag("log-file"))
-	settings.BindPFlag("logging.format", cmd.Flag("log-format"))
-	settings.BindPFlag("board_manager.additional_urls", cmd.Flag("additional-urls"))
-	settings.BindPFlag("output.no_color", cmd.Flag("no-color"))
+// NewSettings creates a new instance of Settings with the default values set
+func NewSettings() *Settings {
+	res := &Settings{
+		Map:      configmap.New(),
+		Defaults: configmap.New(),
+	}
+	SetDefaults(res)
+	return res
 }
 
 // getDefaultArduinoDataDir returns the full path to the default arduino folder
@@ -128,11 +94,6 @@ func getDefaultUserDir() string {
 	}
 }
 
-// GetDefaultBuiltinLibrariesDir returns the full path to the default builtin libraries dir
-func GetDefaultBuiltinLibrariesDir() string {
-	return filepath.Join(getDefaultArduinoDataDir(), "libraries")
-}
-
 // FindConfigFileInArgsFallbackOnEnv returns the config file path using the
 // argument '--config-file' (if specified), if empty looks for the ARDUINO_CONFIG_FILE env,
 // or looking in the current working dir
@@ -145,5 +106,14 @@ func FindConfigFileInArgsFallbackOnEnv(args []string) string {
 			}
 		}
 	}
-	return os.Getenv("ARDUINO_CONFIG_FILE")
+	if p, ok := os.LookupEnv("ARDUINO_CONFIG_FILE"); ok {
+		return p
+	}
+	if p, ok := os.LookupEnv("ARDUINO_DIRECTORIES_DATA"); ok {
+		return filepath.Join(p, "arduino-cli.yaml")
+	}
+	if p, ok := os.LookupEnv("ARDUINO_DATA_DIR"); ok {
+		return filepath.Join(p, "arduino-cli.yaml")
+	}
+	return filepath.Join(getDefaultArduinoDataDir(), "arduino-cli.yaml")
 }
diff --git a/internal/cli/configuration/configuration_test.go b/internal/cli/configuration/configuration_test.go
index 8b0fd3938cc..23af3a964e1 100644
--- a/internal/cli/configuration/configuration_test.go
+++ b/internal/cli/configuration/configuration_test.go
@@ -16,52 +16,35 @@
 package configuration
 
 import (
-	"fmt"
-	"os"
 	"path/filepath"
 	"testing"
 
 	"github.com/stretchr/testify/require"
 )
 
-func tmpDirOrDie() string {
-	dir, err := os.MkdirTemp(os.TempDir(), "cli_test")
-	if err != nil {
-		panic(fmt.Sprintf("error creating tmp dir: %v", err))
-	}
-	// Symlinks are evaluated becase the temp folder on Mac OS is inside /var, it's not writable
-	// and is a symlink to /private/var, we want the full path so we do this
-	dir, err = filepath.EvalSymlinks(dir)
-	if err != nil {
-		panic(fmt.Sprintf("error evaluating tmp dir symlink: %v", err))
-	}
-	return dir
-}
-
 func TestInit(t *testing.T) {
-	tmp := tmpDirOrDie()
-	defer os.RemoveAll(tmp)
-	settings := Init(filepath.Join(tmp, "arduino-cli.yaml"))
-	require.NotNil(t, settings)
+	settings := NewSettings()
 
-	require.Equal(t, "info", settings.GetString("logging.level"))
-	require.Equal(t, "text", settings.GetString("logging.format"))
+	require.Equal(t, "info", settings.Defaults.GetString("logging.level"))
+	require.Equal(t, "text", settings.Defaults.GetString("logging.format"))
 
-	require.Empty(t, settings.GetStringSlice("board_manager.additional_urls"))
+	require.Empty(t, settings.Defaults.GetStringSlice("board_manager.additional_urls"))
 
-	require.NotEmpty(t, settings.GetString("directories.Data"))
-	require.NotEmpty(t, settings.GetString("directories.Downloads"))
-	require.NotEmpty(t, settings.GetString("directories.User"))
+	require.NotEmpty(t, settings.Defaults.GetString("directories.data"))
+	require.Empty(t, settings.Defaults.GetString("directories.downloads"))
+	require.NotEmpty(t, settings.DownloadsDir().String())
+	require.NotEmpty(t, settings.Defaults.GetString("directories.user"))
 
-	require.Equal(t, "50051", settings.GetString("daemon.port"))
+	require.Equal(t, "50051", settings.Defaults.GetString("daemon.port"))
 
-	require.Equal(t, true, settings.GetBool("metrics.enabled"))
-	require.Equal(t, ":9090", settings.GetString("metrics.addr"))
+	require.Equal(t, true, settings.Defaults.GetBool("metrics.enabled"))
+	require.Equal(t, ":9090", settings.Defaults.GetString("metrics.addr"))
 }
 
 func TestFindConfigFile(t *testing.T) {
+	defaultConfigFile := filepath.Join(getDefaultArduinoDataDir(), "arduino-cli.yaml")
 	configFile := FindConfigFileInArgsFallbackOnEnv([]string{"--config-file"})
-	require.Equal(t, "", configFile)
+	require.Equal(t, defaultConfigFile, configFile)
 
 	configFile = FindConfigFileInArgsFallbackOnEnv([]string{"--config-file", "some/path/to/config"})
 	require.Equal(t, "some/path/to/config", configFile)
@@ -70,7 +53,7 @@ func TestFindConfigFile(t *testing.T) {
 	require.Equal(t, "some/path/to/config/arduino-cli.yaml", configFile)
 
 	configFile = FindConfigFileInArgsFallbackOnEnv([]string{})
-	require.Equal(t, "", configFile)
+	require.Equal(t, defaultConfigFile, configFile)
 
 	t.Setenv("ARDUINO_CONFIG_FILE", "some/path/to/config")
 	configFile = FindConfigFileInArgsFallbackOnEnv([]string{})
diff --git a/internal/cli/configuration/daemon.go b/internal/cli/configuration/daemon.go
new file mode 100644
index 00000000000..9ae053c2aee
--- /dev/null
+++ b/internal/cli/configuration/daemon.go
@@ -0,0 +1,23 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package configuration
+
+func (s *Settings) DaemonPort() string {
+	if port, ok, _ := s.GetStringOk("daemon.port"); ok {
+		return port
+	}
+	return s.Defaults.GetString("daemon.port")
+}
diff --git a/internal/cli/configuration/defaults.go b/internal/cli/configuration/defaults.go
index be1a0088a62..8c05b793d19 100644
--- a/internal/cli/configuration/defaults.go
+++ b/internal/cli/configuration/defaults.go
@@ -16,57 +16,78 @@
 package configuration
 
 import (
-	"path/filepath"
-	"strings"
+	"os"
 	"time"
-
-	"github.com/spf13/viper"
 )
 
 // SetDefaults sets the default values for certain keys
-func SetDefaults(settings *viper.Viper) {
+func SetDefaults(settings *Settings) {
+	setKeyTypeSchema := func(k string, v any) {
+		settings.SetKeyTypeSchema(k, v)
+		settings.Defaults.SetKeyTypeSchema(k, v)
+	}
+	setDefaultValueAndKeyTypeSchema := func(k string, v any) {
+		setKeyTypeSchema(k, v)
+		settings.Defaults.Set(k, v)
+	}
+
 	// logging
-	settings.SetDefault("logging.level", "info")
-	settings.SetDefault("logging.format", "text")
+	setDefaultValueAndKeyTypeSchema("logging.level", "info")
+	setDefaultValueAndKeyTypeSchema("logging.format", "text")
+	setKeyTypeSchema("logging.file", "")
 
 	// Libraries
-	settings.SetDefault("library.enable_unsafe_install", false)
+	setDefaultValueAndKeyTypeSchema("library.enable_unsafe_install", false)
 
 	// Boards Manager
-	settings.SetDefault("board_manager.additional_urls", []string{})
+	setDefaultValueAndKeyTypeSchema("board_manager.additional_urls", []string{})
 
 	// arduino directories
-	settings.SetDefault("directories.Data", getDefaultArduinoDataDir())
-	settings.SetDefault("directories.Downloads", filepath.Join(getDefaultArduinoDataDir(), "staging"))
-	settings.SetDefault("directories.User", getDefaultUserDir())
+	setDefaultValueAndKeyTypeSchema("directories.data", getDefaultArduinoDataDir())
+	setDefaultValueAndKeyTypeSchema("directories.downloads", "")
+	setDefaultValueAndKeyTypeSchema("directories.user", getDefaultUserDir())
+	setKeyTypeSchema("directories.builtin.libraries", "")
 
 	// Sketch compilation
-	settings.SetDefault("sketch.always_export_binaries", false)
-	settings.SetDefault("build_cache.ttl", time.Hour*24*30)
-	settings.SetDefault("build_cache.compilations_before_purge", 10)
+	setDefaultValueAndKeyTypeSchema("sketch.always_export_binaries", false)
+	setDefaultValueAndKeyTypeSchema("build_cache.ttl", (time.Hour * 24 * 30).String())
+	setDefaultValueAndKeyTypeSchema("build_cache.compilations_before_purge", uint(10))
 
 	// daemon settings
-	settings.SetDefault("daemon.port", "50051")
+	setDefaultValueAndKeyTypeSchema("daemon.port", "50051")
 
 	// metrics settings
-	settings.SetDefault("metrics.enabled", true)
-	settings.SetDefault("metrics.addr", ":9090")
+	setDefaultValueAndKeyTypeSchema("metrics.enabled", true)
+	setDefaultValueAndKeyTypeSchema("metrics.addr", ":9090")
 
 	// output settings
-	settings.SetDefault("output.no_color", false)
+	setDefaultValueAndKeyTypeSchema("output.no_color", false)
 
 	// updater settings
-	settings.SetDefault("updater.enable_notification", true)
+	setDefaultValueAndKeyTypeSchema("updater.enable_notification", true)
+
+	// network settings
+	setKeyTypeSchema("network.proxy", "")
+	setKeyTypeSchema("network.user_agent_ext", "")
+
+	// locale
+	setDefaultValueAndKeyTypeSchema("locale", "en")
+}
 
+// InjectEnvVars change settings based on the environment variables values
+func InjectEnvVars(settings *Settings) {
 	// Bind env vars
-	settings.SetEnvPrefix("ARDUINO")
-	settings.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
-	settings.AutomaticEnv()
+	settings.InjectEnvVars(os.Environ(), "ARDUINO")
 
 	// Bind env aliases to keep backward compatibility
-	settings.BindEnv("library.enable_unsafe_install", "ARDUINO_ENABLE_UNSAFE_LIBRARY_INSTALL")
-	settings.BindEnv("directories.User", "ARDUINO_SKETCHBOOK_DIR")
-	settings.BindEnv("directories.Downloads", "ARDUINO_DOWNLOADS_DIR")
-	settings.BindEnv("directories.Data", "ARDUINO_DATA_DIR")
-	settings.BindEnv("sketch.always_export_binaries", "ARDUINO_SKETCH_ALWAYS_EXPORT_BINARIES")
+	setIfEnvExists := func(key, env string) {
+		if v, ok := os.LookupEnv(env); ok {
+			settings.SetFromCLIArgs(key, v)
+		}
+	}
+	setIfEnvExists("library.enable_unsafe_install", "ARDUINO_ENABLE_UNSAFE_LIBRARY_INSTALL")
+	setIfEnvExists("directories.user", "ARDUINO_SKETCHBOOK_DIR")
+	setIfEnvExists("directories.downloads", "ARDUINO_DOWNLOADS_DIR")
+	setIfEnvExists("directories.data", "ARDUINO_DATA_DIR")
+	setIfEnvExists("sketch.always_export_binaries", "ARDUINO_SKETCH_ALWAYS_EXPORT_BINARIES")
 }
diff --git a/internal/cli/configuration/directories.go b/internal/cli/configuration/directories.go
index 27669e9017c..a58aa21f885 100644
--- a/internal/cli/configuration/directories.go
+++ b/internal/cli/configuration/directories.go
@@ -17,24 +17,18 @@ package configuration
 
 import (
 	"github.com/arduino/go-paths-helper"
-	"github.com/spf13/viper"
 )
 
 // HardwareDirectories returns all paths that may contains hardware packages.
-func HardwareDirectories(settings *viper.Viper) paths.PathList {
+func (settings *Settings) HardwareDirectories() paths.PathList {
 	res := paths.PathList{}
 
-	if settings.IsSet("directories.Data") {
-		packagesDir := PackagesDir(Settings)
-		if packagesDir.IsDir() {
-			res.Add(packagesDir)
-		}
+	if packagesDir := settings.PackagesDir(); packagesDir.IsDir() {
+		res.Add(packagesDir)
 	}
 
-	if settings.IsSet("directories.User") {
-		skDir := paths.New(settings.GetString("directories.User"))
-		hwDir := skDir.Join("hardware")
-		if hwDir.IsDir() {
+	if userDir, ok, _ := settings.GetStringOk("directories.user"); ok {
+		if hwDir := paths.New(userDir, "hardware"); hwDir.IsDir() {
 			res.Add(hwDir)
 		}
 	}
@@ -44,34 +38,51 @@ func HardwareDirectories(settings *viper.Viper) paths.PathList {
 
 // IDEBuiltinLibrariesDir returns the IDE-bundled libraries path. Usually
 // this directory is present in the Arduino IDE.
-func IDEBuiltinLibrariesDir(settings *viper.Viper) *paths.Path {
-	return paths.New(Settings.GetString("directories.builtin.Libraries"))
+func (settings *Settings) IDEBuiltinLibrariesDir() *paths.Path {
+	if builtinLibsDir, ok, _ := settings.GetStringOk("directories.builtin.libraries"); ok {
+		return paths.New(builtinLibsDir)
+	}
+	return nil
 }
 
 // LibrariesDir returns the full path to the user directory containing
 // custom libraries
-func LibrariesDir(settings *viper.Viper) *paths.Path {
-	return paths.New(settings.GetString("directories.User")).Join("libraries")
+func (settings *Settings) LibrariesDir() *paths.Path {
+	return settings.UserDir().Join("libraries")
+}
+
+// UserDir returns the full path to the user directory
+func (settings *Settings) UserDir() *paths.Path {
+	if userDir, ok, _ := settings.GetStringOk("directories.user"); ok {
+		return paths.New(userDir)
+	}
+	return paths.New(settings.Defaults.GetString("directories.user"))
 }
 
 // PackagesDir returns the full path to the packages folder
-func PackagesDir(settings *viper.Viper) *paths.Path {
-	return DataDir(settings).Join("packages")
+func (settings *Settings) PackagesDir() *paths.Path {
+	return settings.DataDir().Join("packages")
 }
 
 // ProfilesCacheDir returns the full path to the profiles cache directory
 // (it contains all the platforms and libraries used to compile a sketch
 // using profiles)
-func ProfilesCacheDir(settings *viper.Viper) *paths.Path {
-	return DataDir(settings).Join("internal")
+func (settings *Settings) ProfilesCacheDir() *paths.Path {
+	return settings.DataDir().Join("internal")
 }
 
 // DataDir returns the full path to the data directory
-func DataDir(settings *viper.Viper) *paths.Path {
-	return paths.New(settings.GetString("directories.Data"))
+func (settings *Settings) DataDir() *paths.Path {
+	if dataDir, ok, _ := settings.GetStringOk("directories.data"); ok {
+		return paths.New(dataDir)
+	}
+	return paths.New(settings.Defaults.GetString("directories.data"))
 }
 
 // DownloadsDir returns the full path to the download cache directory
-func DownloadsDir(settings *viper.Viper) *paths.Path {
-	return paths.New(settings.GetString("directories.Downloads"))
+func (settings *Settings) DownloadsDir() *paths.Path {
+	if downloadDir, ok, _ := settings.GetStringOk("directories.downloads"); ok {
+		return paths.New(downloadDir)
+	}
+	return settings.DataDir().Join("staging")
 }
diff --git a/internal/cli/configuration/libraries.go b/internal/cli/configuration/libraries.go
new file mode 100644
index 00000000000..6dc73b9b0d6
--- /dev/null
+++ b/internal/cli/configuration/libraries.go
@@ -0,0 +1,20 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package configuration
+
+func (s *Settings) LibraryEnableUnsafeInstall() bool {
+	return s.GetBool("library.enable_unsafe_install")
+}
diff --git a/internal/cli/configuration/locale.go b/internal/cli/configuration/locale.go
new file mode 100644
index 00000000000..e292ea8f170
--- /dev/null
+++ b/internal/cli/configuration/locale.go
@@ -0,0 +1,20 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package configuration
+
+func (s *Settings) Locale() string {
+	return s.Defaults.GetString("locale")
+}
diff --git a/internal/cli/configuration/logging.go b/internal/cli/configuration/logging.go
new file mode 100644
index 00000000000..73f1bacab35
--- /dev/null
+++ b/internal/cli/configuration/logging.go
@@ -0,0 +1,42 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package configuration
+
+import "github.com/arduino/go-paths-helper"
+
+func (s *Settings) LoggingLevel() string {
+	if l, ok, _ := s.GetStringOk("logging.level"); ok {
+		return l
+	}
+	return s.Defaults.GetString("logging.level")
+}
+
+func (s *Settings) LoggingFormat() string {
+	if l, ok, _ := s.GetStringOk("logging.format"); ok {
+		return l
+	}
+	return s.Defaults.GetString("logging.format")
+}
+
+func (s *Settings) LoggingFile() *paths.Path {
+	if l, ok, _ := s.GetStringOk("logging.file"); ok && l != "" {
+		return paths.New(l)
+	}
+	if l, ok, _ := s.Defaults.GetStringOk("logging.file"); ok && l != "" {
+		return paths.New(l)
+	}
+	return nil
+}
diff --git a/internal/cli/configuration/network.go b/internal/cli/configuration/network.go
index 793c67de155..7fac7ee6890 100644
--- a/internal/cli/configuration/network.go
+++ b/internal/cli/configuration/network.go
@@ -1,6 +1,6 @@
 // This file is part of arduino-cli.
 //
-// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
 //
 // This software is released under the GNU General Public License version 3,
 // which covers the main part of arduino-cli.
@@ -17,16 +17,18 @@ package configuration
 
 import (
 	"fmt"
+	"net/http"
 	"net/url"
 	"os"
 	"runtime"
 
+	"github.com/arduino/arduino-cli/commands/cmderrors"
 	"github.com/arduino/arduino-cli/version"
-	"github.com/spf13/viper"
+	"go.bug.st/downloader/v2"
 )
 
 // UserAgent returns the user agent (mainly used by HTTP clients)
-func UserAgent(settings *viper.Viper) string {
+func (settings *Settings) UserAgent() string {
 	subComponent := ""
 	if settings != nil {
 		subComponent = settings.GetString("network.user_agent_ext")
@@ -49,15 +51,14 @@ func UserAgent(settings *viper.Viper) string {
 		extendedUA)
 }
 
+// ExtraUserAgent returns the extended user-agent section provided via configuration settings
+func (settings *Settings) ExtraUserAgent() string {
+	return settings.GetString("network.user_agent_ext")
+}
+
 // NetworkProxy returns the proxy configuration (mainly used by HTTP clients)
-func NetworkProxy(settings *viper.Viper) (*url.URL, error) {
-	if settings == nil || !settings.IsSet("network.proxy") {
-		return nil, nil
-	}
-	if proxyConfig := settings.GetString("network.proxy"); proxyConfig == "" {
-		// empty configuration
-		// this workaround must be here until viper can UnSet properties:
-		// https://github.com/spf13/viper/pull/519
+func (settings *Settings) NetworkProxy() (*url.URL, error) {
+	if proxyConfig, ok, _ := settings.GetStringOk("network.proxy"); !ok {
 		return nil, nil
 	} else if proxy, err := url.Parse(proxyConfig); err != nil {
 		return nil, fmt.Errorf(tr("Invalid network.proxy '%[1]s': %[2]s"), proxyConfig, err)
@@ -65,3 +66,42 @@ func NetworkProxy(settings *viper.Viper) (*url.URL, error) {
 		return proxy, nil
 	}
 }
+
+// NewHttpClient returns a new http client for use in the arduino-cli
+func (settings *Settings) NewHttpClient() (*http.Client, error) {
+	proxy, err := settings.NetworkProxy()
+	if err != nil {
+		return nil, err
+	}
+	return &http.Client{
+		Transport: &httpClientRoundTripper{
+			transport: &http.Transport{
+				Proxy: http.ProxyURL(proxy),
+			},
+			userAgent: settings.UserAgent(),
+		},
+	}, nil
+}
+
+type httpClientRoundTripper struct {
+	transport http.RoundTripper
+	userAgent string
+}
+
+func (h *httpClientRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
+	req.Header.Add("User-Agent", h.userAgent)
+	return h.transport.RoundTrip(req)
+}
+
+// DownloaderConfig returns the downloader configuration based on current settings.
+func (settings *Settings) DownloaderConfig() (downloader.Config, error) {
+	httpClient, err := settings.NewHttpClient()
+	if err != nil {
+		return downloader.Config{}, &cmderrors.InvalidArgumentError{
+			Message: tr("Could not connect via HTTP"),
+			Cause:   err}
+	}
+	return downloader.Config{
+		HttpClient: *httpClient,
+	}, nil
+}
diff --git a/internal/arduino/httpclient/httpclient_test.go b/internal/cli/configuration/network_test.go
similarity index 77%
rename from internal/arduino/httpclient/httpclient_test.go
rename to internal/cli/configuration/network_test.go
index a2dbc61c291..6c8a7def80d 100644
--- a/internal/arduino/httpclient/httpclient_test.go
+++ b/internal/cli/configuration/network_test.go
@@ -13,16 +13,16 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package httpclient
+package configuration_test
 
 import (
 	"fmt"
 	"io"
 	"net/http"
 	"net/http/httptest"
-	"net/url"
 	"testing"
 
+	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/stretchr/testify/require"
 )
 
@@ -32,9 +32,10 @@ func TestUserAgentHeader(t *testing.T) {
 	}))
 	defer ts.Close()
 
-	client := NewWithConfig(&Config{
-		UserAgent: "test-user-agent",
-	})
+	settings := configuration.NewSettings()
+	require.NoError(t, settings.Set("network.user_agent_ext", "test-user-agent"))
+	client, err := settings.NewHttpClient()
+	require.NoError(t, err)
 
 	request, err := http.NewRequest("GET", ts.URL, nil)
 	require.NoError(t, err)
@@ -45,7 +46,8 @@ func TestUserAgentHeader(t *testing.T) {
 	b, err := io.ReadAll(response.Body)
 	require.NoError(t, err)
 
-	require.Equal(t, "test-user-agent", string(b))
+	fmt.Println("RESPONSE:", string(b))
+	require.Contains(t, string(b), "test-user-agent")
 }
 
 func TestProxy(t *testing.T) {
@@ -54,13 +56,11 @@ func TestProxy(t *testing.T) {
 	}))
 	defer ts.Close()
 
-	proxyURL, err := url.Parse(ts.URL)
+	settings := configuration.NewSettings()
+	settings.Set("network.proxy", ts.URL)
+	client, err := settings.NewHttpClient()
 	require.NoError(t, err)
 
-	client := NewWithConfig(&Config{
-		Proxy: proxyURL,
-	})
-
 	request, err := http.NewRequest("GET", "http://arduino.cc", nil)
 	require.NoError(t, err)
 
diff --git a/commands/board/board.go b/internal/cli/configuration/output.go
similarity index 81%
rename from commands/board/board.go
rename to internal/cli/configuration/output.go
index 3baebc26d27..67ae5e72d99 100644
--- a/commands/board/board.go
+++ b/internal/cli/configuration/output.go
@@ -1,6 +1,6 @@
 // This file is part of arduino-cli.
 //
-// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
 //
 // This software is released under the GNU General Public License version 3,
 // which covers the main part of arduino-cli.
@@ -13,8 +13,8 @@
 // Arduino software without disclosing the source code of your own applications.
 // To purchase a commercial license, send an email to license@arduino.cc.
 
-package board
+package configuration
 
-import "github.com/arduino/arduino-cli/internal/i18n"
-
-var tr = i18n.Tr
+func (s *Settings) NoColor() bool {
+	return s.GetBool("output.no_color")
+}
diff --git a/internal/cli/configuration/sketch.go b/internal/cli/configuration/sketch.go
new file mode 100644
index 00000000000..27638b67df5
--- /dev/null
+++ b/internal/cli/configuration/sketch.go
@@ -0,0 +1,25 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package configuration
+
+// SketchAlwaysExportBinaries returns true if the compile command should
+// export binaries by default.
+func (settings *Settings) SketchAlwaysExportBinaries() bool {
+	if res, ok, _ := settings.GetBoolOk("sketch.always_export_binaries"); ok {
+		return res
+	}
+	return settings.Defaults.GetBool("sketch.always_export_binaries")
+}
diff --git a/internal/cli/configuration/updater.go b/internal/cli/configuration/updater.go
new file mode 100644
index 00000000000..3f553782254
--- /dev/null
+++ b/internal/cli/configuration/updater.go
@@ -0,0 +1,23 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package configuration
+
+func (s *Settings) UpdaterEnableNotification() bool {
+	if en, ok, _ := s.GetBoolOk("updater.enable_notification"); ok {
+		return en
+	}
+	return s.GetBool("updater.enable_notification")
+}
diff --git a/internal/cli/core/core.go b/internal/cli/core/core.go
index 3792ad13227..ac337df6c9e 100644
--- a/internal/cli/core/core.go
+++ b/internal/cli/core/core.go
@@ -19,13 +19,14 @@ import (
 	"os"
 
 	"github.com/arduino/arduino-cli/internal/i18n"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/spf13/cobra"
 )
 
 var tr = i18n.Tr
 
 // NewCommand created a new `core` command
-func NewCommand() *cobra.Command {
+func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	coreCommand := &cobra.Command{
 		Use:     "core",
 		Short:   tr("Arduino core operations."),
@@ -33,13 +34,13 @@ func NewCommand() *cobra.Command {
 		Example: "  " + os.Args[0] + " core update-index",
 	}
 
-	coreCommand.AddCommand(initDownloadCommand())
-	coreCommand.AddCommand(initInstallCommand())
-	coreCommand.AddCommand(initListCommand())
-	coreCommand.AddCommand(initUpdateIndexCommand())
-	coreCommand.AddCommand(initUpgradeCommand())
-	coreCommand.AddCommand(initUninstallCommand())
-	coreCommand.AddCommand(initSearchCommand())
+	coreCommand.AddCommand(initDownloadCommand(srv))
+	coreCommand.AddCommand(initInstallCommand(srv))
+	coreCommand.AddCommand(initListCommand(srv))
+	coreCommand.AddCommand(initUpdateIndexCommand(srv))
+	coreCommand.AddCommand(initUpgradeCommand(srv))
+	coreCommand.AddCommand(initUninstallCommand(srv))
+	coreCommand.AddCommand(initSearchCommand(srv))
 
 	return coreCommand
 }
diff --git a/internal/cli/core/download.go b/internal/cli/core/download.go
index daa87f04ef2..fb5fe3f3818 100644
--- a/internal/cli/core/download.go
+++ b/internal/cli/core/download.go
@@ -20,7 +20,7 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/arduino/arduino-cli/commands/core"
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/instance"
@@ -29,7 +29,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func initDownloadCommand() *cobra.Command {
+func initDownloadCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	downloadCommand := &cobra.Command{
 		Use:   fmt.Sprintf("download [%s:%s[@%s]]...", tr("PACKAGER"), tr("ARCH"), tr("VERSION")),
 		Short: tr("Downloads one or more cores and corresponding tool dependencies."),
@@ -38,20 +38,22 @@ func initDownloadCommand() *cobra.Command {
 			"  " + os.Args[0] + " core download arduino:samd       # " + tr("download the latest version of Arduino SAMD core.") + "\n" +
 			"  " + os.Args[0] + " core download arduino:samd@1.6.9 # " + tr("download a specific version (in this case 1.6.9)."),
 		Args: cobra.MinimumNArgs(1),
-		Run:  runDownloadCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			runDownloadCommand(cmd.Context(), srv, args)
+		},
 		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			return arguments.GetInstallableCores(), cobra.ShellCompDirectiveDefault
+			return arguments.GetInstallableCores(cmd.Context(), srv), cobra.ShellCompDirectiveDefault
 		},
 	}
 	return downloadCommand
 }
 
-func runDownloadCommand(cmd *cobra.Command, args []string) {
-	inst := instance.CreateAndInit()
+func runDownloadCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string) {
+	inst := instance.CreateAndInit(ctx, srv)
 
 	logrus.Info("Executing `arduino-cli core download`")
 
-	platformsRefs, err := arguments.ParseReferences(args)
+	platformsRefs, err := arguments.ParseReferences(ctx, srv, args)
 	if err != nil {
 		feedback.Fatal(tr("Invalid argument passed: %v", err), feedback.ErrBadArgument)
 	}
@@ -63,8 +65,8 @@ func runDownloadCommand(cmd *cobra.Command, args []string) {
 			Architecture:    platformRef.Architecture,
 			Version:         platformRef.Version,
 		}
-		_, err := core.PlatformDownload(context.Background(), platformDownloadreq, feedback.ProgressBar())
-		if err != nil {
+		stream := commands.PlatformDownloadStreamResponseToCallbackFunction(ctx, feedback.ProgressBar())
+		if err := srv.PlatformDownload(platformDownloadreq, stream); err != nil {
 			feedback.Fatal(tr("Error downloading %[1]s: %[2]v", args[i], err), feedback.ErrNetwork)
 		}
 	}
diff --git a/internal/cli/core/install.go b/internal/cli/core/install.go
index 8193a8b4344..988602fc2ac 100644
--- a/internal/cli/core/install.go
+++ b/internal/cli/core/install.go
@@ -20,7 +20,7 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/arduino/arduino-cli/commands/core"
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/instance"
@@ -29,7 +29,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func initInstallCommand() *cobra.Command {
+func initInstallCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var noOverwrite bool
 	var scriptFlags arguments.PrePostScriptsFlags
 	installCommand := &cobra.Command{
@@ -45,10 +45,10 @@ func initInstallCommand() *cobra.Command {
 			arguments.CheckFlagsConflicts(cmd, "run-post-install", "skip-post-install")
 		},
 		Run: func(cmd *cobra.Command, args []string) {
-			runInstallCommand(args, scriptFlags, noOverwrite)
+			runInstallCommand(cmd.Context(), srv, args, scriptFlags, noOverwrite)
 		},
 		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			return arguments.GetInstallableCores(), cobra.ShellCompDirectiveDefault
+			return arguments.GetInstallableCores(cmd.Context(), srv), cobra.ShellCompDirectiveDefault
 		},
 	}
 	scriptFlags.AddToCommand(installCommand)
@@ -56,11 +56,11 @@ func initInstallCommand() *cobra.Command {
 	return installCommand
 }
 
-func runInstallCommand(args []string, scriptFlags arguments.PrePostScriptsFlags, noOverwrite bool) {
-	inst := instance.CreateAndInit()
+func runInstallCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string, scriptFlags arguments.PrePostScriptsFlags, noOverwrite bool) {
 	logrus.Info("Executing `arduino-cli core install`")
+	inst := instance.CreateAndInit(ctx, srv)
 
-	platformsRefs, err := arguments.ParseReferences(args)
+	platformsRefs, err := arguments.ParseReferences(ctx, srv, args)
 	if err != nil {
 		feedback.Fatal(tr("Invalid argument passed: %v", err), feedback.ErrBadArgument)
 	}
@@ -75,8 +75,8 @@ func runInstallCommand(args []string, scriptFlags arguments.PrePostScriptsFlags,
 			NoOverwrite:      noOverwrite,
 			SkipPreUninstall: scriptFlags.DetectSkipPreUninstallValue(),
 		}
-		_, err := core.PlatformInstall(context.Background(), platformInstallRequest, feedback.ProgressBar(), feedback.TaskProgress())
-		if err != nil {
+		stream := commands.PlatformInstallStreamResponseToCallbackFunction(ctx, feedback.ProgressBar(), feedback.TaskProgress())
+		if err := srv.PlatformInstall(platformInstallRequest, stream); err != nil {
 			feedback.Fatal(tr("Error during install: %v", err), feedback.ErrGeneric)
 		}
 	}
diff --git a/internal/cli/core/list.go b/internal/cli/core/list.go
index a2434ab676f..3c2dd954818 100644
--- a/internal/cli/core/list.go
+++ b/internal/cli/core/list.go
@@ -16,9 +16,9 @@
 package core
 
 import (
+	"context"
 	"os"
 
-	"github.com/arduino/arduino-cli/commands/core"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/result"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/table"
@@ -28,7 +28,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func initListCommand() *cobra.Command {
+func initListCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var updatableOnly bool
 	var all bool
 	listCommand := &cobra.Command{
@@ -38,7 +38,7 @@ func initListCommand() *cobra.Command {
 		Example: "  " + os.Args[0] + " core list",
 		Args:    cobra.NoArgs,
 		Run: func(cmd *cobra.Command, args []string) {
-			runListCommand(args, all, updatableOnly)
+			runListCommand(cmd.Context(), srv, all, updatableOnly)
 		},
 	}
 	listCommand.Flags().BoolVar(&updatableOnly, "updatable", false, tr("List updatable platforms."))
@@ -46,21 +46,21 @@ func initListCommand() *cobra.Command {
 	return listCommand
 }
 
-func runListCommand(args []string, all bool, updatableOnly bool) {
-	inst := instance.CreateAndInit()
+func runListCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, all bool, updatableOnly bool) {
+	inst := instance.CreateAndInit(ctx, srv)
 	logrus.Info("Executing `arduino-cli core list`")
-	List(inst, all, updatableOnly)
+	List(ctx, srv, inst, all, updatableOnly)
 }
 
 // List gets and prints a list of installed platforms.
-func List(inst *rpc.Instance, all bool, updatableOnly bool) {
-	platforms := GetList(inst, all, updatableOnly)
+func List(ctx context.Context, srv rpc.ArduinoCoreServiceServer, inst *rpc.Instance, all bool, updatableOnly bool) {
+	platforms := GetList(ctx, srv, inst, all, updatableOnly)
 	feedback.PrintResult(newCoreListResult(platforms, updatableOnly))
 }
 
 // GetList returns a list of installed platforms.
-func GetList(inst *rpc.Instance, all bool, updatableOnly bool) []*rpc.PlatformSummary {
-	platforms, err := core.PlatformSearch(&rpc.PlatformSearchRequest{
+func GetList(ctx context.Context, srv rpc.ArduinoCoreServiceServer, inst *rpc.Instance, all bool, updatableOnly bool) []*rpc.PlatformSummary {
+	platforms, err := srv.PlatformSearch(ctx, &rpc.PlatformSearchRequest{
 		Instance:          inst,
 		ManuallyInstalled: true,
 	})
diff --git a/internal/cli/core/search.go b/internal/cli/core/search.go
index d24e486ade7..8a58cbf5a57 100644
--- a/internal/cli/core/search.go
+++ b/internal/cli/core/search.go
@@ -23,7 +23,6 @@ import (
 	"time"
 
 	"github.com/arduino/arduino-cli/commands"
-	"github.com/arduino/arduino-cli/commands/core"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/result"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/table"
@@ -33,7 +32,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func initSearchCommand() *cobra.Command {
+func initSearchCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var allVersions bool
 	searchCommand := &cobra.Command{
 		Use:     fmt.Sprintf("search <%s...>", tr("keywords")),
@@ -42,7 +41,7 @@ func initSearchCommand() *cobra.Command {
 		Example: "  " + os.Args[0] + " core search MKRZero -a -v",
 		Args:    cobra.ArbitraryArgs,
 		Run: func(cmd *cobra.Command, args []string) {
-			runSearchCommand(cmd, args, allVersions)
+			runSearchCommand(cmd.Context(), srv, args, allVersions)
 		},
 	}
 	searchCommand.Flags().BoolVarP(&allVersions, "all", "a", false, tr("Show all available core versions."))
@@ -53,20 +52,20 @@ func initSearchCommand() *cobra.Command {
 // indexUpdateInterval specifies the time threshold over which indexes are updated
 const indexUpdateInterval = 24 * time.Hour
 
-func runSearchCommand(cmd *cobra.Command, args []string, allVersions bool) {
-	inst := instance.CreateAndInit()
+func runSearchCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string, allVersions bool) {
+	inst := instance.CreateAndInit(ctx, srv)
 
-	res, err := commands.UpdateIndex(
-		context.Background(),
+	stream, res := commands.UpdateIndexStreamResponseToCallbackFunction(ctx, feedback.ProgressBar())
+	err := srv.UpdateIndex(
 		&rpc.UpdateIndexRequest{Instance: inst, UpdateIfOlderThanSecs: int64(indexUpdateInterval.Seconds())},
-		feedback.ProgressBar())
+		stream)
 	if err != nil {
 		feedback.FatalError(err, feedback.ErrGeneric)
 	}
-	for _, idxRes := range res.GetUpdatedIndexes() {
+	for _, idxRes := range res().GetUpdatedIndexes() {
 		if idxRes.GetStatus() == rpc.IndexUpdateReport_STATUS_UPDATED {
 			// At least one index has been updated, reinitialize the instance
-			instance.Init(inst)
+			instance.Init(ctx, srv, inst)
 			break
 		}
 	}
@@ -74,7 +73,7 @@ func runSearchCommand(cmd *cobra.Command, args []string, allVersions bool) {
 	arguments := strings.ToLower(strings.Join(args, " "))
 	logrus.Infof("Executing `arduino-cli core search` with args: '%s'", arguments)
 
-	resp, err := core.PlatformSearch(&rpc.PlatformSearchRequest{
+	resp, err := srv.PlatformSearch(ctx, &rpc.PlatformSearchRequest{
 		Instance:   inst,
 		SearchArgs: arguments,
 	})
diff --git a/internal/cli/core/uninstall.go b/internal/cli/core/uninstall.go
index 10992ef9bdf..a5df772935c 100644
--- a/internal/cli/core/uninstall.go
+++ b/internal/cli/core/uninstall.go
@@ -20,7 +20,7 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/arduino/arduino-cli/commands/core"
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/instance"
@@ -29,7 +29,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func initUninstallCommand() *cobra.Command {
+func initUninstallCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var preUninstallFlags arguments.PrePostScriptsFlags
 	uninstallCommand := &cobra.Command{
 		Use:     fmt.Sprintf("uninstall %s:%s ...", tr("PACKAGER"), tr("ARCH")),
@@ -38,21 +38,21 @@ func initUninstallCommand() *cobra.Command {
 		Example: "  " + os.Args[0] + " core uninstall arduino:samd\n",
 		Args:    cobra.MinimumNArgs(1),
 		Run: func(cmd *cobra.Command, args []string) {
-			runUninstallCommand(args, preUninstallFlags)
+			runUninstallCommand(cmd.Context(), srv, args, preUninstallFlags)
 		},
 		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			return arguments.GetUninstallableCores(), cobra.ShellCompDirectiveDefault
+			return arguments.GetUninstallableCores(cmd.Context(), srv), cobra.ShellCompDirectiveDefault
 		},
 	}
 	preUninstallFlags.AddToCommand(uninstallCommand)
 	return uninstallCommand
 }
 
-func runUninstallCommand(args []string, preUninstallFlags arguments.PrePostScriptsFlags) {
-	inst := instance.CreateAndInit()
+func runUninstallCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string, preUninstallFlags arguments.PrePostScriptsFlags) {
 	logrus.Info("Executing `arduino-cli core uninstall`")
+	inst := instance.CreateAndInit(ctx, srv)
 
-	platformsRefs, err := arguments.ParseReferences(args)
+	platformsRefs, err := arguments.ParseReferences(ctx, srv, args)
 	if err != nil {
 		feedback.Fatal(tr("Invalid argument passed: %v", err), feedback.ErrBadArgument)
 	}
@@ -63,13 +63,14 @@ func runUninstallCommand(args []string, preUninstallFlags arguments.PrePostScrip
 		}
 	}
 	for _, platformRef := range platformsRefs {
-		_, err := core.PlatformUninstall(context.Background(), &rpc.PlatformUninstallRequest{
+		req := &rpc.PlatformUninstallRequest{
 			Instance:         inst,
 			PlatformPackage:  platformRef.PackageName,
 			Architecture:     platformRef.Architecture,
 			SkipPreUninstall: preUninstallFlags.DetectSkipPreUninstallValue(),
-		}, feedback.NewTaskProgressCB())
-		if err != nil {
+		}
+		stream := commands.PlatformUninstallStreamResponseToCallbackFunction(ctx, feedback.NewTaskProgressCB())
+		if err := srv.PlatformUninstall(req, stream); err != nil {
 			feedback.Fatal(tr("Error during uninstall: %v", err), feedback.ErrGeneric)
 		}
 	}
diff --git a/internal/cli/core/update_index.go b/internal/cli/core/update_index.go
index 37bd8898a1d..c7f0129291b 100644
--- a/internal/cli/core/update_index.go
+++ b/internal/cli/core/update_index.go
@@ -28,33 +28,36 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func initUpdateIndexCommand() *cobra.Command {
+func initUpdateIndexCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	updateIndexCommand := &cobra.Command{
 		Use:     "update-index",
 		Short:   tr("Updates the index of cores."),
 		Long:    tr("Updates the index of cores to the latest version."),
 		Example: "  " + os.Args[0] + " core update-index",
 		Args:    cobra.NoArgs,
-		Run:     runUpdateIndexCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			runUpdateIndexCommand(cmd.Context(), srv)
+		},
 	}
 	return updateIndexCommand
 }
 
-func runUpdateIndexCommand(cmd *cobra.Command, args []string) {
-	inst := instance.CreateAndInit()
+func runUpdateIndexCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer) {
 	logrus.Info("Executing `arduino-cli core update-index`")
-	resp := UpdateIndex(inst)
+	inst := instance.CreateAndInit(ctx, srv)
+	resp := UpdateIndex(ctx, srv, inst)
 
 	feedback.PrintResult(&updateIndexResult{result.NewUpdateIndexResponse_ResultResult(resp)})
 }
 
 // UpdateIndex updates the index of platforms.
-func UpdateIndex(inst *rpc.Instance) *rpc.UpdateIndexResponse_Result {
-	res, err := commands.UpdateIndex(context.Background(), &rpc.UpdateIndexRequest{Instance: inst}, feedback.ProgressBar())
+func UpdateIndex(ctx context.Context, srv rpc.ArduinoCoreServiceServer, inst *rpc.Instance) *rpc.UpdateIndexResponse_Result {
+	stream, res := commands.UpdateIndexStreamResponseToCallbackFunction(ctx, feedback.ProgressBar())
+	err := srv.UpdateIndex(&rpc.UpdateIndexRequest{Instance: inst}, stream)
 	if err != nil {
 		feedback.FatalError(err, feedback.ErrGeneric)
 	}
-	return res
+	return res()
 }
 
 type updateIndexResult struct {
diff --git a/internal/cli/core/upgrade.go b/internal/cli/core/upgrade.go
index e98dd72b244..03185784536 100644
--- a/internal/cli/core/upgrade.go
+++ b/internal/cli/core/upgrade.go
@@ -21,8 +21,8 @@ import (
 	"fmt"
 	"os"
 
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/commands/cmderrors"
-	"github.com/arduino/arduino-cli/commands/core"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/instance"
@@ -31,7 +31,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func initUpgradeCommand() *cobra.Command {
+func initUpgradeCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var postInstallFlags arguments.PrePostScriptsFlags
 	upgradeCommand := &cobra.Command{
 		Use:   fmt.Sprintf("upgrade [%s:%s] ...", tr("PACKAGER"), tr("ARCH")),
@@ -43,24 +43,24 @@ func initUpgradeCommand() *cobra.Command {
 			"  # " + tr("upgrade arduino:samd to the latest version") + "\n" +
 			"  " + os.Args[0] + " core upgrade arduino:samd",
 		Run: func(cmd *cobra.Command, args []string) {
-			runUpgradeCommand(args, postInstallFlags.DetectSkipPostInstallValue(), postInstallFlags.DetectSkipPreUninstallValue())
+			runUpgradeCommand(cmd.Context(), srv, args, postInstallFlags.DetectSkipPostInstallValue(), postInstallFlags.DetectSkipPreUninstallValue())
 		},
 	}
 	postInstallFlags.AddToCommand(upgradeCommand)
 	return upgradeCommand
 }
 
-func runUpgradeCommand(args []string, skipPostInstall bool, skipPreUninstall bool) {
-	inst := instance.CreateAndInit()
+func runUpgradeCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string, skipPostInstall bool, skipPreUninstall bool) {
 	logrus.Info("Executing `arduino-cli core upgrade`")
-	Upgrade(inst, args, skipPostInstall, skipPreUninstall)
+	inst := instance.CreateAndInit(ctx, srv)
+	Upgrade(ctx, srv, inst, args, skipPostInstall, skipPreUninstall)
 }
 
 // Upgrade upgrades one or all installed platforms to the latest version.
-func Upgrade(inst *rpc.Instance, args []string, skipPostInstall bool, skipPreUninstall bool) {
+func Upgrade(ctx context.Context, srv rpc.ArduinoCoreServiceServer, inst *rpc.Instance, args []string, skipPostInstall bool, skipPreUninstall bool) {
 	// if no platform was passed, upgrade allthethings
 	if len(args) == 0 {
-		platforms, err := core.PlatformSearch(&rpc.PlatformSearchRequest{
+		platforms, err := srv.PlatformSearch(ctx, &rpc.PlatformSearchRequest{
 			Instance: inst,
 		})
 		if err != nil {
@@ -92,17 +92,17 @@ func Upgrade(inst *rpc.Instance, args []string, skipPostInstall bool, skipPreUni
 		}
 	}
 
-	warningMissingIndex := func(response *rpc.PlatformUpgradeResponse) {
-		if response == nil || response.GetPlatform() == nil {
+	warningMissingIndex := func(platform *rpc.Platform) {
+		if platform == nil {
 			return
 		}
-		if !response.GetPlatform().GetMetadata().GetIndexed() {
-			feedback.Warning(tr("missing package index for %s, future updates cannot be guaranteed", response.GetPlatform().GetMetadata().GetId()))
+		if !platform.GetMetadata().GetIndexed() {
+			feedback.Warning(tr("missing package index for %s, future updates cannot be guaranteed", platform.GetMetadata().GetId()))
 		}
 	}
 
 	// proceed upgrading, if anything is upgradable
-	platformsRefs, err := arguments.ParseReferences(args)
+	platformsRefs, err := arguments.ParseReferences(ctx, srv, args)
 	if err != nil {
 		feedback.Fatal(tr("Invalid argument passed: %v", err), feedback.ErrBadArgument)
 	}
@@ -122,8 +122,9 @@ func Upgrade(inst *rpc.Instance, args []string, skipPostInstall bool, skipPreUni
 			SkipPostInstall:  skipPostInstall,
 			SkipPreUninstall: skipPreUninstall,
 		}
-		response, err := core.PlatformUpgrade(context.Background(), r, feedback.ProgressBar(), feedback.TaskProgress())
-		warningMissingIndex(response)
+		stream, respCB := commands.PlatformUpgradeStreamResponseToCallbackFunction(ctx, feedback.ProgressBar(), feedback.TaskProgress())
+		err := srv.PlatformUpgrade(r, stream)
+		warningMissingIndex(respCB())
 		if err != nil {
 			var alreadyAtLatestVersionErr *cmderrors.PlatformAlreadyAtTheLatestVersionError
 			if errors.As(err, &alreadyAtLatestVersionErr) {
diff --git a/internal/cli/daemon/daemon.go b/internal/cli/daemon/daemon.go
index fcda04024f4..76c7e9a24ef 100644
--- a/internal/cli/daemon/daemon.go
+++ b/internal/cli/daemon/daemon.go
@@ -21,15 +21,13 @@ import (
 	"fmt"
 	"net"
 	"os"
+	"path/filepath"
 	"strings"
 	"syscall"
 
-	"github.com/arduino/arduino-cli/commands/daemon"
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/i18n"
-	srv_commands "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
-	"github.com/arduino/arduino-cli/version"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/arduino/go-paths-helper"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
@@ -45,31 +43,55 @@ var (
 )
 
 // NewCommand created a new `daemon` command
-func NewCommand() *cobra.Command {
+func NewCommand(srv rpc.ArduinoCoreServiceServer, settings *rpc.Configuration) *cobra.Command {
+	var daemonPort string
 	daemonCommand := &cobra.Command{
 		Use:     "daemon",
-		Short:   tr("Run as a daemon on port: %s", configuration.Settings.GetString("daemon.port")),
-		Long:    tr("Running as a daemon the initialization of cores and libraries is done only once."),
+		Short:   tr("Run the Arduino CLI as a gRPC daemon."),
 		Example: "  " + os.Args[0] + " daemon",
 		Args:    cobra.NoArgs,
-		Run:     runDaemonCommand,
+		PreRun: func(cmd *cobra.Command, args []string) {
+			// Bundled libraries support is enabled by default when running as a daemon
+			if settings.GetDirectories().GetBuiltin().GetLibraries() == "" {
+				defaultBuiltinLibDir := filepath.Join(settings.GetDirectories().GetData(), "libraries")
+				_, err := srv.SettingsSetValue(cmd.Context(), &rpc.SettingsSetValueRequest{
+					Key:          "directories.builtin.libraries",
+					ValueFormat:  "cli",
+					EncodedValue: defaultBuiltinLibDir,
+				})
+				if err != nil {
+					// Should never happen...
+					panic("Failed to set default value for directories.builtin.libraries: " + err.Error())
+				}
+			}
+		},
+		Run: func(cmd *cobra.Command, args []string) {
+			runDaemonCommand(srv, daemonPort)
+		},
 	}
-	daemonCommand.PersistentFlags().String("port", "", tr("The TCP port the daemon will listen to"))
-	configuration.Settings.BindPFlag("daemon.port", daemonCommand.PersistentFlags().Lookup("port"))
-	daemonCommand.Flags().BoolVar(&daemonize, "daemonize", false, tr("Do not terminate daemon process if the parent process dies"))
-	daemonCommand.Flags().BoolVar(&debug, "debug", false, tr("Enable debug logging of gRPC calls"))
-	daemonCommand.Flags().StringVar(&debugFile, "debug-file", "", tr("Append debug logging to the specified file"))
-	daemonCommand.Flags().StringSliceVar(&debugFilters, "debug-filter", []string{}, tr("Display only the provided gRPC calls"))
+	defaultDaemonPort := settings.GetDaemon().GetPort()
+
+	daemonCommand.Flags().StringVar(&daemonPort,
+		"port", defaultDaemonPort,
+		tr("The TCP port the daemon will listen to"))
+	daemonCommand.Flags().BoolVar(&daemonize,
+		"daemonize", false,
+		tr("Do not terminate daemon process if the parent process dies"))
+	daemonCommand.Flags().BoolVar(&debug,
+		"debug", false,
+		tr("Enable debug logging of gRPC calls"))
+	daemonCommand.Flags().StringVar(&debugFile,
+		"debug-file", "",
+		tr("Append debug logging to the specified file"))
+	daemonCommand.Flags().StringSliceVar(&debugFilters,
+		"debug-filter", []string{},
+		tr("Display only the provided gRPC calls"))
 	return daemonCommand
 }
 
-func runDaemonCommand(cmd *cobra.Command, args []string) {
+func runDaemonCommand(srv rpc.ArduinoCoreServiceServer, daemonPort string) {
 	logrus.Info("Executing `arduino-cli daemon`")
 
-	// Bundled libraries support is enabled by default when running as a daemon
-	configuration.Settings.SetDefault("directories.builtin.Libraries", configuration.GetDefaultBuiltinLibrariesDir())
-
-	port := configuration.Settings.GetString("daemon.port")
 	gRPCOptions := []grpc.ServerOption{}
 	if debugFile != "" {
 		if !debug {
@@ -98,44 +120,40 @@ func runDaemonCommand(cmd *cobra.Command, args []string) {
 		)
 	}
 	s := grpc.NewServer(gRPCOptions...)
-	// Set specific user-agent for the daemon
-	configuration.Settings.Set("network.user_agent_ext", "daemon")
 
 	// register the commands service
-	srv_commands.RegisterArduinoCoreServiceServer(s, &daemon.ArduinoCoreServerImpl{
-		VersionString: version.VersionInfo.VersionString,
-	})
+	rpc.RegisterArduinoCoreServiceServer(s, srv)
 
 	if !daemonize {
 		// When parent process ends terminate also the daemon
 		go feedback.ExitWhenParentProcessEnds()
 	}
 
-	ip := "127.0.0.1"
-	lis, err := net.Listen("tcp", fmt.Sprintf("%s:%s", ip, port))
+	daemonIP := "127.0.0.1"
+	lis, err := net.Listen("tcp", fmt.Sprintf("%s:%s", daemonIP, daemonPort))
 	if err != nil {
 		// Invalid port, such as "Foo"
 		var dnsError *net.DNSError
 		if errors.As(err, &dnsError) {
-			feedback.Fatal(tr("Failed to listen on TCP port: %[1]s. %[2]s is unknown name.", port, dnsError.Name), feedback.ErrBadTCPPortArgument)
+			feedback.Fatal(tr("Failed to listen on TCP port: %[1]s. %[2]s is unknown name.", daemonPort, dnsError.Name), feedback.ErrBadTCPPortArgument)
 		}
 		// Invalid port number, such as -1
 		var addrError *net.AddrError
 		if errors.As(err, &addrError) {
-			feedback.Fatal(tr("Failed to listen on TCP port: %[1]s. %[2]s is an invalid port.", port, addrError.Addr), feedback.ErrBadTCPPortArgument)
+			feedback.Fatal(tr("Failed to listen on TCP port: %[1]s. %[2]s is an invalid port.", daemonPort, addrError.Addr), feedback.ErrBadTCPPortArgument)
 		}
 		// Port is already in use
 		var syscallErr *os.SyscallError
 		if errors.As(err, &syscallErr) && errors.Is(syscallErr.Err, syscall.EADDRINUSE) {
-			feedback.Fatal(tr("Failed to listen on TCP port: %s. Address already in use.", port), feedback.ErrFailedToListenToTCPPort)
+			feedback.Fatal(tr("Failed to listen on TCP port: %s. Address already in use.", daemonPort), feedback.ErrFailedToListenToTCPPort)
 		}
-		feedback.Fatal(tr("Failed to listen on TCP port: %[1]s. Unexpected error: %[2]v", port, err), feedback.ErrFailedToListenToTCPPort)
+		feedback.Fatal(tr("Failed to listen on TCP port: %[1]s. Unexpected error: %[2]v", daemonPort, err), feedback.ErrFailedToListenToTCPPort)
 	}
 
 	// We need to retrieve the port used only if the user did not specify it
 	// and let the OS choose it randomly, in all other cases we already know
 	// which port is used.
-	if port == "0" {
+	if daemonPort == "0" {
 		address := lis.Addr()
 		split := strings.Split(address.String(), ":")
 
@@ -143,12 +161,12 @@ func runDaemonCommand(cmd *cobra.Command, args []string) {
 			feedback.Fatal(tr("Invalid TCP address: port is missing"), feedback.ErrBadTCPPortArgument)
 		}
 
-		port = split[1]
+		daemonPort = split[1]
 	}
 
 	feedback.PrintResult(daemonResult{
-		IP:   ip,
-		Port: port,
+		IP:   daemonIP,
+		Port: daemonPort,
 	})
 
 	if err := s.Serve(lis); err != nil {
diff --git a/internal/cli/debug/debug.go b/internal/cli/debug/debug.go
index 8ead18d8c1b..3da3c125371 100644
--- a/internal/cli/debug/debug.go
+++ b/internal/cli/debug/debug.go
@@ -22,9 +22,8 @@ import (
 	"os"
 	"os/signal"
 
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/commands/cmderrors"
-	"github.com/arduino/arduino-cli/commands/debug"
-	"github.com/arduino/arduino-cli/commands/sketch"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/table"
@@ -39,7 +38,7 @@ import (
 var tr = i18n.Tr
 
 // NewCommand created a new `upload` command
-func NewCommand() *cobra.Command {
+func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var (
 		fqbnArg     arguments.Fqbn
 		portArgs    arguments.Port
@@ -57,15 +56,15 @@ func NewCommand() *cobra.Command {
 		Example: "  " + os.Args[0] + " debug -b arduino:samd:mkr1000 -P atmel_ice /home/user/Arduino/MySketch",
 		Args:    cobra.MaximumNArgs(1),
 		Run: func(cmd *cobra.Command, args []string) {
-			runDebugCommand(args, &portArgs, &fqbnArg, interpreter, importDir, &programmer, printInfo, &profileArg)
+			runDebugCommand(cmd.Context(), srv, args, &portArgs, &fqbnArg, interpreter, importDir, &programmer, printInfo, &profileArg)
 		},
 	}
 
-	debugCommand.AddCommand(newDebugCheckCommand())
-	fqbnArg.AddToCommand(debugCommand)
-	portArgs.AddToCommand(debugCommand)
-	programmer.AddToCommand(debugCommand)
-	profileArg.AddToCommand(debugCommand)
+	debugCommand.AddCommand(newDebugCheckCommand(srv))
+	fqbnArg.AddToCommand(debugCommand, srv)
+	portArgs.AddToCommand(debugCommand, srv)
+	programmer.AddToCommand(debugCommand, srv)
+	profileArg.AddToCommand(debugCommand, srv)
 	debugCommand.Flags().StringVar(&interpreter, "interpreter", "console", tr("Debug interpreter e.g.: %s", "console, mi, mi1, mi2, mi3"))
 	debugCommand.Flags().StringVarP(&importDir, "input-dir", "", "", tr("Directory containing binaries for debug."))
 	debugCommand.Flags().BoolVarP(&printInfo, "info", "I", false, tr("Show metadata about the debug session instead of starting the debugger."))
@@ -73,7 +72,7 @@ func NewCommand() *cobra.Command {
 	return debugCommand
 }
 
-func runDebugCommand(args []string, portArgs *arguments.Port, fqbnArg *arguments.Fqbn,
+func runDebugCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string, portArgs *arguments.Port, fqbnArg *arguments.Fqbn,
 	interpreter string, importDir string, programmer *arguments.Programmer, printInfo bool, profileArg *arguments.Profile) {
 	logrus.Info("Executing `arduino-cli debug`")
 
@@ -83,30 +82,31 @@ func runDebugCommand(args []string, portArgs *arguments.Port, fqbnArg *arguments
 	}
 
 	sketchPath := arguments.InitSketchPath(path)
-	sk, err := sketch.LoadSketch(context.Background(), &rpc.LoadSketchRequest{SketchPath: sketchPath.String()})
+	resp, err := srv.LoadSketch(ctx, &rpc.LoadSketchRequest{SketchPath: sketchPath.String()})
 	if err != nil {
 		feedback.FatalError(err, feedback.ErrGeneric)
 	}
+	sk := resp.GetSketch()
 	feedback.WarnAboutDeprecatedFiles(sk)
 
 	var inst *rpc.Instance
 	var profile *rpc.SketchProfile
 
 	if profileArg.Get() == "" {
-		inst, profile = instance.CreateAndInitWithProfile(sk.GetDefaultProfile().GetName(), sketchPath)
+		inst, profile = instance.CreateAndInitWithProfile(ctx, srv, sk.GetDefaultProfile().GetName(), sketchPath)
 	} else {
-		inst, profile = instance.CreateAndInitWithProfile(profileArg.Get(), sketchPath)
+		inst, profile = instance.CreateAndInitWithProfile(ctx, srv, profileArg.Get(), sketchPath)
 	}
 
 	if fqbnArg.String() == "" {
 		fqbnArg.Set(profile.GetFqbn())
 	}
 
-	fqbn, port := arguments.CalculateFQBNAndPort(portArgs, fqbnArg, inst, sk.GetDefaultFqbn(), sk.GetDefaultPort(), sk.GetDefaultProtocol())
+	fqbn, port := arguments.CalculateFQBNAndPort(ctx, portArgs, fqbnArg, inst, srv, sk.GetDefaultFqbn(), sk.GetDefaultPort(), sk.GetDefaultProtocol())
 
 	prog := profile.GetProgrammer()
 	if prog == "" || programmer.GetProgrammer() != "" {
-		prog = programmer.String(inst, fqbn)
+		prog = programmer.String(ctx, inst, srv, fqbn)
 	}
 	if prog == "" {
 		prog = sk.GetDefaultProgrammer()
@@ -124,7 +124,7 @@ func runDebugCommand(args []string, portArgs *arguments.Port, fqbnArg *arguments
 
 	if printInfo {
 
-		if res, err := debug.GetDebugConfig(context.Background(), debugConfigRequested); err != nil {
+		if res, err := commands.GetDebugConfig(ctx, debugConfigRequested); err != nil {
 			errcode := feedback.ErrBadArgument
 			if errors.Is(err, &cmderrors.MissingProgrammerError{}) {
 				errcode = feedback.ErrMissingProgrammer
@@ -144,7 +144,7 @@ func runDebugCommand(args []string, portArgs *arguments.Port, fqbnArg *arguments
 		if err != nil {
 			feedback.FatalError(err, feedback.ErrBadArgument)
 		}
-		if _, err := debug.Debug(context.Background(), debugConfigRequested, in, out, ctrlc); err != nil {
+		if _, err := commands.Debug(ctx, debugConfigRequested, in, out, ctrlc); err != nil {
 			errcode := feedback.ErrGeneric
 			if errors.Is(err, &cmderrors.MissingProgrammerError{}) {
 				errcode = feedback.ErrMissingProgrammer
diff --git a/internal/cli/debug/debug_check.go b/internal/cli/debug/debug_check.go
index 52e25d40577..c16bb13cc25 100644
--- a/internal/cli/debug/debug_check.go
+++ b/internal/cli/debug/debug_check.go
@@ -19,7 +19,7 @@ import (
 	"context"
 	"os"
 
-	"github.com/arduino/arduino-cli/commands/debug"
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/result"
@@ -29,7 +29,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func newDebugCheckCommand() *cobra.Command {
+func newDebugCheckCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var (
 		fqbnArg     arguments.Fqbn
 		portArgs    arguments.Port
@@ -41,31 +41,31 @@ func newDebugCheckCommand() *cobra.Command {
 		Short:   tr("Check if the given board/programmer combination supports debugging."),
 		Example: "  " + os.Args[0] + " debug check -b arduino:samd:mkr1000 -P atmel_ice",
 		Run: func(cmd *cobra.Command, args []string) {
-			runDebugCheckCommand(&portArgs, &fqbnArg, interpreter, &programmer)
+			runDebugCheckCommand(cmd.Context(), srv, &portArgs, &fqbnArg, interpreter, &programmer)
 		},
 	}
-	fqbnArg.AddToCommand(debugCheckCommand)
-	portArgs.AddToCommand(debugCheckCommand)
-	programmer.AddToCommand(debugCheckCommand)
+	fqbnArg.AddToCommand(debugCheckCommand, srv)
+	portArgs.AddToCommand(debugCheckCommand, srv)
+	programmer.AddToCommand(debugCheckCommand, srv)
 	debugCheckCommand.Flags().StringVar(&interpreter, "interpreter", "console", tr("Debug interpreter e.g.: %s", "console, mi, mi1, mi2, mi3"))
 	return debugCheckCommand
 }
 
-func runDebugCheckCommand(portArgs *arguments.Port, fqbnArg *arguments.Fqbn, interpreter string, programmerArg *arguments.Programmer) {
-	instance := instance.CreateAndInit()
+func runDebugCheckCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, portArgs *arguments.Port, fqbnArg *arguments.Fqbn, interpreter string, programmerArg *arguments.Programmer) {
+	instance := instance.CreateAndInit(ctx, srv)
 	logrus.Info("Executing `arduino-cli debug`")
 
-	port, err := portArgs.GetPort(instance, "", "")
+	port, err := portArgs.GetPort(ctx, instance, srv, "", "")
 	if err != nil {
 		feedback.FatalError(err, feedback.ErrBadArgument)
 	}
 	fqbn := fqbnArg.String()
-	resp, err := debug.IsDebugSupported(context.Background(), &rpc.IsDebugSupportedRequest{
+	resp, err := commands.IsDebugSupported(ctx, &rpc.IsDebugSupportedRequest{
 		Instance:    instance,
 		Fqbn:        fqbn,
 		Port:        port,
 		Interpreter: interpreter,
-		Programmer:  programmerArg.String(instance, fqbn),
+		Programmer:  programmerArg.String(ctx, instance, srv, fqbn),
 	})
 	if err != nil {
 		feedback.FatalError(err, feedback.ErrGeneric)
diff --git a/internal/cli/instance/instance.go b/internal/cli/instance/instance.go
index ec1513105aa..28a19ad6e5a 100644
--- a/internal/cli/instance/instance.go
+++ b/internal/cli/instance/instance.go
@@ -16,6 +16,8 @@
 package instance
 
 import (
+	"context"
+
 	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/i18n"
@@ -29,26 +31,26 @@ var tr = i18n.Tr
 // If Create fails the CLI prints an error and exits since
 // to execute further operations a valid Instance is mandatory.
 // If Init returns errors they're printed only.
-func CreateAndInit() *rpc.Instance {
-	inst, _ := CreateAndInitWithProfile("", nil)
+func CreateAndInit(ctx context.Context, srv rpc.ArduinoCoreServiceServer) *rpc.Instance {
+	inst, _ := CreateAndInitWithProfile(ctx, srv, "", nil)
 	return inst
 }
 
 // CreateAndInitWithProfile returns a new initialized instance using the given profile of the given sketch.
 // If Create fails the CLI prints an error and exits since to execute further operations a valid Instance is mandatory.
 // If Init returns errors they're printed only.
-func CreateAndInitWithProfile(profileName string, sketchPath *paths.Path) (*rpc.Instance, *rpc.SketchProfile) {
-	instance, err := create()
+func CreateAndInitWithProfile(ctx context.Context, srv rpc.ArduinoCoreServiceServer, profileName string, sketchPath *paths.Path) (*rpc.Instance, *rpc.SketchProfile) {
+	instance, err := create(ctx, srv)
 	if err != nil {
 		feedback.Fatal(tr("Error creating instance: %v", err), feedback.ErrGeneric)
 	}
-	profile := InitWithProfile(instance, profileName, sketchPath)
+	profile := InitWithProfile(ctx, srv, instance, profileName, sketchPath)
 	return instance, profile
 }
 
 // create and return a new Instance.
-func create() (*rpc.Instance, error) {
-	res, err := commands.Create(&rpc.CreateRequest{})
+func create(ctx context.Context, srv rpc.ArduinoCoreServiceServer) (*rpc.Instance, error) {
+	res, err := srv.Create(ctx, &rpc.CreateRequest{})
 	if err != nil {
 		return nil, err
 	}
@@ -60,14 +62,14 @@ func create() (*rpc.Instance, error) {
 // platform or library that we failed to load.
 // Package and library indexes files are automatically updated if the
 // CLI is run for the first time.
-func Init(instance *rpc.Instance) {
-	InitWithProfile(instance, "", nil)
+func Init(ctx context.Context, srv rpc.ArduinoCoreServiceServer, instance *rpc.Instance) {
+	InitWithProfile(ctx, srv, instance, "", nil)
 }
 
 // InitWithProfile initializes instance by loading libraries and platforms specified in the given profile of the given sketch.
 // In case of loading failures return a list of errors for each platform or library that we failed to load.
 // Required Package and library indexes files are automatically downloaded.
-func InitWithProfile(instance *rpc.Instance, profileName string, sketchPath *paths.Path) *rpc.SketchProfile {
+func InitWithProfile(ctx context.Context, srv rpc.ArduinoCoreServiceServer, instance *rpc.Instance, profileName string, sketchPath *paths.Path) *rpc.SketchProfile {
 	downloadCallback := feedback.ProgressBar()
 	taskCallback := feedback.TaskProgress()
 
@@ -77,7 +79,7 @@ func InitWithProfile(instance *rpc.Instance, profileName string, sketchPath *pat
 		initReq.Profile = profileName
 	}
 	var profile *rpc.SketchProfile
-	err := commands.Init(initReq, func(res *rpc.InitResponse) {
+	err := srv.Init(initReq, commands.InitStreamResponseToCallbackFunction(ctx, func(res *rpc.InitResponse) error {
 		if st := res.GetError(); st != nil {
 			feedback.Warning(tr("Error initializing instance: %v", st.GetMessage()))
 		}
@@ -94,7 +96,8 @@ func InitWithProfile(instance *rpc.Instance, profileName string, sketchPath *pat
 		if p := res.GetProfile(); p != nil {
 			profile = p
 		}
-	})
+		return nil
+	}))
 	if err != nil {
 		feedback.Warning(tr("Error initializing instance: %v", err))
 	}
diff --git a/internal/cli/lib/args.go b/internal/cli/lib/args.go
index 75639b8bc84..3d3546a0c7e 100644
--- a/internal/cli/lib/args.go
+++ b/internal/cli/lib/args.go
@@ -20,7 +20,6 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/arduino/arduino-cli/commands/lib"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 )
 
@@ -74,9 +73,9 @@ func ParseLibraryReferenceArgs(args []string) ([]*LibraryReferenceArg, error) {
 
 // ParseLibraryReferenceArgAndAdjustCase parse a command line argument that reference a
 // library and possibly adjust the case of the name to match a library in the index
-func ParseLibraryReferenceArgAndAdjustCase(instance *rpc.Instance, arg string) (*LibraryReferenceArg, error) {
+func ParseLibraryReferenceArgAndAdjustCase(ctx context.Context, srv rpc.ArduinoCoreServiceServer, instance *rpc.Instance, arg string) (*LibraryReferenceArg, error) {
 	libRef, _ := ParseLibraryReferenceArg(arg)
-	res, err := lib.LibrarySearch(context.Background(), &rpc.LibrarySearchRequest{
+	res, err := srv.LibrarySearch(ctx, &rpc.LibrarySearchRequest{
 		Instance:   instance,
 		SearchArgs: libRef.Name,
 	})
@@ -98,10 +97,10 @@ func ParseLibraryReferenceArgAndAdjustCase(instance *rpc.Instance, arg string) (
 
 // ParseLibraryReferenceArgsAndAdjustCase is a convenient wrapper that operates on a slice of
 // strings and calls ParseLibraryReferenceArgAndAdjustCase for each of them. It returns at the first invalid argument.
-func ParseLibraryReferenceArgsAndAdjustCase(instance *rpc.Instance, args []string) ([]*LibraryReferenceArg, error) {
+func ParseLibraryReferenceArgsAndAdjustCase(ctx context.Context, srv rpc.ArduinoCoreServiceServer, instance *rpc.Instance, args []string) ([]*LibraryReferenceArg, error) {
 	ret := []*LibraryReferenceArg{}
 	for _, arg := range args {
-		if reference, err := ParseLibraryReferenceArgAndAdjustCase(instance, arg); err == nil {
+		if reference, err := ParseLibraryReferenceArgAndAdjustCase(ctx, srv, instance, arg); err == nil {
 			ret = append(ret, reference)
 		} else {
 			return nil, err
diff --git a/internal/cli/lib/check_deps.go b/internal/cli/lib/check_deps.go
index 557983298fb..8e18e800515 100644
--- a/internal/cli/lib/check_deps.go
+++ b/internal/cli/lib/check_deps.go
@@ -21,7 +21,6 @@ import (
 	"os"
 	"sort"
 
-	"github.com/arduino/arduino-cli/commands/lib"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/result"
@@ -32,7 +31,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func initDepsCommand() *cobra.Command {
+func initDepsCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var noOverwrite bool
 	depsCommand := &cobra.Command{
 		Use:   fmt.Sprintf("deps %s[@%s]...", tr("LIBRARY"), tr("VERSION_NUMBER")),
@@ -43,25 +42,26 @@ func initDepsCommand() *cobra.Command {
 			"  " + os.Args[0] + " lib deps AudioZero@1.0.0 # " + tr("for the specific version."),
 		Args: cobra.ExactArgs(1),
 		Run: func(cmd *cobra.Command, args []string) {
-			runDepsCommand(args, noOverwrite)
+			runDepsCommand(cmd.Context(), srv, args, noOverwrite)
 		},
 		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			return arguments.GetInstalledLibraries(), cobra.ShellCompDirectiveDefault
+			return arguments.GetInstalledLibraries(cmd.Context(), srv), cobra.ShellCompDirectiveDefault
 		},
 	}
 	depsCommand.Flags().BoolVar(&noOverwrite, "no-overwrite", false, tr("Do not try to update library dependencies if already installed."))
 	return depsCommand
 }
 
-func runDepsCommand(args []string, noOverwrite bool) {
-	instance := instance.CreateAndInit()
+func runDepsCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string, noOverwrite bool) {
+	instance := instance.CreateAndInit(ctx, srv)
+
 	logrus.Info("Executing `arduino-cli lib deps`")
-	libRef, err := ParseLibraryReferenceArgAndAdjustCase(instance, args[0])
+	libRef, err := ParseLibraryReferenceArgAndAdjustCase(ctx, srv, instance, args[0])
 	if err != nil {
 		feedback.Fatal(tr("Arguments error: %v", err), feedback.ErrBadArgument)
 	}
 
-	deps, err := lib.LibraryResolveDependencies(context.Background(), &rpc.LibraryResolveDependenciesRequest{
+	deps, err := srv.LibraryResolveDependencies(ctx, &rpc.LibraryResolveDependenciesRequest{
 		Instance:                      instance,
 		Name:                          libRef.Name,
 		Version:                       libRef.Version,
diff --git a/internal/cli/lib/download.go b/internal/cli/lib/download.go
index 3616584a91d..562f8b07bb9 100644
--- a/internal/cli/lib/download.go
+++ b/internal/cli/lib/download.go
@@ -20,7 +20,7 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/arduino/arduino-cli/commands/lib"
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/instance"
@@ -29,7 +29,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func initDownloadCommand() *cobra.Command {
+func initDownloadCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	downloadCommand := &cobra.Command{
 		Use:   fmt.Sprintf("download [%s]...", tr("LIBRARY_NAME")),
 		Short: tr("Downloads one or more libraries without installing them."),
@@ -38,18 +38,21 @@ func initDownloadCommand() *cobra.Command {
 			"  " + os.Args[0] + " lib download AudioZero       # " + tr("for the latest version.") + "\n" +
 			"  " + os.Args[0] + " lib download AudioZero@1.0.0 # " + tr("for a specific version."),
 		Args: cobra.MinimumNArgs(1),
-		Run:  runDownloadCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			runDownloadCommand(cmd.Context(), srv, args)
+		},
 		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			return arguments.GetInstallableLibs(), cobra.ShellCompDirectiveDefault
+			return arguments.GetInstallableLibs(cmd.Context(), srv), cobra.ShellCompDirectiveDefault
 		},
 	}
 	return downloadCommand
 }
 
-func runDownloadCommand(cmd *cobra.Command, args []string) {
-	instance := instance.CreateAndInit()
+func runDownloadCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string) {
 	logrus.Info("Executing `arduino-cli lib download`")
-	refs, err := ParseLibraryReferenceArgsAndAdjustCase(instance, args)
+	instance := instance.CreateAndInit(ctx, srv)
+
+	refs, err := ParseLibraryReferenceArgsAndAdjustCase(ctx, srv, instance, args)
 	if err != nil {
 		feedback.Fatal(tr("Invalid argument passed: %v", err), feedback.ErrBadArgument)
 	}
@@ -60,8 +63,8 @@ func runDownloadCommand(cmd *cobra.Command, args []string) {
 			Name:     library.Name,
 			Version:  library.Version,
 		}
-		_, err := lib.LibraryDownload(context.Background(), libraryDownloadRequest, feedback.ProgressBar())
-		if err != nil {
+		stream := commands.LibraryDownloadStreamResponseToCallbackFunction(ctx, feedback.ProgressBar())
+		if err := srv.LibraryDownload(libraryDownloadRequest, stream); err != nil {
 			feedback.Fatal(tr("Error downloading %[1]s: %[2]v", library, err), feedback.ErrNetwork)
 		}
 	}
diff --git a/internal/cli/lib/examples.go b/internal/cli/lib/examples.go
index 11fadebdbef..ff24e3616cb 100644
--- a/internal/cli/lib/examples.go
+++ b/internal/cli/lib/examples.go
@@ -22,7 +22,6 @@ import (
 	"sort"
 	"strings"
 
-	"github.com/arduino/arduino-cli/commands/lib"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/result"
@@ -38,32 +37,34 @@ var (
 	fqbn arguments.Fqbn
 )
 
-func initExamplesCommand() *cobra.Command {
+func initExamplesCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	examplesCommand := &cobra.Command{
 		Use:     fmt.Sprintf("examples [%s]", tr("LIBRARY_NAME")),
 		Short:   tr("Shows the list of the examples for libraries."),
 		Long:    tr("Shows the list of the examples for libraries. A name may be given as argument to search a specific library."),
 		Example: "  " + os.Args[0] + " lib examples Wire",
 		Args:    cobra.MaximumNArgs(1),
-		Run:     runExamplesCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			runExamplesCommand(cmd.Context(), srv, args)
+		},
 		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			return arguments.GetInstalledLibraries(), cobra.ShellCompDirectiveDefault
+			return arguments.GetInstalledLibraries(cmd.Context(), srv), cobra.ShellCompDirectiveDefault
 		},
 	}
-	fqbn.AddToCommand(examplesCommand)
+	fqbn.AddToCommand(examplesCommand, srv)
 	return examplesCommand
 }
 
-func runExamplesCommand(cmd *cobra.Command, args []string) {
-	instance := instance.CreateAndInit()
+func runExamplesCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string) {
 	logrus.Info("Executing `arduino-cli lib examples`")
+	instance := instance.CreateAndInit(ctx, srv)
 
 	name := ""
 	if len(args) > 0 {
 		name = args[0]
 	}
 
-	res, err := lib.LibraryList(context.Background(), &rpc.LibraryListRequest{
+	res, err := srv.LibraryList(ctx, &rpc.LibraryListRequest{
 		Instance: instance,
 		All:      true,
 		Name:     name,
diff --git a/internal/cli/lib/install.go b/internal/cli/lib/install.go
index 72a2a0592f9..fbde0cf8a6b 100644
--- a/internal/cli/lib/install.go
+++ b/internal/cli/lib/install.go
@@ -21,9 +21,8 @@ import (
 	"os"
 	"strings"
 
-	"github.com/arduino/arduino-cli/commands/lib"
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/instance"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
@@ -34,12 +33,13 @@ import (
 	semver "go.bug.st/relaxed-semver"
 )
 
-func initInstallCommand() *cobra.Command {
+func initInstallCommand(srv rpc.ArduinoCoreServiceServer, settings *rpc.Configuration) *cobra.Command {
 	var noDeps bool
 	var noOverwrite bool
 	var gitURL bool
 	var zipPath bool
 	var useBuiltinLibrariesDir bool
+	enableUnsafeInstall := settings.GetLibrary().GetEnableUnsafeInstall()
 	installCommand := &cobra.Command{
 		Use:   fmt.Sprintf("install %s[@%s]...", tr("LIBRARY"), tr("VERSION_NUMBER")),
 		Short: tr("Installs one or more specified libraries into the system."),
@@ -52,10 +52,10 @@ func initInstallCommand() *cobra.Command {
 			"  " + os.Args[0] + " lib install --zip-path /path/to/WiFi101.zip /path/to/ArduinoBLE.zip\n",
 		Args: cobra.MinimumNArgs(1),
 		Run: func(cmd *cobra.Command, args []string) {
-			runInstallCommand(args, noDeps, noOverwrite, gitURL, zipPath, useBuiltinLibrariesDir)
+			runInstallCommand(cmd.Context(), srv, args, noDeps, noOverwrite, gitURL, zipPath, useBuiltinLibrariesDir, enableUnsafeInstall)
 		},
 		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			return arguments.GetInstallableLibs(), cobra.ShellCompDirectiveDefault
+			return arguments.GetInstallableLibs(cmd.Context(), srv), cobra.ShellCompDirectiveDefault
 		},
 	}
 	installCommand.Flags().BoolVar(&noDeps, "no-deps", false, tr("Do not install dependencies."))
@@ -66,12 +66,12 @@ func initInstallCommand() *cobra.Command {
 	return installCommand
 }
 
-func runInstallCommand(args []string, noDeps bool, noOverwrite bool, gitURL bool, zipPath bool, useBuiltinLibrariesDir bool) {
-	instance := instance.CreateAndInit()
+func runInstallCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string, noDeps bool, noOverwrite bool, gitURL bool, zipPath bool, useBuiltinLibrariesDir bool, enableUnsafeInstall bool) {
+	instance := instance.CreateAndInit(ctx, srv)
 	logrus.Info("Executing `arduino-cli lib install`")
 
 	if zipPath || gitURL {
-		if !configuration.Settings.GetBool("library.enable_unsafe_install") {
+		if !enableUnsafeInstall {
 			documentationURL := "https://arduino.github.io/arduino-cli/latest/configuration/#configuration-keys"
 			_, err := semver.Parse(version.VersionInfo.VersionString)
 			if err == nil {
@@ -89,12 +89,13 @@ func runInstallCommand(args []string, noDeps bool, noOverwrite bool, gitURL bool
 
 	if zipPath {
 		for _, path := range args {
-			err := lib.ZipLibraryInstall(context.Background(), &rpc.ZipLibraryInstallRequest{
+			req := &rpc.ZipLibraryInstallRequest{
 				Instance:  instance,
 				Path:      path,
 				Overwrite: !noOverwrite,
-			}, feedback.TaskProgress())
-			if err != nil {
+			}
+			stream := commands.ZipLibraryInstallStreamResponseToCallbackFunction(ctx, feedback.TaskProgress())
+			if err := srv.ZipLibraryInstall(req, stream); err != nil {
 				feedback.Fatal(tr("Error installing Zip Library: %v", err), feedback.ErrGeneric)
 			}
 		}
@@ -110,19 +111,20 @@ func runInstallCommand(args []string, noDeps bool, noOverwrite bool, gitURL bool
 				}
 				url = wd.String()
 			}
-			err := lib.GitLibraryInstall(context.Background(), &rpc.GitLibraryInstallRequest{
+			req := &rpc.GitLibraryInstallRequest{
 				Instance:  instance,
 				Url:       url,
 				Overwrite: !noOverwrite,
-			}, feedback.TaskProgress())
-			if err != nil {
+			}
+			stream := commands.GitLibraryInstallStreamResponseToCallbackFunction(ctx, feedback.TaskProgress())
+			if err := srv.GitLibraryInstall(req, stream); err != nil {
 				feedback.Fatal(tr("Error installing Git Library: %v", err), feedback.ErrGeneric)
 			}
 		}
 		return
 	}
 
-	libRefs, err := ParseLibraryReferenceArgsAndAdjustCase(instance, args)
+	libRefs, err := ParseLibraryReferenceArgsAndAdjustCase(ctx, srv, instance, args)
 	if err != nil {
 		feedback.Fatal(tr("Arguments error: %v", err), feedback.ErrBadArgument)
 	}
@@ -140,8 +142,8 @@ func runInstallCommand(args []string, noDeps bool, noOverwrite bool, gitURL bool
 			NoOverwrite:     noOverwrite,
 			InstallLocation: installLocation,
 		}
-		err := lib.LibraryInstall(context.Background(), libraryInstallRequest, feedback.ProgressBar(), feedback.TaskProgress())
-		if err != nil {
+		stream := commands.LibraryInstallStreamResponseToCallbackFunction(ctx, feedback.ProgressBar(), feedback.TaskProgress())
+		if err := srv.LibraryInstall(libraryInstallRequest, stream); err != nil {
 			feedback.Fatal(tr("Error installing %s: %v", libRef.Name, err), feedback.ErrGeneric)
 		}
 	}
diff --git a/internal/cli/lib/lib.go b/internal/cli/lib/lib.go
index edba456bde9..5aa89cee8dd 100644
--- a/internal/cli/lib/lib.go
+++ b/internal/cli/lib/lib.go
@@ -19,13 +19,14 @@ import (
 	"os"
 
 	"github.com/arduino/arduino-cli/internal/i18n"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/spf13/cobra"
 )
 
 var tr = i18n.Tr
 
 // NewCommand created a new `lib` command
-func NewCommand() *cobra.Command {
+func NewCommand(srv rpc.ArduinoCoreServiceServer, defaultSettings *rpc.Configuration) *cobra.Command {
 	libCommand := &cobra.Command{
 		Use:   "lib",
 		Short: tr("Arduino commands about libraries."),
@@ -35,14 +36,14 @@ func NewCommand() *cobra.Command {
 			"  " + os.Args[0] + " lib update-index",
 	}
 
-	libCommand.AddCommand(initDownloadCommand())
-	libCommand.AddCommand(initInstallCommand())
-	libCommand.AddCommand(initListCommand())
-	libCommand.AddCommand(initExamplesCommand())
-	libCommand.AddCommand(initSearchCommand())
-	libCommand.AddCommand(initUninstallCommand())
-	libCommand.AddCommand(initUpgradeCommand())
-	libCommand.AddCommand(initUpdateIndexCommand())
-	libCommand.AddCommand(initDepsCommand())
+	libCommand.AddCommand(initDownloadCommand(srv))
+	libCommand.AddCommand(initInstallCommand(srv, defaultSettings))
+	libCommand.AddCommand(initListCommand(srv))
+	libCommand.AddCommand(initExamplesCommand(srv))
+	libCommand.AddCommand(initSearchCommand(srv))
+	libCommand.AddCommand(initUninstallCommand(srv))
+	libCommand.AddCommand(initUpgradeCommand(srv))
+	libCommand.AddCommand(initUpdateIndexCommand(srv))
+	libCommand.AddCommand(initDepsCommand(srv))
 	return libCommand
 }
diff --git a/internal/cli/lib/list.go b/internal/cli/lib/list.go
index dca14c6f407..f764df4985c 100644
--- a/internal/cli/lib/list.go
+++ b/internal/cli/lib/list.go
@@ -22,7 +22,6 @@ import (
 	"sort"
 	"strings"
 
-	"github.com/arduino/arduino-cli/commands/lib"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/result"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/table"
@@ -32,7 +31,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func initListCommand() *cobra.Command {
+func initListCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var all bool
 	var updatable bool
 	listCommand := &cobra.Command{
@@ -46,20 +45,21 @@ not listed, they can be listed by adding the --all flag.`),
 		Example: "  " + os.Args[0] + " lib list",
 		Args:    cobra.MaximumNArgs(1),
 		Run: func(cmd *cobra.Command, args []string) {
-			instance := instance.CreateAndInit()
+			ctx := cmd.Context()
+			instance := instance.CreateAndInit(ctx, srv)
 			logrus.Info("Executing `arduino-cli lib list`")
-			List(instance, args, all, updatable)
+			List(ctx, srv, instance, args, all, updatable)
 		},
 	}
 	listCommand.Flags().BoolVar(&all, "all", false, tr("Include built-in libraries (from platforms and IDE) in listing."))
-	fqbn.AddToCommand(listCommand)
+	fqbn.AddToCommand(listCommand, srv)
 	listCommand.Flags().BoolVar(&updatable, "updatable", false, tr("List updatable libraries."))
 	return listCommand
 }
 
 // List gets and prints a list of installed libraries.
-func List(instance *rpc.Instance, args []string, all bool, updatable bool) {
-	installedLibs := GetList(instance, args, all, updatable)
+func List(ctx context.Context, srv rpc.ArduinoCoreServiceServer, instance *rpc.Instance, args []string, all bool, updatable bool) {
+	installedLibs := GetList(ctx, srv, instance, args, all, updatable)
 
 	installedLibsResult := make([]*result.InstalledLibrary, len(installedLibs))
 	for i, v := range installedLibs {
@@ -73,18 +73,13 @@ func List(instance *rpc.Instance, args []string, all bool, updatable bool) {
 }
 
 // GetList returns a list of installed libraries.
-func GetList(
-	instance *rpc.Instance,
-	args []string,
-	all bool,
-	updatable bool,
-) []*rpc.InstalledLibrary {
+func GetList(ctx context.Context, srv rpc.ArduinoCoreServiceServer, instance *rpc.Instance, args []string, all bool, updatable bool) []*rpc.InstalledLibrary {
 	name := ""
 	if len(args) > 0 {
 		name = args[0]
 	}
 
-	res, err := lib.LibraryList(context.Background(), &rpc.LibraryListRequest{
+	res, err := srv.LibraryList(ctx, &rpc.LibraryListRequest{
 		Instance:  instance,
 		All:       all,
 		Updatable: updatable,
diff --git a/internal/cli/lib/search.go b/internal/cli/lib/search.go
index 913344f582d..94780be95c4 100644
--- a/internal/cli/lib/search.go
+++ b/internal/cli/lib/search.go
@@ -23,7 +23,6 @@ import (
 	"time"
 
 	"github.com/arduino/arduino-cli/commands"
-	"github.com/arduino/arduino-cli/commands/lib"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/result"
 	"github.com/arduino/arduino-cli/internal/cli/instance"
@@ -32,7 +31,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func initSearchCommand() *cobra.Command {
+func initSearchCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var namesOnly bool
 	var omitReleasesDetails bool
 	searchCommand := &cobra.Command{
@@ -91,7 +90,7 @@ In addition to the fields listed above, QV terms can use these qualifiers:
 			"  " + os.Args[0] + " lib search dependencies=IRremote               # " + tr("libraries that depend only on \"IRremote\"") + "\n",
 		Args: cobra.ArbitraryArgs,
 		Run: func(cmd *cobra.Command, args []string) {
-			runSearchCommand(args, namesOnly, omitReleasesDetails)
+			runSearchCommand(cmd.Context(), srv, args, namesOnly, omitReleasesDetails)
 		},
 	}
 	searchCommand.Flags().BoolVar(&namesOnly, "names", false, tr("Show library names only."))
@@ -102,24 +101,22 @@ In addition to the fields listed above, QV terms can use these qualifiers:
 // indexUpdateInterval specifies the time threshold over which indexes are updated
 const indexUpdateInterval = 60 * time.Minute
 
-func runSearchCommand(args []string, namesOnly bool, omitReleasesDetails bool) {
-	inst := instance.CreateAndInit()
+func runSearchCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string, namesOnly bool, omitReleasesDetails bool) {
+	inst := instance.CreateAndInit(ctx, srv)
 
 	logrus.Info("Executing `arduino-cli lib search`")
 
-	res, err := commands.UpdateLibrariesIndex(
-		context.Background(),
-		&rpc.UpdateLibrariesIndexRequest{Instance: inst, UpdateIfOlderThanSecs: int64(indexUpdateInterval.Seconds())},
-		feedback.ProgressBar(),
-	)
-	if err != nil {
+	stream, res := commands.UpdateLibrariesIndexStreamResponseToCallbackFunction(ctx, feedback.ProgressBar())
+	req := &rpc.UpdateLibrariesIndexRequest{Instance: inst, UpdateIfOlderThanSecs: int64(indexUpdateInterval.Seconds())}
+	if err := srv.UpdateLibrariesIndex(req, stream); err != nil {
 		feedback.Fatal(tr("Error updating library index: %v", err), feedback.ErrGeneric)
 	}
-	if res.GetLibrariesIndex().GetStatus() == rpc.IndexUpdateReport_STATUS_UPDATED {
-		instance.Init(inst)
+	if res().GetLibrariesIndex().GetStatus() == rpc.IndexUpdateReport_STATUS_UPDATED {
+		instance.Init(ctx, srv, inst)
 	}
 
-	searchResp, err := lib.LibrarySearch(context.Background(), &rpc.LibrarySearchRequest{
+	// Perform library search
+	searchResp, err := srv.LibrarySearch(ctx, &rpc.LibrarySearchRequest{
 		Instance:            inst,
 		SearchArgs:          strings.Join(args, " "),
 		OmitReleasesDetails: omitReleasesDetails,
diff --git a/internal/cli/lib/uninstall.go b/internal/cli/lib/uninstall.go
index 897b0203320..07ca7248db2 100644
--- a/internal/cli/lib/uninstall.go
+++ b/internal/cli/lib/uninstall.go
@@ -20,7 +20,7 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/arduino/arduino-cli/commands/lib"
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/instance"
@@ -29,37 +29,40 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func initUninstallCommand() *cobra.Command {
+func initUninstallCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	uninstallCommand := &cobra.Command{
 		Use:     fmt.Sprintf("uninstall %s...", tr("LIBRARY_NAME")),
 		Short:   tr("Uninstalls one or more libraries."),
 		Long:    tr("Uninstalls one or more libraries."),
 		Example: "  " + os.Args[0] + " lib uninstall AudioZero",
 		Args:    cobra.MinimumNArgs(1),
-		Run:     runUninstallCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			runUninstallCommand(cmd.Context(), srv, args)
+		},
 		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-			return arguments.GetUninstallableLibraries(), cobra.ShellCompDirectiveDefault
+			return arguments.GetUninstallableLibraries(cmd.Context(), srv), cobra.ShellCompDirectiveDefault
 		},
 	}
 	return uninstallCommand
 }
 
-func runUninstallCommand(cmd *cobra.Command, args []string) {
-	instance := instance.CreateAndInit()
+func runUninstallCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string) {
 	logrus.Info("Executing `arduino-cli lib uninstall`")
+	instance := instance.CreateAndInit(ctx, srv)
 
-	refs, err := ParseLibraryReferenceArgsAndAdjustCase(instance, args)
+	refs, err := ParseLibraryReferenceArgsAndAdjustCase(ctx, srv, instance, args)
 	if err != nil {
 		feedback.Fatal(tr("Invalid argument passed: %v", err), feedback.ErrBadArgument)
 	}
 
 	for _, library := range refs {
-		err := lib.LibraryUninstall(context.Background(), &rpc.LibraryUninstallRequest{
+		req := &rpc.LibraryUninstallRequest{
 			Instance: instance,
 			Name:     library.Name,
 			Version:  library.Version,
-		}, feedback.TaskProgress())
-		if err != nil {
+		}
+		stream := commands.LibraryUninstallStreamResponseToCallbackFunction(ctx, feedback.TaskProgress())
+		if err := srv.LibraryUninstall(req, stream); err != nil {
 			feedback.Fatal(tr("Error uninstalling %[1]s: %[2]v", library, err), feedback.ErrGeneric)
 		}
 	}
diff --git a/internal/cli/lib/update_index.go b/internal/cli/lib/update_index.go
index 462ba003240..a9c93240b10 100644
--- a/internal/cli/lib/update_index.go
+++ b/internal/cli/lib/update_index.go
@@ -28,34 +28,36 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func initUpdateIndexCommand() *cobra.Command {
+func initUpdateIndexCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	updateIndexCommand := &cobra.Command{
 		Use:     "update-index",
 		Short:   tr("Updates the libraries index."),
 		Long:    tr("Updates the libraries index to the latest version."),
 		Example: "  " + os.Args[0] + " lib update-index",
 		Args:    cobra.NoArgs,
-		Run:     runUpdateIndexCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			runUpdateIndexCommand(cmd.Context(), srv)
+		},
 	}
 	return updateIndexCommand
 }
 
-func runUpdateIndexCommand(cmd *cobra.Command, args []string) {
-	inst := instance.CreateAndInit()
+func runUpdateIndexCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer) {
+	inst := instance.CreateAndInit(ctx, srv)
+
 	logrus.Info("Executing `arduino-cli lib update-index`")
-	resp := UpdateIndex(inst)
+	resp := UpdateIndex(ctx, srv, inst)
 	feedback.PrintResult(&libUpdateIndexResult{result.NewUpdateLibrariesIndexResponse_ResultResult(resp)})
 }
 
 // UpdateIndex updates the index of libraries.
-func UpdateIndex(inst *rpc.Instance) *rpc.UpdateLibrariesIndexResponse_Result {
-	resp, err := commands.UpdateLibrariesIndex(context.Background(), &rpc.UpdateLibrariesIndexRequest{
-		Instance: inst,
-	}, feedback.ProgressBar())
-	if err != nil {
+func UpdateIndex(ctx context.Context, srv rpc.ArduinoCoreServiceServer, inst *rpc.Instance) *rpc.UpdateLibrariesIndexResponse_Result {
+	req := &rpc.UpdateLibrariesIndexRequest{Instance: inst}
+	stream, resp := commands.UpdateLibrariesIndexStreamResponseToCallbackFunction(ctx, feedback.ProgressBar())
+	if err := srv.UpdateLibrariesIndex(req, stream); err != nil {
 		feedback.Fatal(tr("Error updating library index: %v", err), feedback.ErrGeneric)
 	}
-	return resp
+	return resp()
 }
 
 type libUpdateIndexResult struct {
diff --git a/internal/cli/lib/upgrade.go b/internal/cli/lib/upgrade.go
index 5375e5d6795..bb0da0caaea 100644
--- a/internal/cli/lib/upgrade.go
+++ b/internal/cli/lib/upgrade.go
@@ -20,7 +20,7 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/arduino/arduino-cli/commands/lib"
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/instance"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
@@ -28,7 +28,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func initUpgradeCommand() *cobra.Command {
+func initUpgradeCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	upgradeCommand := &cobra.Command{
 		Use:   "upgrade",
 		Short: tr("Upgrades installed libraries."),
@@ -37,31 +37,34 @@ func initUpgradeCommand() *cobra.Command {
 			"  " + os.Args[0] + " lib upgrade Audio\n" +
 			"  " + os.Args[0] + " lib upgrade Audio ArduinoJson",
 		Args: cobra.ArbitraryArgs,
-		Run:  runUpgradeCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			runUpgradeCommand(cmd.Context(), srv, args)
+		},
 	}
 	return upgradeCommand
 }
 
-func runUpgradeCommand(cmd *cobra.Command, args []string) {
-	instance := instance.CreateAndInit()
+func runUpgradeCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string) {
 	logrus.Info("Executing `arduino-cli lib upgrade`")
-	Upgrade(instance, args)
+	instance := instance.CreateAndInit(ctx, srv)
+	Upgrade(ctx, srv, instance, args)
 }
 
 // Upgrade upgrades the specified libraries
-func Upgrade(instance *rpc.Instance, libraries []string) {
+func Upgrade(ctx context.Context, srv rpc.ArduinoCoreServiceServer, instance *rpc.Instance, libraries []string) {
 	var upgradeErr error
 	if len(libraries) == 0 {
 		req := &rpc.LibraryUpgradeAllRequest{Instance: instance}
-		upgradeErr = lib.LibraryUpgradeAll(req, feedback.ProgressBar(), feedback.TaskProgress())
+		stream := commands.LibraryUpgradeAllStreamResponseToCallbackFunction(ctx, feedback.ProgressBar(), feedback.TaskProgress())
+		upgradeErr = srv.LibraryUpgradeAll(req, stream)
 	} else {
 		for _, libName := range libraries {
 			req := &rpc.LibraryUpgradeRequest{
 				Instance: instance,
 				Name:     libName,
 			}
-			upgradeErr = lib.LibraryUpgrade(context.Background(), req, feedback.ProgressBar(), feedback.TaskProgress())
-			if upgradeErr != nil {
+			stream := commands.LibraryUpgradeStreamResponseToCallbackFunction(ctx, feedback.ProgressBar(), feedback.TaskProgress())
+			if upgradeErr = srv.LibraryUpgrade(req, stream); upgradeErr != nil {
 				break
 			}
 		}
diff --git a/internal/cli/monitor/monitor.go b/internal/cli/monitor/monitor.go
index aacf38bd9b3..eb237521445 100644
--- a/internal/cli/monitor/monitor.go
+++ b/internal/cli/monitor/monitor.go
@@ -26,8 +26,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/arduino/arduino-cli/commands/monitor"
-	sk "github.com/arduino/arduino-cli/commands/sketch"
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/result"
@@ -44,7 +43,7 @@ import (
 var tr = i18n.Tr
 
 // NewCommand created a new `monitor` command
-func NewCommand() *cobra.Command {
+func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var (
 		portArgs   arguments.Port
 		fqbnArg    arguments.Fqbn
@@ -67,21 +66,22 @@ func NewCommand() *cobra.Command {
 			if len(args) > 0 {
 				sketchPath = args[0]
 			}
-			runMonitorCmd(&portArgs, &fqbnArg, &profileArg, sketchPath, configs, describe, timestamp, quiet, raw)
+			runMonitorCmd(cmd.Context(), srv, &portArgs, &fqbnArg, &profileArg, sketchPath, configs, describe, timestamp, quiet, raw)
 		},
 	}
-	portArgs.AddToCommand(monitorCommand)
-	profileArg.AddToCommand(monitorCommand)
+	portArgs.AddToCommand(monitorCommand, srv)
+	profileArg.AddToCommand(monitorCommand, srv)
 	monitorCommand.Flags().BoolVar(&raw, "raw", false, tr("Set terminal in raw mode (unbuffered)."))
 	monitorCommand.Flags().BoolVar(&describe, "describe", false, tr("Show all the settings of the communication port."))
 	monitorCommand.Flags().StringSliceVarP(&configs, "config", "c", []string{}, tr("Configure communication port settings. The format is <ID>=<value>[,<ID>=<value>]..."))
 	monitorCommand.Flags().BoolVarP(&quiet, "quiet", "q", false, tr("Run in silent mode, show only monitor input and output."))
 	monitorCommand.Flags().BoolVar(&timestamp, "timestamp", false, tr("Timestamp each incoming line."))
-	fqbnArg.AddToCommand(monitorCommand)
+	fqbnArg.AddToCommand(monitorCommand, srv)
 	return monitorCommand
 }
 
 func runMonitorCmd(
+	ctx context.Context, srv rpc.ArduinoCoreServiceServer,
 	portArgs *arguments.Port, fqbnArg *arguments.Fqbn, profileArg *arguments.Profile, sketchPathArg string,
 	configs []string, describe, timestamp, quiet, raw bool,
 ) {
@@ -104,25 +104,26 @@ func runMonitorCmd(
 	// If only --port is set we read the fqbn in the following order: default_fqbn -> discovery
 	// If only --fqbn is set we read the port in the following order: default_port
 	sketchPath := arguments.InitSketchPath(sketchPathArg)
-	sketch, err := sk.LoadSketch(context.Background(), &rpc.LoadSketchRequest{SketchPath: sketchPath.String()})
+	resp, err := srv.LoadSketch(ctx, &rpc.LoadSketchRequest{SketchPath: sketchPath.String()})
 	if err != nil && !portArgs.IsPortFlagSet() {
 		feedback.Fatal(
 			tr("Error getting default port from `sketch.yaml`. Check if you're in the correct sketch folder or provide the --port flag: %s", err),
 			feedback.ErrGeneric,
 		)
 	}
+	sketch := resp.GetSketch()
 	if sketch != nil {
 		defaultPort, defaultProtocol = sketch.GetDefaultPort(), sketch.GetDefaultProtocol()
 	}
 	if fqbnArg.String() == "" {
 		if profileArg.Get() == "" {
-			inst, profile = instance.CreateAndInitWithProfile(sketch.GetDefaultProfile().GetName(), sketchPath)
+			inst, profile = instance.CreateAndInitWithProfile(ctx, srv, sketch.GetDefaultProfile().GetName(), sketchPath)
 		} else {
-			inst, profile = instance.CreateAndInitWithProfile(profileArg.Get(), sketchPath)
+			inst, profile = instance.CreateAndInitWithProfile(ctx, srv, profileArg.Get(), sketchPath)
 		}
 	}
 	if inst == nil {
-		inst = instance.CreateAndInit()
+		inst = instance.CreateAndInit(ctx, srv)
 	}
 	// Priority on how to retrieve the fqbn
 	// 1. from flag
@@ -137,15 +138,15 @@ func runMonitorCmd(
 	case sketch.GetDefaultFqbn() != "":
 		fqbn = sketch.GetDefaultFqbn()
 	default:
-		fqbn, _ = portArgs.DetectFQBN(inst)
+		fqbn, _ = portArgs.DetectFQBN(ctx, inst, srv)
 	}
 
-	portAddress, portProtocol, err := portArgs.GetPortAddressAndProtocol(inst, defaultPort, defaultProtocol)
+	portAddress, portProtocol, err := portArgs.GetPortAddressAndProtocol(ctx, inst, srv, defaultPort, defaultProtocol)
 	if err != nil {
 		feedback.FatalError(err, feedback.ErrGeneric)
 	}
 
-	enumerateResp, err := monitor.EnumerateMonitorPortSettings(context.Background(), &rpc.EnumerateMonitorPortSettingsRequest{
+	enumerateResp, err := srv.EnumerateMonitorPortSettings(ctx, &rpc.EnumerateMonitorPortSettingsRequest{
 		Instance:     inst,
 		PortProtocol: portProtocol,
 		Fqbn:         fqbn,
@@ -203,20 +204,6 @@ func runMonitorCmd(
 			}
 		}
 	}
-	portProxy, _, err := monitor.Monitor(context.Background(), &rpc.MonitorPortOpenRequest{
-		Instance:          inst,
-		Port:              &rpc.Port{Address: portAddress, Protocol: portProtocol},
-		Fqbn:              fqbn,
-		PortConfiguration: configuration,
-	})
-	if err != nil {
-		feedback.FatalError(err, feedback.ErrGeneric)
-	}
-	defer portProxy.Close()
-
-	if !quiet {
-		feedback.Print(tr("Connected to %s! Press CTRL-C to exit.", portAddress))
-	}
 
 	ttyIn, ttyOut, err := feedback.InteractiveStreams()
 	if err != nil {
@@ -227,7 +214,7 @@ func runMonitorCmd(
 		ttyOut = newTimeStampWriter(ttyOut)
 	}
 
-	ctx, cancel := cleanup.InterruptableContext(context.Background())
+	ctx, cancel := cleanup.InterruptableContext(ctx)
 	if raw {
 		if feedback.IsInteractive() {
 			if err := feedback.SetRawModeStdin(); err != nil {
@@ -245,6 +232,22 @@ func runMonitorCmd(
 		ttyIn = io.TeeReader(ttyIn, ctrlCDetector)
 	}
 
+	monitorServer, portProxy := commands.MonitorServerToReadWriteCloser(ctx, &rpc.MonitorPortOpenRequest{
+		Instance:          inst,
+		Port:              &rpc.Port{Address: portAddress, Protocol: portProtocol},
+		Fqbn:              fqbn,
+		PortConfiguration: configuration,
+	})
+	go func() {
+		if !quiet {
+			feedback.Print(tr("Connecting to %s. Press CTRL-C to exit.", portAddress))
+		}
+		if err := srv.Monitor(monitorServer); err != nil {
+			feedback.FatalError(err, feedback.ErrGeneric)
+		}
+		portProxy.Close()
+		cancel()
+	}()
 	go func() {
 		_, err := io.Copy(ttyOut, portProxy)
 		if err != nil && !errors.Is(err, io.EOF) {
diff --git a/internal/cli/outdated/outdated.go b/internal/cli/outdated/outdated.go
index 4859f6159fc..7ca0a442e06 100644
--- a/internal/cli/outdated/outdated.go
+++ b/internal/cli/outdated/outdated.go
@@ -16,6 +16,7 @@
 package outdated
 
 import (
+	"context"
 	"fmt"
 	"os"
 	"sort"
@@ -36,7 +37,7 @@ import (
 var tr = i18n.Tr
 
 // NewCommand creates a new `outdated` command
-func NewCommand() *cobra.Command {
+func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	outdatedCommand := &cobra.Command{
 		Use:   "outdated",
 		Short: tr("Lists cores and libraries that can be upgraded"),
@@ -44,21 +45,25 @@ func NewCommand() *cobra.Command {
 that can be upgraded. If nothing needs to be updated the output is empty.`),
 		Example: "  " + os.Args[0] + " outdated\n",
 		Args:    cobra.NoArgs,
-		Run:     runOutdatedCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			runOutdatedCommand(cmd.Context(), srv)
+		},
 	}
 	return outdatedCommand
 }
 
-func runOutdatedCommand(cmd *cobra.Command, args []string) {
-	inst := instance.CreateAndInit()
+func runOutdatedCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer) {
 	logrus.Info("Executing `arduino-cli outdated`")
-	Outdated(inst)
+	inst := instance.CreateAndInit(ctx, srv)
+	Outdated(ctx, srv, inst)
 }
 
 // Outdated prints a list of outdated platforms and libraries
-func Outdated(inst *rpc.Instance) {
+func Outdated(ctx context.Context, srv rpc.ArduinoCoreServiceServer, inst *rpc.Instance) {
 	feedback.PrintResult(
-		newOutdatedResult(core.GetList(inst, false, true), lib.GetList(inst, []string{}, false, true)),
+		newOutdatedResult(
+			core.GetList(ctx, srv, inst, false, true),
+			lib.GetList(ctx, srv, inst, []string{}, false, true)),
 	)
 }
 
diff --git a/internal/cli/sketch/archive.go b/internal/cli/sketch/archive.go
index fc0875be9fb..19b2a280651 100644
--- a/internal/cli/sketch/archive.go
+++ b/internal/cli/sketch/archive.go
@@ -20,7 +20,6 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/arduino/arduino-cli/commands/sketch"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
@@ -29,7 +28,7 @@ import (
 )
 
 // initArchiveCommand creates a new `archive` command
-func initArchiveCommand() *cobra.Command {
+func initArchiveCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var includeBuildDir, overwrite bool
 
 	archiveCommand := &cobra.Command{
@@ -43,7 +42,9 @@ func initArchiveCommand() *cobra.Command {
 			"  " + os.Args[0] + " archive /home/user/Arduino/MySketch\n" +
 			"  " + os.Args[0] + " archive /home/user/Arduino/MySketch /home/user/MySketchArchive.zip",
 		Args: cobra.MaximumNArgs(2),
-		Run:  func(cmd *cobra.Command, args []string) { runArchiveCommand(args, includeBuildDir, overwrite) },
+		Run: func(cmd *cobra.Command, args []string) {
+			runArchiveCommand(cmd.Context(), srv, args, includeBuildDir, overwrite)
+		},
 	}
 
 	archiveCommand.Flags().BoolVar(&includeBuildDir, "include-build-dir", false, tr("Includes %s directory in the archive.", "build"))
@@ -52,9 +53,8 @@ func initArchiveCommand() *cobra.Command {
 	return archiveCommand
 }
 
-func runArchiveCommand(args []string, includeBuildDir bool, overwrite bool) {
+func runArchiveCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string, includeBuildDir bool, overwrite bool) {
 	logrus.Info("Executing `arduino-cli sketch archive`")
-
 	sketchPathArg := ""
 	if len(args) > 0 {
 		sketchPathArg = args[0]
@@ -66,13 +66,14 @@ func runArchiveCommand(args []string, includeBuildDir bool, overwrite bool) {
 	}
 
 	sketchPath := arguments.InitSketchPath(sketchPathArg)
-	sk, err := sketch.LoadSketch(context.Background(), &rpc.LoadSketchRequest{SketchPath: sketchPath.String()})
+	resp, err := srv.LoadSketch(ctx, &rpc.LoadSketchRequest{SketchPath: sketchPath.String()})
 	if err != nil {
 		feedback.FatalError(err, feedback.ErrGeneric)
 	}
+	sk := resp.GetSketch()
 	feedback.WarnAboutDeprecatedFiles(sk)
 
-	if _, err := sketch.ArchiveSketch(context.Background(),
+	if _, err := srv.ArchiveSketch(ctx,
 		&rpc.ArchiveSketchRequest{
 			SketchPath:      sketchPath.String(),
 			ArchivePath:     archivePathArg,
diff --git a/internal/cli/sketch/new.go b/internal/cli/sketch/new.go
index 3514a05556b..79dfcccc373 100644
--- a/internal/cli/sketch/new.go
+++ b/internal/cli/sketch/new.go
@@ -20,7 +20,6 @@ import (
 	"os"
 	"strings"
 
-	sk "github.com/arduino/arduino-cli/commands/sketch"
 	"github.com/arduino/arduino-cli/internal/arduino/globals"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
@@ -29,7 +28,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func initNewCommand() *cobra.Command {
+func initNewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var overwrite bool
 
 	newCommand := &cobra.Command{
@@ -38,7 +37,9 @@ func initNewCommand() *cobra.Command {
 		Long:    tr("Create a new Sketch"),
 		Example: "  " + os.Args[0] + " sketch new MultiBlinker",
 		Args:    cobra.ExactArgs(1),
-		Run:     func(cmd *cobra.Command, args []string) { runNewCommand(args, overwrite) },
+		Run: func(cmd *cobra.Command, args []string) {
+			runNewCommand(cmd.Context(), srv, args, overwrite)
+		},
 	}
 
 	newCommand.Flags().BoolVarP(&overwrite, "overwrite", "f", false, tr("Overwrites an existing .ino sketch."))
@@ -46,7 +47,7 @@ func initNewCommand() *cobra.Command {
 	return newCommand
 }
 
-func runNewCommand(args []string, overwrite bool) {
+func runNewCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string, overwrite bool) {
 	logrus.Info("Executing `arduino-cli sketch new`")
 	// Trim to avoid issues if user creates a sketch adding the .ino extesion to the name
 	inputSketchName := args[0]
@@ -72,7 +73,7 @@ func runNewCommand(args []string, overwrite bool) {
 		sketchName = sketchDirPath.Base()
 	}
 
-	_, err = sk.NewSketch(context.Background(), &rpc.NewSketchRequest{
+	_, err = srv.NewSketch(ctx, &rpc.NewSketchRequest{
 		SketchName: sketchName,
 		SketchDir:  sketchDir,
 		Overwrite:  overwrite,
diff --git a/internal/cli/sketch/sketch.go b/internal/cli/sketch/sketch.go
index 5d8da390eed..2530c72247d 100644
--- a/internal/cli/sketch/sketch.go
+++ b/internal/cli/sketch/sketch.go
@@ -19,13 +19,14 @@ import (
 	"os"
 
 	"github.com/arduino/arduino-cli/internal/i18n"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/spf13/cobra"
 )
 
 var tr = i18n.Tr
 
 // NewCommand created a new `sketch` command
-func NewCommand() *cobra.Command {
+func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	sketchCommand := &cobra.Command{
 		Use:     "sketch",
 		Short:   tr("Arduino CLI sketch commands."),
@@ -33,8 +34,8 @@ func NewCommand() *cobra.Command {
 		Example: "  " + os.Args[0] + " sketch new MySketch",
 	}
 
-	sketchCommand.AddCommand(initNewCommand())
-	sketchCommand.AddCommand(initArchiveCommand())
+	sketchCommand.AddCommand(initNewCommand(srv))
+	sketchCommand.AddCommand(initArchiveCommand(srv))
 
 	return sketchCommand
 }
diff --git a/internal/cli/update/update.go b/internal/cli/update/update.go
index d516a5cc701..6fd4e7c91e0 100644
--- a/internal/cli/update/update.go
+++ b/internal/cli/update/update.go
@@ -16,6 +16,7 @@
 package update
 
 import (
+	"context"
 	"os"
 
 	"github.com/arduino/arduino-cli/internal/cli/core"
@@ -23,6 +24,7 @@ import (
 	"github.com/arduino/arduino-cli/internal/cli/lib"
 	"github.com/arduino/arduino-cli/internal/cli/outdated"
 	"github.com/arduino/arduino-cli/internal/i18n"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 )
@@ -30,7 +32,7 @@ import (
 var tr = i18n.Tr
 
 // NewCommand creates a new `update` command
-func NewCommand() *cobra.Command {
+func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var showOutdated bool
 	updateCommand := &cobra.Command{
 		Use:     "update",
@@ -39,20 +41,21 @@ func NewCommand() *cobra.Command {
 		Example: "  " + os.Args[0] + " update",
 		Args:    cobra.NoArgs,
 		Run: func(cmd *cobra.Command, args []string) {
-			runUpdateCommand(showOutdated)
+			runUpdateCommand(cmd.Context(), srv, showOutdated)
 		},
 	}
 	updateCommand.Flags().BoolVar(&showOutdated, "show-outdated", false, tr("Show outdated cores and libraries after index update"))
 	return updateCommand
 }
 
-func runUpdateCommand(showOutdated bool) {
-	inst := instance.CreateAndInit()
+func runUpdateCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, showOutdated bool) {
 	logrus.Info("Executing `arduino-cli update`")
-	lib.UpdateIndex(inst)
-	core.UpdateIndex(inst)
-	instance.Init(inst)
+	inst := instance.CreateAndInit(ctx, srv)
+
+	lib.UpdateIndex(ctx, srv, inst)
+	core.UpdateIndex(ctx, srv, inst)
+	instance.Init(ctx, srv, inst)
 	if showOutdated {
-		outdated.Outdated(inst)
+		outdated.Outdated(ctx, srv, inst)
 	}
 }
diff --git a/internal/cli/upgrade/upgrade.go b/internal/cli/upgrade/upgrade.go
index cf1f71448e7..4c311201dab 100644
--- a/internal/cli/upgrade/upgrade.go
+++ b/internal/cli/upgrade/upgrade.go
@@ -16,6 +16,7 @@
 package upgrade
 
 import (
+	"context"
 	"os"
 
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
@@ -23,6 +24,7 @@ import (
 	"github.com/arduino/arduino-cli/internal/cli/instance"
 	"github.com/arduino/arduino-cli/internal/cli/lib"
 	"github.com/arduino/arduino-cli/internal/i18n"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 )
@@ -30,7 +32,7 @@ import (
 var tr = i18n.Tr
 
 // NewCommand creates a new `upgrade` command
-func NewCommand() *cobra.Command {
+func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	var postInstallFlags arguments.PrePostScriptsFlags
 	upgradeCommand := &cobra.Command{
 		Use:     "upgrade",
@@ -39,16 +41,16 @@ func NewCommand() *cobra.Command {
 		Example: "  " + os.Args[0] + " upgrade",
 		Args:    cobra.NoArgs,
 		Run: func(cmd *cobra.Command, args []string) {
-			runUpgradeCommand(postInstallFlags.DetectSkipPostInstallValue(), postInstallFlags.DetectSkipPreUninstallValue())
+			runUpgradeCommand(cmd.Context(), srv, postInstallFlags.DetectSkipPostInstallValue(), postInstallFlags.DetectSkipPreUninstallValue())
 		},
 	}
 	postInstallFlags.AddToCommand(upgradeCommand)
 	return upgradeCommand
 }
 
-func runUpgradeCommand(skipPostInstall bool, skipPreUninstall bool) {
-	inst := instance.CreateAndInit()
+func runUpgradeCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, skipPostInstall bool, skipPreUninstall bool) {
+	inst := instance.CreateAndInit(ctx, srv)
 	logrus.Info("Executing `arduino-cli upgrade`")
-	lib.Upgrade(inst, []string{})
-	core.Upgrade(inst, []string{}, skipPostInstall, skipPreUninstall)
+	lib.Upgrade(ctx, srv, inst, []string{})
+	core.Upgrade(ctx, srv, inst, []string{}, skipPostInstall, skipPreUninstall)
 }
diff --git a/internal/cli/upload/upload.go b/internal/cli/upload/upload.go
index 46ec877fc8b..752a1a5ccc4 100644
--- a/internal/cli/upload/upload.go
+++ b/internal/cli/upload/upload.go
@@ -22,10 +22,8 @@ import (
 	"os"
 	"strings"
 
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/commands/cmderrors"
-	"github.com/arduino/arduino-cli/commands/core"
-	sk "github.com/arduino/arduino-cli/commands/sketch"
-	"github.com/arduino/arduino-cli/commands/upload"
 	"github.com/arduino/arduino-cli/internal/cli/arguments"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/feedback/result"
@@ -51,7 +49,7 @@ var (
 )
 
 // NewCommand created a new `upload` command
-func NewCommand() *cobra.Command {
+func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	uploadFields := map[string]string{}
 	uploadCommand := &cobra.Command{
 		Use:   "upload",
@@ -65,25 +63,25 @@ func NewCommand() *cobra.Command {
 			arguments.CheckFlagsConflicts(cmd, "input-file", "input-dir")
 		},
 		Run: func(cmd *cobra.Command, args []string) {
-			runUploadCommand(args, uploadFields)
+			runUploadCommand(cmd.Context(), srv, args, uploadFields)
 		},
 	}
 
-	fqbnArg.AddToCommand(uploadCommand)
-	portArgs.AddToCommand(uploadCommand)
-	profileArg.AddToCommand(uploadCommand)
+	fqbnArg.AddToCommand(uploadCommand, srv)
+	portArgs.AddToCommand(uploadCommand, srv)
+	profileArg.AddToCommand(uploadCommand, srv)
 	uploadCommand.Flags().StringVarP(&importDir, "input-dir", "", "", tr("Directory containing binaries to upload."))
 	uploadCommand.Flags().StringVarP(&importFile, "input-file", "i", "", tr("Binary file to upload."))
 	uploadCommand.Flags().BoolVarP(&verify, "verify", "t", false, tr("Verify uploaded binary after the upload."))
 	uploadCommand.Flags().BoolVarP(&verbose, "verbose", "v", false, tr("Optional, turns on verbose mode."))
-	programmer.AddToCommand(uploadCommand)
+	programmer.AddToCommand(uploadCommand, srv)
 	uploadCommand.Flags().BoolVar(&dryRun, "dry-run", false, tr("Do not perform the actual upload, just log out actions"))
 	uploadCommand.Flags().MarkHidden("dry-run")
 	arguments.AddKeyValuePFlag(uploadCommand, &uploadFields, "upload-field", "F", nil, tr("Set a value for a field required to upload."))
 	return uploadCommand
 }
 
-func runUploadCommand(args []string, uploadFieldsArgs map[string]string) {
+func runUploadCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer, args []string, uploadFieldsArgs map[string]string) {
 	logrus.Info("Executing `arduino-cli upload`")
 
 	path := ""
@@ -91,7 +89,8 @@ func runUploadCommand(args []string, uploadFieldsArgs map[string]string) {
 		path = args[0]
 	}
 	sketchPath := arguments.InitSketchPath(path)
-	sketch, err := sk.LoadSketch(context.Background(), &rpc.LoadSketchRequest{SketchPath: sketchPath.String()})
+	resp, err := srv.LoadSketch(ctx, &rpc.LoadSketchRequest{SketchPath: sketchPath.String()})
+	sketch := resp.GetSketch()
 	if importDir == "" && importFile == "" {
 		if err != nil {
 			feedback.Fatal(tr("Error during Upload: %v", err), feedback.ErrGeneric)
@@ -103,9 +102,9 @@ func runUploadCommand(args []string, uploadFieldsArgs map[string]string) {
 	var profile *rpc.SketchProfile
 
 	if profileArg.Get() == "" {
-		inst, profile = instance.CreateAndInitWithProfile(sketch.GetDefaultProfile().GetName(), sketchPath)
+		inst, profile = instance.CreateAndInitWithProfile(ctx, srv, sketch.GetDefaultProfile().GetName(), sketchPath)
 	} else {
-		inst, profile = instance.CreateAndInitWithProfile(profileArg.Get(), sketchPath)
+		inst, profile = instance.CreateAndInitWithProfile(ctx, srv, profileArg.Get(), sketchPath)
 	}
 
 	if fqbnArg.String() == "" {
@@ -115,9 +114,9 @@ func runUploadCommand(args []string, uploadFieldsArgs map[string]string) {
 	defaultFQBN := sketch.GetDefaultFqbn()
 	defaultAddress := sketch.GetDefaultPort()
 	defaultProtocol := sketch.GetDefaultProtocol()
-	fqbn, port := arguments.CalculateFQBNAndPort(&portArgs, &fqbnArg, inst, defaultFQBN, defaultAddress, defaultProtocol)
+	fqbn, port := arguments.CalculateFQBNAndPort(ctx, &portArgs, &fqbnArg, inst, srv, defaultFQBN, defaultAddress, defaultProtocol)
 
-	userFieldRes, err := upload.SupportedUserFields(context.Background(), &rpc.SupportedUserFieldsRequest{
+	userFieldRes, err := srv.SupportedUserFields(ctx, &rpc.SupportedUserFieldsRequest{
 		Instance: inst,
 		Fqbn:     fqbn,
 		Protocol: port.GetProtocol(),
@@ -135,7 +134,7 @@ func runUploadCommand(args []string, uploadFieldsArgs map[string]string) {
 			}
 
 			msg += "\n"
-			if platform, err := core.PlatformSearch(&rpc.PlatformSearchRequest{
+			if platform, err := srv.PlatformSearch(ctx, &rpc.PlatformSearchRequest{
 				Instance:   inst,
 				SearchArgs: platformErr.Platform,
 			}); err != nil {
@@ -178,7 +177,7 @@ func runUploadCommand(args []string, uploadFieldsArgs map[string]string) {
 
 	prog := profile.GetProgrammer()
 	if prog == "" || programmer.GetProgrammer() != "" {
-		prog = programmer.String(inst, fqbn)
+		prog = programmer.String(ctx, inst, srv, fqbn)
 	}
 	if prog == "" {
 		prog = sketch.GetDefaultProgrammer()
@@ -198,7 +197,8 @@ func runUploadCommand(args []string, uploadFieldsArgs map[string]string) {
 		DryRun:     dryRun,
 		UserFields: fields,
 	}
-	if res, err := upload.Upload(context.Background(), req, stdOut, stdErr); err != nil {
+	stream, streamResp := commands.UploadToServerStreams(ctx, stdOut, stdErr)
+	if err := srv.Upload(req, stream); err != nil {
 		errcode := feedback.ErrGeneric
 		if errors.Is(err, &cmderrors.ProgrammerRequiredForUploadError{}) {
 			errcode = feedback.ErrMissingProgrammer
@@ -212,7 +212,7 @@ func runUploadCommand(args []string, uploadFieldsArgs map[string]string) {
 		feedback.PrintResult(&uploadResult{
 			Stdout:            io.Stdout,
 			Stderr:            io.Stderr,
-			UpdatedUploadPort: result.NewPort(res.GetUpdatedUploadPort()),
+			UpdatedUploadPort: result.NewPort(streamResp().GetUpdatedUploadPort()),
 		})
 	}
 }
diff --git a/internal/cli/version/version.go b/internal/cli/version/version.go
index 0f0c85872e0..8c5d8eec4c1 100644
--- a/internal/cli/version/version.go
+++ b/internal/cli/version/version.go
@@ -20,7 +20,6 @@ import (
 	"os"
 	"strings"
 
-	"github.com/arduino/arduino-cli/commands/updatecheck"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/cli/updater"
 	"github.com/arduino/arduino-cli/internal/i18n"
@@ -33,19 +32,21 @@ import (
 var tr = i18n.Tr
 
 // NewCommand created a new `version` command
-func NewCommand() *cobra.Command {
+func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
 	versionCommand := &cobra.Command{
 		Use:     "version",
 		Short:   tr("Shows version number of Arduino CLI."),
 		Long:    tr("Shows the version number of Arduino CLI which is installed on your system."),
 		Example: "  " + os.Args[0] + " version",
 		Args:    cobra.NoArgs,
-		Run:     runVersionCommand,
+		Run: func(cmd *cobra.Command, args []string) {
+			runVersionCommand(cmd.Context(), srv)
+		},
 	}
 	return versionCommand
 }
 
-func runVersionCommand(cmd *cobra.Command, args []string) {
+func runVersionCommand(ctx context.Context, srv rpc.ArduinoCoreServiceServer) {
 	logrus.Info("Executing `arduino-cli version`")
 
 	info := version.VersionInfo
@@ -57,7 +58,7 @@ func runVersionCommand(cmd *cobra.Command, args []string) {
 	}
 
 	latestVersion := ""
-	res, err := updatecheck.CheckForArduinoCLIUpdates(context.Background(), &rpc.CheckForArduinoCLIUpdatesRequest{})
+	res, err := srv.CheckForArduinoCLIUpdates(ctx, &rpc.CheckForArduinoCLIUpdatesRequest{})
 	if err != nil {
 		feedback.Warning("Failed to check for updates: " + err.Error())
 	} else {
diff --git a/internal/docsgen/main.go b/internal/docsgen/main.go
index 3448c9f5f62..fa175fff583 100644
--- a/internal/docsgen/main.go
+++ b/internal/docsgen/main.go
@@ -18,8 +18,8 @@ package main
 import (
 	"os"
 
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/internal/cli"
-	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/spf13/cobra/doc"
 )
 
@@ -31,11 +31,10 @@ func main() {
 
 	os.MkdirAll(os.Args[1], 0755) // Create the output folder if it doesn't already exist
 
-	configuration.Settings = configuration.Init(configuration.FindConfigFileInArgsFallbackOnEnv(os.Args))
-	cli := cli.NewCommand()
+	srv := commands.NewArduinoCoreServer()
+	cli := cli.NewCommand(srv)
 	cli.DisableAutoGenTag = true // Disable addition of auto-generated date stamp
-	err := doc.GenMarkdownTree(cli, os.Args[1])
-	if err != nil {
+	if err := doc.GenMarkdownTree(cli, os.Args[1]); err != nil {
 		panic(err)
 	}
 }
diff --git a/internal/go-configmap/cli.go b/internal/go-configmap/cli.go
new file mode 100644
index 00000000000..d467cfc9c32
--- /dev/null
+++ b/internal/go-configmap/cli.go
@@ -0,0 +1,109 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package configmap
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+func (c *Map) SetFromCLIArgs(key string, args ...string) error {
+	if len(args) == 0 {
+		c.Delete(key)
+		return nil
+	}
+
+	// in case of schemaless configuration, we don't know the type of the setting
+	// we will save it as a string or array of strings
+	if len(c.schema) == 0 {
+		switch len(args) {
+		case 1:
+			c.Set(key, args[0])
+		default:
+			c.Set(key, args)
+		}
+		return nil
+	}
+
+	// Find the correct type for the given setting
+	valueType, ok := c.schema[key]
+	if !ok {
+		return fmt.Errorf("key not found: %s", key)
+	}
+
+	var value any
+	isArray := false
+	{
+		var conversionError error
+		switch valueType.String() {
+		case "uint":
+			value, conversionError = strconv.Atoi(args[0])
+		case "bool":
+			value, conversionError = strconv.ParseBool(args[0])
+		case "string":
+			value = args[0]
+		case "[]string":
+			value = args
+			isArray = true
+		default:
+			return fmt.Errorf("unhandled type: %s", valueType)
+		}
+		if conversionError != nil {
+			return fmt.Errorf("error setting value: %v", conversionError)
+		}
+	}
+	if !isArray && len(args) != 1 {
+		return fmt.Errorf("error setting value: key is not an array, but multiple values were provided")
+	}
+
+	return c.Set(key, value)
+}
+
+func (c *Map) InjectEnvVars(env []string, prefix string) []error {
+	if prefix != "" {
+		prefix = strings.ToUpper(prefix) + "_"
+	}
+
+	errs := []error{}
+
+	envKeyToConfigKey := map[string]string{}
+	for _, k := range c.AllKeys() {
+		normalizedKey := prefix + strings.ToUpper(k)
+		normalizedKey = strings.ReplaceAll(normalizedKey, ".", "_")
+		envKeyToConfigKey[normalizedKey] = k
+	}
+
+	for _, e := range env {
+		// Extract key and value from env
+		envKey, envValue, ok := strings.Cut(e, "=")
+		if !ok {
+			continue
+		}
+
+		// Check if the configuration has a matching key
+		key, ok := envKeyToConfigKey[strings.ToUpper(envKey)]
+		if !ok {
+			continue
+		}
+
+		// Update the configuration value
+		if err := c.SetFromCLIArgs(key, envValue); err != nil {
+			errs = append(errs, err)
+		}
+	}
+	return errs
+}
diff --git a/internal/go-configmap/configuration.go b/internal/go-configmap/configuration.go
new file mode 100644
index 00000000000..e1a9dbb791e
--- /dev/null
+++ b/internal/go-configmap/configuration.go
@@ -0,0 +1,219 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package configmap
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+)
+
+type Map struct {
+	values map[string]any
+	schema map[string]reflect.Type
+}
+
+func New() *Map {
+	return &Map{
+		values: make(map[string]any),
+		schema: make(map[string]reflect.Type),
+	}
+}
+
+func (c Map) Get(key string) any {
+	value, _ := c.GetOk(key)
+	return value
+}
+
+func (c Map) GetOk(key string) (any, bool) {
+	keys := strings.Split(key, ".")
+	return c.get(keys)
+}
+
+func (c Map) get(keys []string) (any, bool) {
+	if len(keys) == 0 {
+		return nil, false
+	}
+	value, ok := c.values[keys[0]]
+	if len(keys) == 1 {
+		return value, ok
+	}
+
+	if subConf, ok := value.(*Map); ok {
+		return subConf.get(keys[1:])
+	}
+	return nil, false
+}
+
+func (c Map) Set(key string, value any) error {
+	if len(c.schema) > 0 {
+		t, ok := c.schema[key]
+		if !ok {
+			return fmt.Errorf("schema not defined for key '%s'", key)
+		}
+		newValue, err := tryConversion(value, t)
+		if err != nil {
+			return fmt.Errorf("invalid type for key '%s': %w", key, err)
+		}
+		value = newValue
+	}
+	keys := strings.Split(key, ".")
+	c.set(keys, value)
+	return nil
+}
+
+func tryConversion(current any, desiredType reflect.Type) (any, error) {
+	currentType := reflect.TypeOf(current)
+	if currentType == desiredType {
+		return current, nil
+	}
+
+	switch desiredType.Kind() {
+	case reflect.Uint:
+		// Exception for JSON decoder: json decoder will decode all numbers as float64
+		if currentFloat, ok := current.(float64); ok {
+			return uint(currentFloat), nil
+		}
+		if currentInt, ok := current.(int); ok {
+			return uint(currentInt), nil
+		}
+	case reflect.Int:
+		// Exception for JSON decoder: json decoder will decode all numbers as float64
+		if currentFloat, ok := current.(float64); ok {
+			return int(currentFloat), nil
+		}
+	case reflect.Array, reflect.Slice:
+		currentArray, ok := current.([]any)
+		if !ok && current != nil {
+			break
+		}
+
+		resArray := reflect.MakeSlice(desiredType, len(currentArray), len(currentArray))
+		for i, elem := range currentArray {
+			newElem, err := tryConversion(elem, desiredType.Elem())
+			if err != nil {
+				return nil, err
+			}
+			resArray.Index(i).Set(reflect.ValueOf(newElem))
+		}
+		return resArray.Interface(), nil
+	}
+
+	currentTypeString := currentType.String()
+	if currentTypeString == "[]interface {}" {
+		currentTypeString = "array"
+	}
+	return nil, fmt.Errorf("invalid conversion, got %s but want %v", currentTypeString, desiredType)
+}
+
+func (c Map) set(keys []string, value any) {
+	if len(keys) == 0 {
+		return
+	}
+	if len(keys) == 1 {
+		c.values[keys[0]] = value
+		return
+	}
+
+	var subConf *Map
+	if subValue, ok := c.values[keys[0]]; !ok {
+		subConf = New()
+		c.values[keys[0]] = subConf
+	} else if conf, ok := subValue.(*Map); !ok {
+		subConf = New()
+		c.values[keys[0]] = subConf
+	} else {
+		subConf = conf
+	}
+	subConf.set(keys[1:], value)
+}
+
+func (c Map) Delete(key string) {
+	keys := strings.Split(key, ".")
+	c.delete(keys)
+}
+
+func (c Map) delete(keys []string) {
+	if len(keys) == 0 {
+		return
+	}
+	if len(keys) == 1 {
+		delete(c.values, keys[0])
+		return
+	}
+
+	if subValue, ok := c.values[keys[0]]; !ok {
+		return
+	} else if subConf, ok := subValue.(*Map); !ok {
+		return
+	} else {
+		subConf.delete(keys[1:])
+	}
+}
+
+func (c *Map) Merge(x *Map) error {
+	for xk, xv := range x.values {
+		if xSubConf, ok := xv.(*Map); ok {
+			if subConf, ok := c.values[xk].(*Map); ok {
+				if err := subConf.Merge(xSubConf); err != nil {
+					return err
+				}
+				continue
+			}
+			return fmt.Errorf("cannot merge sub-configuration into non sub-configuration: '%s'", xk)
+		}
+
+		v, ok := c.values[xk]
+		if !ok {
+			return fmt.Errorf("target key do not exist: '%s'", xk)
+		}
+		if reflect.TypeOf(v) != reflect.TypeOf(xv) {
+			return fmt.Errorf("invalid types for key '%s': got %T but want %T", xk, v, xv)
+		}
+		c.values[xk] = xv
+	}
+	return nil
+}
+
+func (c *Map) AllKeys() []string {
+	return c.allKeys("")
+}
+
+func (c *Map) Schema() map[string]reflect.Type {
+	return c.schema
+}
+
+func (c *Map) allKeys(prefix string) []string {
+	keys := []string{}
+	if len(c.schema) > 0 {
+		for k := range c.schema {
+			keys = append(keys, prefix+k)
+		}
+	} else {
+		for k, v := range c.values {
+			if subConf, ok := v.(*Map); ok {
+				keys = append(keys, subConf.allKeys(prefix+k+".")...)
+			} else {
+				keys = append(keys, prefix+k)
+			}
+		}
+	}
+	return keys
+}
+
+func (c *Map) SetKeyTypeSchema(key string, t any) {
+	c.schema[key] = reflect.TypeOf(t)
+}
diff --git a/internal/go-configmap/configuration_test.go b/internal/go-configmap/configuration_test.go
new file mode 100644
index 00000000000..c9c025cb3ce
--- /dev/null
+++ b/internal/go-configmap/configuration_test.go
@@ -0,0 +1,218 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package configmap_test
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/arduino/arduino-cli/internal/go-configmap"
+	"github.com/stretchr/testify/require"
+	"gopkg.in/yaml.v3"
+)
+
+func TestConfiguration(t *testing.T) {
+	c := configmap.New()
+	c.Set("foo", "bar")
+	c.Set("fooz.bar", "baz")
+	c.Set("answer", 42)
+	require.Equal(t, "bar", c.Get("foo"))
+	require.Equal(t, "baz", c.Get("fooz.bar"))
+	require.Equal(t, 42, c.Get("answer"))
+
+	yml, err := yaml.Marshal(c)
+	require.NoError(t, err)
+
+	d := configmap.New()
+	err = yaml.Unmarshal(yml, &d)
+	require.NoError(t, err)
+
+	yml2, err := yaml.Marshal(d)
+	require.NoError(t, err)
+	require.Equal(t, string(yml), string(yml2))
+
+	d.Set("fooz.abc", "def")
+	d.Set("fooz.cde", "fgh")
+	require.Equal(t, "def", d.Get("fooz.abc"))
+	require.Equal(t, "fgh", d.Get("fooz.cde"))
+	d.Delete("fooz.abc")
+	require.Nil(t, d.Get("fooz.abc"))
+	require.Equal(t, "fgh", d.Get("fooz.cde"))
+	d.Delete("fooz")
+	require.Nil(t, d.Get("fooz.cde"))
+}
+
+func TestYAMLCleanUpOfZeroValues(t *testing.T) {
+	inYml := []byte(`
+foo: bar
+directories:
+  builtins: {}
+`)
+	c := configmap.New()
+	outYml1, err := yaml.Marshal(c)
+	require.NoError(t, err)
+	require.Equal(t, "{}\n", string(outYml1))
+
+	err = yaml.Unmarshal(inYml, &c)
+	require.NoError(t, err)
+
+	outYml2, err := yaml.Marshal(c)
+	require.NoError(t, err)
+	require.Equal(t, "foo: bar\n", string(outYml2))
+}
+
+func TestApplyEnvVars(t *testing.T) {
+	c := configmap.New()
+	c.Set("foo", "bar")
+	c.Set("fooz.bar", "baz")
+	c.Set("answer", 42)
+	c.InjectEnvVars([]string{"APP_FOO=app-bar", "APP_FOOZ_BAR=app-baz"}, "APP")
+	require.Equal(t, "app-bar", c.Get("foo"))
+	require.Equal(t, "app-baz", c.Get("fooz.bar"))
+	require.Equal(t, 42, c.Get("answer"))
+}
+
+func TestMerge(t *testing.T) {
+	c := configmap.New()
+	c.Set("foo", "bar")
+	c.Set("fooz.bar", "baz")
+	c.Set("answer", 42)
+
+	d := configmap.New()
+	d.Set("answer", 24)
+	require.NoError(t, c.Merge(d))
+	require.Equal(t, "bar", c.Get("foo"))
+	require.Equal(t, "baz", c.Get("fooz.bar"))
+	require.Equal(t, 24, c.Get("answer"))
+
+	e := configmap.New()
+	e.Set("fooz.bar", "barz")
+	require.NoError(t, c.Merge(e))
+	require.Equal(t, "bar", c.Get("foo"))
+	require.Equal(t, "barz", c.Get("fooz.bar"))
+	require.Equal(t, 24, c.Get("answer"))
+
+	f := configmap.New()
+	f.Set("fooz.bar", 10)
+	require.EqualError(t, c.Merge(f), "invalid types for key 'bar': got string but want int")
+
+	g := configmap.New()
+	g.Set("fooz.bart", "baz")
+	require.EqualError(t, c.Merge(g), "target key do not exist: 'bart'")
+}
+
+func TestAllKeys(t *testing.T) {
+	{
+		c := configmap.New()
+		c.Set("foo", "bar")
+		c.Set("fooz.bar", "baz")
+		c.Set("answer", 42)
+		require.ElementsMatch(t, []string{"foo", "fooz.bar", "answer"}, c.AllKeys())
+	}
+	{
+		inYml := []byte(`
+foo: bar
+dir:
+  a: yes
+  b: no
+  c: {}
+  d:
+    - 1
+    - 2
+`)
+		c := configmap.New()
+		err := yaml.Unmarshal(inYml, &c)
+		require.NoError(t, err)
+		require.ElementsMatch(t, []string{"foo", "dir.a", "dir.b", "dir.d"}, c.AllKeys())
+	}
+}
+
+func TestSchema(t *testing.T) {
+	c := configmap.New()
+	c.SetKeyTypeSchema("string", "")
+	c.SetKeyTypeSchema("int", 15)
+	c.SetKeyTypeSchema("obj.string", "")
+	c.SetKeyTypeSchema("obj.int", 15)
+	c.SetKeyTypeSchema("uint", uint(15))
+	c.SetKeyTypeSchema("obj.uint", uint(15))
+	c.SetKeyTypeSchema("array", []string{})
+	c.SetKeyTypeSchema("obj.array", []string{})
+
+	// Set array of string
+	require.NoError(t, c.Set("array", []string{"abc", "def"}))
+	require.NoError(t, c.Set("obj.array", []string{"abc", "def"}))
+	require.Equal(t, []string{"abc", "def"}, c.Get("array"))
+	require.Equal(t, []string{"abc", "def"}, c.Get("obj.array"))
+	// Set array of string with array of any
+	require.NoError(t, c.Set("array", []any{"abc", "def"}))
+	require.NoError(t, c.Set("obj.array", []any{"abc", "def"}))
+	require.Equal(t, []string{"abc", "def"}, c.Get("array"))
+	require.Equal(t, []string{"abc", "def"}, c.Get("obj.array"))
+	// Set array of string with array of int
+	require.EqualError(t, c.Set("array", []any{"abc", 123}), "invalid type for key 'array': invalid conversion, got int but want string")
+	require.EqualError(t, c.Set("obj.array", []any{"abc", 123}), "invalid type for key 'obj.array': invalid conversion, got int but want string")
+
+	// Set string
+	require.NoError(t, c.Set("string", "abc"))
+	require.NoError(t, c.Set("obj.string", "abc"))
+	require.Equal(t, "abc", c.Get("string"))
+	require.Equal(t, "abc", c.Get("obj.string"))
+	// Set string with int
+	require.EqualError(t, c.Set("string", 123), "invalid type for key 'string': invalid conversion, got int but want string")
+	require.EqualError(t, c.Set("obj.string", 123), "invalid type for key 'obj.string': invalid conversion, got int but want string")
+
+	// Set int
+	require.NoError(t, c.Set("int", 123))
+	require.NoError(t, c.Set("obj.int", 123))
+	require.Equal(t, 123, c.Get("int"))
+	require.Equal(t, 123, c.Get("obj.int"))
+	// Set int with string
+	require.EqualError(t, c.Set("int", "abc"), "invalid type for key 'int': invalid conversion, got string but want int")
+	require.EqualError(t, c.Set("obj.int", "abc"), "invalid type for key 'obj.int': invalid conversion, got string but want int")
+
+	// Set uint
+	require.NoError(t, c.Set("uint", uint(234)))
+	require.NoError(t, c.Set("obj.uint", uint(234)))
+	require.Equal(t, uint(234), c.Get("uint"))
+	require.Equal(t, uint(234), c.Get("obj.uint"))
+	// Set uint using int
+	require.NoError(t, c.Set("uint", 345))
+	require.NoError(t, c.Set("obj.uint", 345))
+	require.Equal(t, uint(345), c.Get("uint"))
+	require.Equal(t, uint(345), c.Get("obj.uint"))
+	// Set uint using float
+	require.NoError(t, c.Set("uint", 456.0))
+	require.NoError(t, c.Set("obj.uint", 456.0))
+	require.Equal(t, uint(456), c.Get("uint"))
+	require.Equal(t, uint(456), c.Get("obj.uint"))
+	// Set uint using string
+	require.EqualError(t, c.Set("uint", "567"), "invalid type for key 'uint': invalid conversion, got string but want uint")
+	require.EqualError(t, c.Set("obj.uint", "567"), "invalid type for key 'obj.uint': invalid conversion, got string but want uint")
+	require.Equal(t, uint(456), c.Get("uint"))
+	require.Equal(t, uint(456), c.Get("obj.uint"))
+
+	json1 := []byte(`{"string":"abcd","int":1234,"obj":{"string":"abcd","int":1234}}`)
+	require.NoError(t, json.Unmarshal(json1, &c))
+	require.Equal(t, "abcd", c.Get("string"))
+	require.Equal(t, 1234, c.Get("int"))
+	require.Equal(t, "abcd", c.Get("obj.string"))
+	require.Equal(t, 1234, c.Get("obj.int"))
+
+	json2 := []byte(`{"string":123,"int":123,"obj":{"string":"abc","int":123}}`)
+	require.EqualError(t, json.Unmarshal(json2, &c), "invalid type for key 'string': invalid conversion, got float64 but want string")
+	json3 := []byte(`{"string":"avc","int":123,"obj":{"string":123,"int":123}}`)
+	require.EqualError(t, json.Unmarshal(json3, &c), "invalid type for key 'obj.string': invalid conversion, got float64 but want string")
+}
diff --git a/internal/go-configmap/errors.go b/internal/go-configmap/errors.go
new file mode 100644
index 00000000000..2ad42f3e129
--- /dev/null
+++ b/internal/go-configmap/errors.go
@@ -0,0 +1,54 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package configmap
+
+import "strings"
+
+// UnmarshalErrors is a collection of errors that occurred during unmarshalling.
+// Do not return this type directly, but use its result() method instead.
+type UnmarshalErrors struct {
+	wrapped []error
+}
+
+func (e *UnmarshalErrors) append(err error) {
+	e.wrapped = append(e.wrapped, err)
+}
+
+func (e *UnmarshalErrors) result() error {
+	if len(e.wrapped) == 0 {
+		return nil
+	}
+	return e
+}
+
+func (e *UnmarshalErrors) Error() string {
+	if len(e.wrapped) == 1 {
+		return e.wrapped[0].Error()
+	}
+	errs := []string{"multiple errors:"}
+	for _, err := range e.wrapped {
+		errs = append(errs, "- "+err.Error())
+	}
+	return strings.Join(errs, "\n")
+}
+
+// WrappedErrors returns the list of errors that occurred during unmarshalling.
+func (e *UnmarshalErrors) WrappedErrors() []error {
+	if e == nil {
+		return nil
+	}
+	return e.wrapped
+}
diff --git a/internal/go-configmap/json.go b/internal/go-configmap/json.go
new file mode 100644
index 00000000000..7ab0deaad4f
--- /dev/null
+++ b/internal/go-configmap/json.go
@@ -0,0 +1,52 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package configmap
+
+import "encoding/json"
+
+func (c Map) MarshalJSON() ([]byte, error) {
+	return json.Marshal(c.values)
+}
+
+func (c *Map) UnmarshalJSON(data []byte) error {
+	in := map[string]any{}
+	if err := json.Unmarshal(data, &in); err != nil {
+		return err
+	}
+
+	c.values = map[string]any{}
+	for k, v := range flattenMap(in) {
+		if err := c.Set(k, v); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func flattenMap(in map[string]any) map[string]any {
+	out := map[string]any{}
+	for k, v := range in {
+		switch v := v.(type) {
+		case map[string]any:
+			for kk, vv := range flattenMap(v) {
+				out[k+"."+kk] = vv
+			}
+		default:
+			out[k] = v
+		}
+	}
+	return out
+}
diff --git a/internal/go-configmap/json_test.go b/internal/go-configmap/json_test.go
new file mode 100644
index 00000000000..54566589baf
--- /dev/null
+++ b/internal/go-configmap/json_test.go
@@ -0,0 +1,48 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package configmap_test
+
+import (
+	"encoding/json"
+	"fmt"
+	"testing"
+
+	"github.com/arduino/arduino-cli/internal/go-configmap"
+	"github.com/stretchr/testify/require"
+)
+
+func TestJson(t *testing.T) {
+	c := configmap.New()
+	c.Set("foo", "bar")
+	c.Set("fooz.bar", "baz")
+	c.Set("answer", 42)
+	require.Equal(t, "bar", c.Get("foo"))
+	require.Equal(t, "baz", c.Get("fooz.bar"))
+	require.Equal(t, 42, c.Get("answer"))
+
+	j1, err := json.Marshal(c)
+	require.NoError(t, err)
+	fmt.Println(string(j1))
+
+	d := configmap.New()
+	err = json.Unmarshal(j1, d)
+	require.NoError(t, err)
+	require.Equal(t, "baz", d.Get("fooz.bar"))
+
+	j2, err := json.Marshal(d)
+	require.NoError(t, err)
+	require.Equal(t, string(j1), string(j2))
+}
diff --git a/internal/go-configmap/types.go b/internal/go-configmap/types.go
new file mode 100644
index 00000000000..d6a032b8dd6
--- /dev/null
+++ b/internal/go-configmap/types.go
@@ -0,0 +1,215 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package configmap
+
+import (
+	"errors"
+	"fmt"
+	"time"
+)
+
+func (c Map) GetStringOk(key string) (string, bool, error) {
+	v, ok := c.GetOk(key)
+	if !ok {
+		return "", false, nil
+	}
+	if s, ok := v.(string); ok {
+		return s, true, nil
+	}
+	return "", false, errors.New(key + " is not a string")
+}
+
+func (c Map) GetString(key string) string {
+	v, ok, err := c.GetStringOk(key)
+	if err != nil {
+		panic(err.Error())
+	}
+	if ok {
+		return v
+	}
+	return ""
+}
+
+func (c Map) SetString(key string, value string) {
+	c.Set(key, value)
+}
+
+func (c Map) GetBoolOk(key string) (bool, bool, error) {
+	v, ok := c.GetOk(key)
+	if !ok {
+		return false, false, nil
+	}
+	if b, ok := v.(bool); ok {
+		return b, true, nil
+	}
+	return false, false, errors.New(key + " is not a bool")
+}
+
+func (c Map) GetBool(key string) bool {
+	v, ok, err := c.GetBoolOk(key)
+	if err != nil {
+		panic(err.Error())
+	}
+	if ok {
+		return v
+	}
+	return false
+}
+
+func (c Map) SetBool(key string, value bool) {
+	c.Set(key, value)
+}
+
+func (c Map) GetUintOk(key string) (uint, bool, error) {
+	v, ok := c.GetOk(key)
+	if !ok {
+		return 0, false, nil
+	}
+	if i, ok := v.(uint); ok {
+		return i, true, nil
+	}
+	return 0, false, errors.New(key + " is not a uint")
+}
+
+func (c Map) GetUint(key string) uint {
+	v, ok, err := c.GetUintOk(key)
+	if err != nil {
+		panic(err.Error())
+	}
+	if ok {
+		return v
+	}
+	return 0
+}
+
+func (c Map) SetUint(key string, value uint) {
+	c.Set(key, value)
+}
+
+func (c Map) GetIntOk(key string) (int, bool, error) {
+	v, ok := c.GetOk(key)
+	if !ok {
+		return 0, false, nil
+	}
+	if i, ok := v.(int); ok {
+		return i, true, nil
+	}
+	return 0, false, errors.New(key + " is not a uint")
+}
+
+func (c Map) GetInt(key string) int {
+	v, ok, err := c.GetIntOk(key)
+	if err != nil {
+		panic(err.Error())
+	}
+	if ok {
+		return v
+	}
+	return 0
+}
+
+func (c Map) SetInt(key string, value int) {
+	c.Set(key, value)
+}
+
+func (c Map) GetUint32Ok(key string) (uint32, bool, error) {
+	v, ok := c.GetOk(key)
+	if !ok {
+		return 0, false, nil
+	}
+	if i, ok := v.(uint32); ok {
+		return i, true, nil
+	}
+	return 0, false, errors.New(key + " is not a uint32")
+}
+
+func (c Map) GetUint32(key string) uint32 {
+	v, ok, err := c.GetUint32Ok(key)
+	if err != nil {
+		panic(err.Error())
+	}
+	if ok {
+		return v
+	}
+	return 0
+}
+
+func (c Map) SetUint32(key string, value uint32) {
+	c.Set(key, value)
+}
+
+func (c Map) GetStringSliceOk(key string) ([]string, bool, error) {
+	v, ok := c.GetOk(key)
+	if !ok {
+		return nil, false, nil
+	}
+	if genArray, ok := v.([]string); ok {
+		return genArray, true, nil
+	}
+	if genArray, ok := v.([]interface{}); ok {
+		// transform []interface{} to []string
+		var strArray []string
+		for i, gen := range genArray {
+			if str, ok := gen.(string); ok {
+				strArray = append(strArray, str)
+			} else {
+				return nil, false, fmt.Errorf("%s[%d] is not a string", key, i)
+			}
+		}
+		return strArray, true, nil
+	}
+	return nil, false, fmt.Errorf("%s is not an array of strings", key)
+}
+
+func (c Map) GetStringSlice(key string) []string {
+	v, ok, err := c.GetStringSliceOk(key)
+	if err != nil {
+		panic(err.Error())
+	}
+	if ok {
+		return v
+	}
+	return nil
+}
+
+func (c Map) GetDurationOk(key string) (time.Duration, bool, error) {
+	v, ok := c.GetOk(key)
+	if !ok {
+		return 0, false, nil
+	}
+	if s, ok := v.(string); !ok {
+		return 0, false, errors.New(key + " is not a Duration")
+	} else if d, err := time.ParseDuration(s); err != nil {
+		return 0, false, fmt.Errorf("%s is not a valid Duration: %w", key, err)
+	} else {
+		return d, true, nil
+	}
+}
+
+func (c Map) GetDuration(key string) time.Duration {
+	v, ok, err := c.GetDurationOk(key)
+	if err != nil {
+		panic(err.Error())
+	}
+	if ok {
+		return v
+	}
+	return 0
+}
+
+func (c Map) SetDuration(key string, value time.Duration) {
+	c.SetString(key, value.String())
+}
diff --git a/internal/go-configmap/yaml.go b/internal/go-configmap/yaml.go
new file mode 100644
index 00000000000..e58f8f31ecf
--- /dev/null
+++ b/internal/go-configmap/yaml.go
@@ -0,0 +1,39 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package configmap
+
+import (
+	"gopkg.in/yaml.v3"
+)
+
+func (c Map) MarshalYAML() (interface{}, error) {
+	return c.values, nil
+}
+
+func (c *Map) UnmarshalYAML(node *yaml.Node) error {
+	in := map[string]any{}
+	if err := node.Decode(&in); err != nil {
+		return err
+	}
+
+	errs := &UnmarshalErrors{}
+	for k, v := range flattenMap(in) {
+		if err := c.Set(k, v); err != nil {
+			errs.append(err)
+		}
+	}
+	return errs.result()
+}
diff --git a/internal/go-configmap/yaml_test.go b/internal/go-configmap/yaml_test.go
new file mode 100644
index 00000000000..9293f70662f
--- /dev/null
+++ b/internal/go-configmap/yaml_test.go
@@ -0,0 +1,48 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2024 ARDUINO SA (http://www.arduino.cc/)
+//
+// This software is released under the GNU General Public License version 3,
+// which covers the main part of arduino-cli.
+// The terms of this license can be found at:
+// https://www.gnu.org/licenses/gpl-3.0.en.html
+//
+// You can be released from the requirements of the above licenses by purchasing
+// a commercial license. Buying such a license is mandatory if you want to
+// modify or otherwise use the software for commercial activities involving the
+// Arduino software without disclosing the source code of your own applications.
+// To purchase a commercial license, send an email to license@arduino.cc.
+
+package configmap_test
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/arduino/arduino-cli/internal/go-configmap"
+	"github.com/stretchr/testify/require"
+	"gopkg.in/yaml.v3"
+)
+
+func TestYaml(t *testing.T) {
+	c := configmap.New()
+	c.Set("foo", "bar")
+	c.Set("fooz.bar", "baz")
+	c.Set("answer", 42)
+	require.Equal(t, "bar", c.Get("foo"))
+	require.Equal(t, "baz", c.Get("fooz.bar"))
+	require.Equal(t, 42, c.Get("answer"))
+
+	y1, err := yaml.Marshal(c)
+	require.NoError(t, err)
+	fmt.Println(string(y1))
+
+	d := configmap.New()
+	err = yaml.Unmarshal(y1, d)
+	require.NoError(t, err)
+	require.Equal(t, "baz", d.Get("fooz.bar"))
+
+	y2, err := yaml.Marshal(d)
+	require.NoError(t, err)
+	require.Equal(t, string(y1), string(y2))
+}
diff --git a/internal/i18n/data/README.md b/internal/i18n/data/README.md
index d52ec1782ae..5d8d773c26d 100644
--- a/internal/i18n/data/README.md
+++ b/internal/i18n/data/README.md
@@ -6,7 +6,7 @@ This folder contains the [localization](https://wikipedia.org/wiki/Language_loca
 at the source:
 
 - **en.po** - edit the string in the source code file indicated by the comment above it <br /> e.g., a comment
-  `#: commands/upload/upload.go:615` indicates the source string is at line 615 of the file
-  [`commands/upload/upload.go`](../../../commands/upload/upload.go)
+  `#: internal/cli/lib/check_deps.go:102` indicates the source string is at line 102 of the file
+  [`internal/cli/lib/check_deps.go`](../../../internal/cli/lib/check_deps.go)
 - **All other files** - the localization is done on **Transifex**: <br />
   https://explore.transifex.com/arduino-1/arduino-cli/
diff --git a/internal/integrationtest/arduino-cli.go b/internal/integrationtest/arduino-cli.go
index 3ec6f44f1ea..0e795f64d26 100644
--- a/internal/integrationtest/arduino-cli.go
+++ b/internal/integrationtest/arduino-cli.go
@@ -109,10 +109,10 @@ func NewArduinoCliWithinEnvironment(env *Environment, config *ArduinoCLIConfig)
 	}
 
 	cli.cliEnvVars = map[string]string{
-		"LANG":                   "en",
-		"ARDUINO_DATA_DIR":       cli.dataDir.String(),
-		"ARDUINO_DOWNLOADS_DIR":  cli.stagingDir.String(),
-		"ARDUINO_SKETCHBOOK_DIR": cli.sketchbookDir.String(),
+		"LANG":                                          "en",
+		"ARDUINO_DIRECTORIES_DATA":                      cli.dataDir.String(),
+		"ARDUINO_DIRECTORIES_DOWNLOADS":                 cli.stagingDir.String(),
+		"ARDUINO_DIRECTORIES_USER":                      cli.sketchbookDir.String(),
 		"ARDUINO_BUILD_CACHE_COMPILATIONS_BEFORE_PURGE": "0",
 	}
 	env.RegisterCleanUpCallback(cli.CleanUp)
@@ -326,7 +326,7 @@ func (cli *ArduinoCLI) run(stdoutBuff, stderrBuff io.Writer, stdinBuff io.Reader
 		defer fmt.Fprintln(terminalOut, "::endgroup::")
 	}
 
-	fmt.Fprintln(terminalOut, color.HiBlackString(">>> Running: ")+color.HiYellowString("%s %s", cli.path, strings.Join(args, " ")))
+	fmt.Fprintln(terminalOut, color.HiBlackString(">>> Running: ")+color.HiYellowString("%s %s %s", cli.path, strings.Join(args, " "), env))
 	cliProc, err := paths.NewProcessFromPath(cli.convertEnvForExecutils(env), cli.path, args...)
 	cli.t.NoError(err)
 	stdout, err := cliProc.StdoutPipe()
@@ -444,8 +444,8 @@ func (cli *ArduinoCLI) Create() *ArduinoCLIInstance {
 // SetValue calls the "SetValue" gRPC method.
 func (cli *ArduinoCLI) SetValue(key, jsonData string) error {
 	req := &commands.SettingsSetValueRequest{
-		Key:      key,
-		JsonData: jsonData,
+		Key:          key,
+		EncodedValue: jsonData,
 	}
 	logCallf(">>> SetValue(%+v)\n", req)
 	_, err := cli.daemonClient.SettingsSetValue(context.Background(), req)
diff --git a/internal/integrationtest/board/board_test.go b/internal/integrationtest/board/board_test.go
index 639a27d3a7b..0541f1a5c0f 100644
--- a/internal/integrationtest/board/board_test.go
+++ b/internal/integrationtest/board/board_test.go
@@ -651,7 +651,7 @@ func TestCLIStartupWithCorruptedInventory(t *testing.T) {
 	require.NoError(t, f.Close())
 
 	// the CLI should not be able to load inventory and report it to the logs
-	_, stderr, err := cli.Run("core", "update-index", "-v")
+	stdout, _, err := cli.Run("core", "update-index", "-v")
 	require.NoError(t, err)
-	require.Contains(t, string(stderr), "Error loading inventory store")
+	require.Contains(t, string(stdout), "Error loading inventory store")
 }
diff --git a/internal/integrationtest/compile_1/compile_test.go b/internal/integrationtest/compile_1/compile_test.go
index 6d6dd605602..27b31cb82c1 100644
--- a/internal/integrationtest/compile_1/compile_test.go
+++ b/internal/integrationtest/compile_1/compile_test.go
@@ -522,7 +522,7 @@ func compileWithExportBinariesConfig(t *testing.T, env *integrationtest.Environm
 		{
 			"config": {
 				"sketch": {
-					"always_export_binaries": "true"
+					"always_export_binaries": true
 				}
 			}
 		}`)
diff --git a/internal/integrationtest/config/config_test.go b/internal/integrationtest/config/config_test.go
index 107141ccfb4..5ed5145dd62 100644
--- a/internal/integrationtest/config/config_test.go
+++ b/internal/integrationtest/config/config_test.go
@@ -17,6 +17,7 @@ package config_test
 
 import (
 	"path/filepath"
+	"strings"
 	"testing"
 
 	"github.com/arduino/arduino-cli/internal/integrationtest"
@@ -50,15 +51,6 @@ func TestInitWithExistingCustomConfig(t *testing.T) {
 	err = yaml.Unmarshal(configFile, config)
 	require.NoError(t, err)
 	require.Equal(t, config["board_manager"]["additional_urls"].([]interface{})[0].(string), "https://example.com")
-	require.Equal(t, config["daemon"]["port"].(string), "50051")
-	require.Equal(t, config["directories"]["data"].(string), cli.DataDir().String())
-	require.Equal(t, config["directories"]["downloads"].(string), cli.DownloadDir().String())
-	require.Equal(t, config["directories"]["user"].(string), cli.SketchbookDir().String())
-	require.Empty(t, config["logging"]["file"])
-	require.Equal(t, config["logging"]["format"].(string), "text")
-	require.Equal(t, config["logging"]["level"].(string), "info")
-	require.Equal(t, config["metrics"]["addr"].(string), ":9090")
-	require.True(t, config["metrics"]["enabled"].(bool))
 
 	configFilePath := cli.WorkingDir().Join("config", "test", "config.yaml")
 	require.NoFileExists(t, configFilePath.String())
@@ -71,15 +63,6 @@ func TestInitWithExistingCustomConfig(t *testing.T) {
 	err = yaml.Unmarshal(configFile, config)
 	require.NoError(t, err)
 	require.Empty(t, config["board_manager"]["additional_urls"])
-	require.Equal(t, config["daemon"]["port"].(string), "50051")
-	require.Equal(t, config["directories"]["data"].(string), cli.DataDir().String())
-	require.Equal(t, config["directories"]["downloads"].(string), cli.DownloadDir().String())
-	require.Equal(t, config["directories"]["user"].(string), cli.SketchbookDir().String())
-	require.Empty(t, config["logging"]["file"])
-	require.Equal(t, config["logging"]["format"].(string), "text")
-	require.Equal(t, config["logging"]["level"].(string), "info")
-	require.Equal(t, config["metrics"]["addr"].(string), ":9090")
-	require.True(t, config["metrics"]["enabled"].(bool))
 }
 
 func TestInitOverwriteExistingCustomFile(t *testing.T) {
@@ -96,15 +79,6 @@ func TestInitOverwriteExistingCustomFile(t *testing.T) {
 	err = yaml.Unmarshal(configFile, config)
 	require.NoError(t, err)
 	require.Equal(t, config["board_manager"]["additional_urls"].([]interface{})[0].(string), "https://example.com")
-	require.Equal(t, config["daemon"]["port"].(string), "50051")
-	require.Equal(t, config["directories"]["data"].(string), cli.DataDir().String())
-	require.Equal(t, config["directories"]["downloads"].(string), cli.DownloadDir().String())
-	require.Equal(t, config["directories"]["user"].(string), cli.SketchbookDir().String())
-	require.Empty(t, config["logging"]["file"])
-	require.Equal(t, config["logging"]["format"].(string), "text")
-	require.Equal(t, config["logging"]["level"].(string), "info")
-	require.Equal(t, config["metrics"]["addr"].(string), ":9090")
-	require.True(t, config["metrics"]["enabled"].(bool))
 
 	stdout, _, err = cli.Run("config", "init", "--overwrite")
 	require.NoError(t, err)
@@ -115,15 +89,6 @@ func TestInitOverwriteExistingCustomFile(t *testing.T) {
 	err = yaml.Unmarshal(configFile, config)
 	require.NoError(t, err)
 	require.Empty(t, config["board_manager"]["additional_urls"])
-	require.Equal(t, config["daemon"]["port"].(string), "50051")
-	require.Equal(t, config["directories"]["data"].(string), cli.DataDir().String())
-	require.Equal(t, config["directories"]["downloads"].(string), cli.DownloadDir().String())
-	require.Equal(t, config["directories"]["user"].(string), cli.SketchbookDir().String())
-	require.Empty(t, config["logging"]["file"])
-	require.Equal(t, config["logging"]["format"].(string), "text")
-	require.Equal(t, config["logging"]["level"].(string), "info")
-	require.Equal(t, config["metrics"]["addr"].(string), ":9090")
-	require.True(t, config["metrics"]["enabled"].(bool))
 }
 
 func TestInitDestAbsolutePath(t *testing.T) {
@@ -289,21 +254,37 @@ func TestAddRemoveSetDeleteOnUnexistingKey(t *testing.T) {
 	_, _, err := cli.Run("config", "init", "--dest-dir", ".")
 	require.NoError(t, err)
 
-	_, stderr, err := cli.Run("config", "add", "some.key", "some_value", "--config-file", "arduino-cli.yaml")
-	require.Error(t, err)
-	require.Contains(t, string(stderr), "Settings key doesn't exist")
+	j, _, err := cli.Run("config", "dump", "--format", "json", "--config-file", "arduino-cli.yaml")
+	require.NoError(t, err)
+	requirejson.Contains(t, j, `{"config":{ "board_manager": {"additional_urls":[]} } }`)
 
-	_, stderr, err = cli.Run("config", "remove", "some.key", "some_value", "--config-file", "arduino-cli.yaml")
-	require.Error(t, err)
-	require.Contains(t, string(stderr), "Settings key doesn't exist")
+	// Delete array key
+	_, _, err = cli.Run("config", "delete", "board_manager.additional_urls", "--config-file", "arduino-cli.yaml")
+	require.NoError(t, err)
+	j, _, err = cli.Run("config", "dump", "--format", "json", "--config-file", "arduino-cli.yaml")
+	require.NoError(t, err)
+	requirejson.NotContains(t, j, `{"config":{ "board_manager": {"additional_urls":[]} } }`)
 
-	_, stderr, err = cli.Run("config", "set", "some.key", "some_value", "--config-file", "arduino-cli.yaml")
-	require.Error(t, err)
-	require.Contains(t, string(stderr), "Settings key doesn't exist")
+	// Add to non-existing array key
+	_, _, err = cli.Run("config", "add", "board_manager.additional_urls", "some_value", "--config-file", "arduino-cli.yaml")
+	require.NoError(t, err)
+	j, _, err = cli.Run("config", "dump", "--format", "json", "--config-file", "arduino-cli.yaml")
+	require.NoError(t, err)
+	requirejson.Contains(t, j, `{"config":{ "board_manager": {"additional_urls":["some_value"]} } }`)
 
-	_, stderr, err = cli.Run("config", "delete", "some.key", "--config-file", "arduino-cli.yaml")
-	require.Error(t, err)
-	require.Contains(t, string(stderr), "Cannot delete the key some.key: key not found in settings\n")
+	// Remove from non-existing array key
+	_, _, err = cli.Run("config", "remove", "board_manager.additional_urls", "some_value", "--config-file", "arduino-cli.yaml")
+	require.NoError(t, err)
+	j, _, err = cli.Run("config", "dump", "--format", "json", "--config-file", "arduino-cli.yaml")
+	require.NoError(t, err)
+	requirejson.NotContains(t, j, `{"config":{ "board_manager": {"additional_urls":["some_value"]} } }`)
+
+	// Set on non-existing key
+	_, _, err = cli.Run("config", "set", "board_manager.additional_urls", "some_value", "other_value", "--config-file", "arduino-cli.yaml")
+	require.NoError(t, err)
+	j, _, err = cli.Run("config", "dump", "--format", "json", "--config-file", "arduino-cli.yaml")
+	require.NoError(t, err)
+	requirejson.Contains(t, j, `{"config":{ "board_manager": {"additional_urls":["some_value","other_value"]} } }`)
 }
 
 func TestAddSingleArgument(t *testing.T) {
@@ -434,6 +415,8 @@ func TestAddOnUnsupportedKey(t *testing.T) {
 	// Create a config file
 	_, _, err := cli.Run("config", "init", "--dest-dir", ".")
 	require.NoError(t, err)
+	_, _, err = cli.Run("config", "set", "daemon.port", "50051", "--config-file", "arduino-cli.yaml")
+	require.NoError(t, err)
 
 	// Verifies default value
 	stdout, _, err := cli.Run("config", "dump", "--json", "--config-file", "arduino-cli.yaml")
@@ -542,6 +525,8 @@ func TestRemoveOnUnsupportedKey(t *testing.T) {
 	// Create a config file
 	_, _, err := cli.Run("config", "init", "--dest-dir", ".")
 	require.NoError(t, err)
+	_, _, err = cli.Run("config", "set", "daemon.port", "50051", "--config-file", "arduino-cli.yaml")
+	require.NoError(t, err)
 
 	// Verifies default value
 	stdout, _, err := cli.Run("config", "dump", "--json", "--config-file", "arduino-cli.yaml")
@@ -700,7 +685,7 @@ func TestSetStringWithSingleArgument(t *testing.T) {
 	// Verifies default state
 	stdout, _, err := cli.Run("config", "dump", "--json", "--config-file", "arduino-cli.yaml")
 	require.NoError(t, err)
-	requirejson.Query(t, stdout, ".config | .logging | .level", "\"info\"")
+	requirejson.NotContains(t, stdout, `{"config":{"logging":{"level"}}}`)
 
 	// Changes value
 	_, _, err = cli.Run("config", "set", "logging.level", "trace", "--config-file", "arduino-cli.yaml")
@@ -723,12 +708,12 @@ func TestSetStringWithMultipleArguments(t *testing.T) {
 	// Verifies default state
 	stdout, _, err := cli.Run("config", "dump", "--json", "--config-file", "arduino-cli.yaml")
 	require.NoError(t, err)
-	requirejson.Query(t, stdout, ".config | .logging | .level", "\"info\"")
+	requirejson.NotContains(t, stdout, `{"config":{"logging":{"level"}}}`)
 
 	// Tries to change value
 	_, stderr, err := cli.Run("config", "set", "logging.level", "trace", "debug")
 	require.Error(t, err)
-	require.Contains(t, string(stderr), "Can't set multiple values in key logging.level")
+	require.Contains(t, string(stderr), "Error setting value: invalid type for key 'logging.level': invalid conversion, got array but want string")
 }
 
 func TestSetBoolWithSingleArgument(t *testing.T) {
@@ -742,7 +727,7 @@ func TestSetBoolWithSingleArgument(t *testing.T) {
 	// Verifies default state
 	stdout, _, err := cli.Run("config", "dump", "--json", "--config-file", "arduino-cli.yaml")
 	require.NoError(t, err)
-	requirejson.Query(t, stdout, ".config | .library | .enable_unsafe_install", "false")
+	requirejson.NotContains(t, stdout, `{"config": {"library": {"enable_unsafe_install"}}}`)
 
 	// Changes value
 	_, _, err = cli.Run("config", "set", "library.enable_unsafe_install", "true", "--config-file", "arduino-cli.yaml")
@@ -761,6 +746,8 @@ func TestSetBoolWithMultipleArguments(t *testing.T) {
 	// Create a config file
 	_, _, err := cli.Run("config", "init", "--dest-dir", ".")
 	require.NoError(t, err)
+	_, _, err = cli.Run("config", "set", "library.enable_unsafe_install", "false", "--config-file", "arduino-cli.yaml")
+	require.NoError(t, err)
 
 	// Verifies default state
 	stdout, _, err := cli.Run("config", "dump", "--json", "--config-file", "arduino-cli.yaml")
@@ -770,7 +757,7 @@ func TestSetBoolWithMultipleArguments(t *testing.T) {
 	// Changes value
 	_, stderr, err := cli.Run("config", "set", "library.enable_unsafe_install", "true", "foo", "--config-file", "arduino-cli.yaml")
 	require.Error(t, err)
-	require.Contains(t, string(stderr), "Can't set multiple values in key library.enable_unsafe_install")
+	require.Contains(t, string(stderr), "Error setting value: invalid type for key 'library.enable_unsafe_install': invalid conversion, got array but want bool")
 }
 
 func TestDelete(t *testing.T) {
@@ -780,6 +767,8 @@ func TestDelete(t *testing.T) {
 	// Create a config file
 	_, _, err := cli.Run("config", "init", "--dest-dir", ".")
 	require.NoError(t, err)
+	_, _, err = cli.Run("config", "set", "library.enable_unsafe_install", "false", "--config-file", "arduino-cli.yaml")
+	require.NoError(t, err)
 
 	// Verifies default state
 	stdout, _, err := cli.Run("config", "dump", "--json", "--config-file", "arduino-cli.yaml")
@@ -824,6 +813,8 @@ func TestGet(t *testing.T) {
 	// Create a config file
 	_, _, err := cli.Run("config", "init", "--dest-dir", ".")
 	require.NoError(t, err)
+	_, _, err = cli.Run("config", "set", "daemon.port", "50051", "--config-file", "arduino-cli.yaml")
+	require.NoError(t, err)
 
 	// Verifies default state
 	stdout, _, err := cli.Run("config", "dump", "--json", "--config-file", "arduino-cli.yaml")
@@ -843,7 +834,7 @@ func TestGet(t *testing.T) {
 	// Get undefined key
 	_, stderr, err := cli.Run("config", "get", "foo", "--json", "--config-file", "arduino-cli.yaml")
 	require.Error(t, err)
-	requirejson.Contains(t, stderr, `{"error":"Cannot get the configuration key foo: key not found in settings"}`)
+	requirejson.Contains(t, stderr, `{"error":"Cannot get the configuration key foo: key foo not found"}`)
 }
 
 func TestInitializationOrderOfConfigThroughFlagAndEnv(t *testing.T) {
@@ -851,9 +842,10 @@ func TestInitializationOrderOfConfigThroughFlagAndEnv(t *testing.T) {
 	defer env.CleanUp()
 
 	tmp := t.TempDir()
-	cliConfig, envConfig := paths.New(filepath.Join(tmp, "cli.yaml")), paths.New(filepath.Join(tmp, "env.yaml"))
-	cliConfig.WriteFile([]byte(`cli-test: "test"`))
-	envConfig.WriteFile([]byte(`env-test: "test"`))
+	cliConfig := paths.New(filepath.Join(tmp, "cli.yaml"))
+	cliConfig.WriteFile([]byte(`locale: "test"`))
+	envConfig := paths.New(filepath.Join(tmp, "env.yaml"))
+	envConfig.WriteFile([]byte(`locale: "test2"`))
 
 	// No flag nor env specified.
 	stdout, _, err := cli.Run("config", "dump", "--json")
@@ -863,16 +855,50 @@ func TestInitializationOrderOfConfigThroughFlagAndEnv(t *testing.T) {
 	// Flag specified
 	stdout, _, err = cli.Run("config", "dump", "--config-file", cliConfig.String(), "--json")
 	require.NoError(t, err)
-	requirejson.Contains(t, stdout, `{"config":{ "cli-test": "test" }}`)
+	requirejson.Contains(t, stdout, `{"config":{ "locale": "test" }}`)
 
 	// Env specified
 	customEnv := map[string]string{"ARDUINO_CONFIG_FILE": envConfig.String()}
 	stdout, _, err = cli.RunWithCustomEnv(customEnv, "config", "dump", "--json")
 	require.NoError(t, err)
-	requirejson.Contains(t, stdout, `{"config":{ "env-test": "test" }}`)
+	requirejson.Contains(t, stdout, `{"config":{ "locale": "test2" }}`)
 
 	// Flag and env specified, flag takes precedence
 	stdout, _, err = cli.RunWithCustomEnv(customEnv, "config", "dump", "--config-file", cliConfig.String(), "--json")
 	require.NoError(t, err)
-	requirejson.Contains(t, stdout, `{"config":{ "cli-test": "test" }}`)
+	requirejson.Contains(t, stdout, `{"config":{ "locale": "test" }}`)
+}
+
+func TestConfigLoadWithUnknownOrInvalidKeys(t *testing.T) {
+	env, cli := integrationtest.CreateArduinoCLIWithEnvironment(t)
+	defer env.CleanUp()
+
+	tmp := t.TempDir()
+	unkwnownConfig := paths.New(filepath.Join(tmp, "unknown.yaml"))
+	unkwnownConfig.WriteFile([]byte(`
+unk: "test"
+build.unk: 123
+`))
+	t.Cleanup(func() { unkwnownConfig.Remove() })
+
+	// Run "config get" with a configuration containing an unknown key
+	out, _, err := cli.Run("config", "get", "locale", "--config-file", unkwnownConfig.String())
+	require.NoError(t, err)
+	require.Equal(t, "en", strings.TrimSpace(string(out)))
+
+	// Run "config get" with a configuration containing an invalid key
+	invalidConfig := paths.New(filepath.Join(tmp, "invalid.yaml"))
+	invalidConfig.WriteFile([]byte(`locale: 123`))
+	t.Cleanup(func() { invalidConfig.Remove() })
+	out, _, err = cli.Run("config", "get", "locale", "--config-file", invalidConfig.String())
+	require.NoError(t, err)
+	require.Equal(t, "en", strings.TrimSpace(string(out)))
+
+	// Run "config get" with a configuration containing a null array
+	nullArrayConfig := paths.New(filepath.Join(tmp, "null_array.yaml"))
+	nullArrayConfig.WriteFile([]byte(`board_manager.additional_urls:`))
+	t.Cleanup(func() { nullArrayConfig.Remove() })
+	out, _, err = cli.Run("config", "get", "locale", "--config-file", invalidConfig.String())
+	require.NoError(t, err)
+	require.Equal(t, "en", strings.TrimSpace(string(out)))
 }
diff --git a/internal/integrationtest/core/core_test.go b/internal/integrationtest/core/core_test.go
index 864e45ab710..fbda8da1c0c 100644
--- a/internal/integrationtest/core/core_test.go
+++ b/internal/integrationtest/core/core_test.go
@@ -101,7 +101,7 @@ func TestCoreSearch(t *testing.T) {
 
 	checkPlatformIsInJSONOutput := func(stdout []byte, id, version string) {
 		jqquery := fmt.Sprintf(`{"platforms":[{id:"%s", releases:{"%s":{}}}]}`, id, version)
-		requirejson.Contains(t, out, jqquery, "platform %s@%s is missing from the output", id, version)
+		requirejson.Contains(t, stdout, jqquery, "platform %s@%s is missing from the output", id, version)
 	}
 
 	// Search all Retrokit platforms
diff --git a/internal/integrationtest/daemon/daemon_test.go b/internal/integrationtest/daemon/daemon_test.go
index 3bae601a01d..08e60cffccf 100644
--- a/internal/integrationtest/daemon/daemon_test.go
+++ b/internal/integrationtest/daemon/daemon_test.go
@@ -63,15 +63,15 @@ func TestArduinoCliDaemon(t *testing.T) {
 		require.NoError(t, err)
 		watcherCanceldCh := make(chan struct{})
 		go func() {
+			defer close(watcherCanceldCh)
 			for {
 				msg, err := watcher.Recv()
 				if errors.Is(err, io.EOF) {
-					fmt.Println("Watcher EOF")
+					fmt.Println("Got EOF from watcher")
 					return
 				}
 				if s, ok := status.FromError(err); ok && s.Code() == codes.Canceled {
-					fmt.Println("Watcher canceled")
-					watcherCanceldCh <- struct{}{}
+					fmt.Println("Got Canceled error from watcher")
 					return
 				}
 				require.NoError(t, err, "BoardListWatch grpc call returned an error")
@@ -357,7 +357,7 @@ func TestDaemonBundleLibInstall(t *testing.T) {
 	}
 
 	// Un-Set builtin libraries dir
-	err := cli.SetValue("directories.builtin.libraries", `""`)
+	err := cli.SetValue("directories.builtin.libraries", "")
 	require.NoError(t, err)
 
 	// Re-init
@@ -503,7 +503,7 @@ func TestDaemonCoreUpgradePlatform(t *testing.T) {
 			require.NoError(t, err)
 
 			platform, upgradeError := analyzePlatformUpgradeClient(plUpgrade)
-			require.ErrorIs(t, upgradeError, (&cmderrors.PlatformAlreadyAtTheLatestVersionError{Platform: "esp8266:esp8266"}).ToRPCStatus().Err())
+			require.ErrorIs(t, upgradeError, (&cmderrors.PlatformAlreadyAtTheLatestVersionError{Platform: "esp8266:esp8266"}).GRPCStatus().Err())
 			require.NotNil(t, platform)
 			require.False(t, platform.GetMetadata().GetIndexed())        // the esp866 is not present in the additional-urls
 			require.False(t, platform.GetRelease().GetMissingMetadata()) // install.json is present
@@ -528,7 +528,7 @@ func TestDaemonCoreUpgradePlatform(t *testing.T) {
 			require.NoError(t, err)
 
 			platform, upgradeError := analyzePlatformUpgradeClient(plUpgrade)
-			require.ErrorIs(t, upgradeError, (&cmderrors.PlatformAlreadyAtTheLatestVersionError{Platform: "esp8266:esp8266"}).ToRPCStatus().Err())
+			require.ErrorIs(t, upgradeError, (&cmderrors.PlatformAlreadyAtTheLatestVersionError{Platform: "esp8266:esp8266"}).GRPCStatus().Err())
 			require.NotNil(t, platform)
 			require.False(t, platform.GetMetadata().GetIndexed())       // the esp866 is not present in the additional-urls
 			require.True(t, platform.GetRelease().GetMissingMetadata()) // install.json is present
diff --git a/main.go b/main.go
index db1baad2bee..fa29df9c148 100644
--- a/main.go
+++ b/main.go
@@ -16,19 +16,73 @@
 package main
 
 import (
+	"context"
+	"fmt"
+	"io"
 	"os"
 
+	"github.com/arduino/arduino-cli/commands"
 	"github.com/arduino/arduino-cli/internal/cli"
+	"github.com/arduino/arduino-cli/internal/cli/config"
 	"github.com/arduino/arduino-cli/internal/cli/configuration"
 	"github.com/arduino/arduino-cli/internal/cli/feedback"
 	"github.com/arduino/arduino-cli/internal/i18n"
+	rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
+	"github.com/arduino/go-paths-helper"
+	"github.com/sirupsen/logrus"
+	"github.com/spf13/cobra"
 )
 
 func main() {
-	configuration.Settings = configuration.Init(configuration.FindConfigFileInArgsFallbackOnEnv(os.Args))
-	i18n.Init(configuration.Settings.GetString("locale"))
-	arduinoCmd := cli.NewCommand()
-	if err := arduinoCmd.Execute(); err != nil {
+	// Disable logging until it is setup in the arduino-cli pre-run
+	logrus.SetOutput(io.Discard)
+
+	// Create a new ArduinoCoreServer
+	srv := commands.NewArduinoCoreServer()
+
+	// Search for the configuration file in the command line arguments and in the environment
+	configFile := configuration.FindConfigFileInArgsFallbackOnEnv(os.Args)
+	ctx := config.SetConfigFile(context.Background(), configFile)
+
+	// Read the settings from the configuration file
+	openReq := &rpc.ConfigurationOpenRequest{SettingsFormat: "yaml"}
+	var configFileLoadingWarnings []string
+	if configData, err := paths.New(configFile).ReadFile(); err == nil {
+		openReq.EncodedSettings = string(configData)
+	} else if !os.IsNotExist(err) {
+		feedback.FatalError(fmt.Errorf("couldn't read configuration file: %w", err), feedback.ErrGeneric)
+	}
+	if resp, err := srv.ConfigurationOpen(ctx, openReq); err != nil {
+		feedback.FatalError(fmt.Errorf("couldn't load configuration: %w", err), feedback.ErrGeneric)
+	} else if warnings := resp.GetWarnings(); len(warnings) > 0 {
+		// Save the warnings to show them later when the feedback package is fully initialized
+		configFileLoadingWarnings = warnings
+	}
+
+	// Get the current settings from the server
+	resp, err := srv.ConfigurationGet(ctx, &rpc.ConfigurationGetRequest{})
+	if err != nil {
+		feedback.FatalError(err, feedback.ErrGeneric)
+	}
+	config := resp.GetConfiguration()
+
+	// Setup i18n
+	i18n.Init(config.GetLocale())
+
+	// Setup command line parser with the server and settings
+	arduinoCmd := cli.NewCommand(srv)
+	parentPreRun := arduinoCmd.PersistentPreRun
+	arduinoCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
+		if parentPreRun != nil {
+			parentPreRun(cmd, args)
+		}
+		for _, warning := range configFileLoadingWarnings {
+			feedback.Warning(warning)
+		}
+	}
+
+	// Execute the command line
+	if err := arduinoCmd.ExecuteContext(ctx); err != nil {
 		feedback.FatalError(err, feedback.ErrGeneric)
 	}
 }
diff --git a/rpc/cc/arduino/cli/commands/v1/board.pb.go b/rpc/cc/arduino/cli/commands/v1/board.pb.go
index 479446d50e6..65e778c1ba6 100644
--- a/rpc/cc/arduino/cli/commands/v1/board.pb.go
+++ b/rpc/cc/arduino/cli/commands/v1/board.pb.go
@@ -935,6 +935,8 @@ type BoardListResponse struct {
 
 	// List of ports and the boards detected on those ports.
 	Ports []*DetectedPort `protobuf:"bytes,1,rep,name=ports,proto3" json:"ports,omitempty"`
+	// Warning messages or errors coming from the discoveries.
+	Warnings []string `protobuf:"bytes,2,rep,name=warnings,proto3" json:"warnings,omitempty"`
 }
 
 func (x *BoardListResponse) Reset() {
@@ -976,6 +978,13 @@ func (x *BoardListResponse) GetPorts() []*DetectedPort {
 	return nil
 }
 
+func (x *BoardListResponse) GetWarnings() []string {
+	if x != nil {
+		return x.Warnings
+	}
+	return nil
+}
+
 type DetectedPort struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -1606,84 +1615,86 @@ var file_cc_arduino_cli_commands_v1_board_proto_rawDesc = []byte{
 	0x63, 0x65, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x18, 0x0a, 0x07,
 	0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74,
 	0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x62, 0x6e, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x62, 0x6e, 0x22, 0x53, 0x0a, 0x11, 0x42, 0x6f,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x71, 0x62, 0x6e, 0x22, 0x6f, 0x0a, 0x11, 0x42, 0x6f,
 	0x61, 0x72, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
 	0x3e, 0x0a, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28,
 	0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e,
 	0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x74, 0x65,
-	0x63, 0x74, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x22,
-	0x98, 0x01, 0x0a, 0x0c, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74,
-	0x12, 0x52, 0x0a, 0x0f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x6f, 0x61,
-	0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x63, 0x2e, 0x61,
-	0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61,
-	0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x4c, 0x69, 0x73, 0x74,
-	0x49, 0x74, 0x65, 0x6d, 0x52, 0x0e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x42, 0x6f,
-	0x61, 0x72, 0x64, 0x73, 0x12, 0x34, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e,
+	0x63, 0x74, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x05, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x12,
+	0x1a, 0x0a, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
+	0x09, 0x52, 0x08, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x98, 0x01, 0x0a, 0x0c,
+	0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x52, 0x0a, 0x0f,
+	0x6d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x18,
+	0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69,
+	0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e,
+	0x76, 0x31, 0x2e, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d,
+	0x52, 0x0e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x73,
+	0x12, 0x34, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20,
+	0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e,
+	0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x72, 0x74,
+	0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x22, 0xac, 0x01, 0x0a, 0x13, 0x42, 0x6f, 0x61, 0x72, 0x64,
+	0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40,
+	0x0a, 0x08, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x24, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c,
+	0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e,
+	0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65,
+	0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x61, 0x72, 0x67, 0x73, 0x18,
+	0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x41, 0x72, 0x67,
+	0x73, 0x12, 0x32, 0x0a, 0x15, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x68, 0x69, 0x64,
+	0x64, 0x65, 0x6e, 0x5f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08,
+	0x52, 0x13, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x48, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x42,
+	0x6f, 0x61, 0x72, 0x64, 0x73, 0x22, 0x59, 0x0a, 0x14, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x4c, 0x69,
+	0x73, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a,
+	0x06, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e,
+	0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63,
+	0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6f, 0x61, 0x72, 0x64,
+	0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x06, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73,
+	0x22, 0x59, 0x0a, 0x15, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x61, 0x74,
+	0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x08, 0x69, 0x6e, 0x73,
+	0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x63, 0x63,
+	0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d,
+	0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63,
+	0x65, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x22, 0x8b, 0x01, 0x0a, 0x16,
+	0x42, 0x6f, 0x61, 0x72, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65,
+	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f,
+	0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x76, 0x65, 0x6e,
+	0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3c, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f,
+	0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31,
+	0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x04, 0x70,
+	0x6f, 0x72, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x96, 0x01, 0x0a, 0x0d, 0x42, 0x6f,
+	0x61, 0x72, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x6e,
+	0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
+	0x12, 0x0a, 0x04, 0x66, 0x71, 0x62, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66,
+	0x71, 0x62, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x48, 0x69, 0x64, 0x64, 0x65, 0x6e,
+	0x12, 0x40, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x06, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x24, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e,
 	0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e,
-	0x50, 0x6f, 0x72, 0x74, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x22, 0xac, 0x01, 0x0a, 0x13, 0x42,
-	0x6f, 0x61, 0x72, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x12, 0x40, 0x0a, 0x08, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e,
-	0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76,
-	0x31, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x74,
-	0x61, 0x6e, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x61,
-	0x72, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x61, 0x72, 0x63,
-	0x68, 0x41, 0x72, 0x67, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65,
-	0x5f, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x5f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x48, 0x69, 0x64,
-	0x64, 0x65, 0x6e, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x22, 0x59, 0x0a, 0x14, 0x42, 0x6f, 0x61,
-	0x72, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
-	0x65, 0x12, 0x41, 0x0a, 0x06, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
-	0x0b, 0x32, 0x29, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63,
-	0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42,
-	0x6f, 0x61, 0x72, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x06, 0x62, 0x6f,
-	0x61, 0x72, 0x64, 0x73, 0x22, 0x59, 0x0a, 0x15, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x4c, 0x69, 0x73,
-	0x74, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a,
-	0x08, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x24, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69,
-	0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x73,
-	0x74, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x22,
-	0x8b, 0x01, 0x0a, 0x16, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x61, 0x74,
-	0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x76,
-	0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
-	0x65, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3c, 0x0a, 0x04, 0x70, 0x6f, 0x72,
-	0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64,
-	0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
-	0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x50, 0x6f, 0x72,
-	0x74, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72,
-	0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x96, 0x01,
-	0x0a, 0x0d, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12,
-	0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
-	0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x71, 0x62, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x04, 0x66, 0x71, 0x62, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x68, 0x69,
-	0x64, 0x64, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x48, 0x69,
-	0x64, 0x64, 0x65, 0x6e, 0x12, 0x40, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d,
-	0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75,
-	0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73,
-	0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x08, 0x70, 0x6c,
-	0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x22, 0xab, 0x01, 0x0a, 0x12, 0x42, 0x6f, 0x61, 0x72, 0x64,
-	0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a,
-	0x08, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x24, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69,
-	0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x73,
-	0x74, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12,
-	0x1f, 0x0a, 0x0b, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x61, 0x72, 0x67, 0x73, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x41, 0x72, 0x67, 0x73,
-	0x12, 0x32, 0x0a, 0x15, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x68, 0x69, 0x64, 0x64,
-	0x65, 0x6e, 0x5f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52,
-	0x13, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x48, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x42, 0x6f,
-	0x61, 0x72, 0x64, 0x73, 0x22, 0x58, 0x0a, 0x13, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x53, 0x65, 0x61,
-	0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x06, 0x62,
-	0x6f, 0x61, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x63,
+	0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f,
+	0x72, 0x6d, 0x22, 0xab, 0x01, 0x0a, 0x12, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x53, 0x65, 0x61, 0x72,
+	0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x40, 0x0a, 0x08, 0x69, 0x6e, 0x73,
+	0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x63, 0x63,
 	0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d,
-	0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x4c, 0x69,
-	0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x06, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x42, 0x48,
-	0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x72, 0x64,
-	0x75, 0x69, 0x6e, 0x6f, 0x2f, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2d, 0x63, 0x6c, 0x69,
-	0x2f, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x63, 0x2f, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2f,
-	0x63, 0x6c, 0x69, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2f, 0x76, 0x31, 0x3b,
-	0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63,
+	0x65, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73,
+	0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x61, 0x72, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0a, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x41, 0x72, 0x67, 0x73, 0x12, 0x32, 0x0a, 0x15,
+	0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x5f, 0x62,
+	0x6f, 0x61, 0x72, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x69, 0x6e, 0x63,
+	0x6c, 0x75, 0x64, 0x65, 0x48, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x73,
+	0x22, 0x58, 0x0a, 0x13, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x06, 0x62, 0x6f, 0x61, 0x72, 0x64,
+	0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64,
+	0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
+	0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74,
+	0x65, 0x6d, 0x52, 0x06, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x73, 0x42, 0x48, 0x5a, 0x46, 0x67, 0x69,
+	0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f,
+	0x2f, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2d, 0x63, 0x6c, 0x69, 0x2f, 0x72, 0x70, 0x63,
+	0x2f, 0x63, 0x63, 0x2f, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2f, 0x63, 0x6c, 0x69, 0x2f,
+	0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6f, 0x6d, 0x6d,
+	0x61, 0x6e, 0x64, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/rpc/cc/arduino/cli/commands/v1/board.proto b/rpc/cc/arduino/cli/commands/v1/board.proto
index 8918473ec86..46a48c945f7 100644
--- a/rpc/cc/arduino/cli/commands/v1/board.proto
+++ b/rpc/cc/arduino/cli/commands/v1/board.proto
@@ -166,6 +166,8 @@ message BoardListRequest {
 message BoardListResponse {
   // List of ports and the boards detected on those ports.
   repeated DetectedPort ports = 1;
+  // Warning messages or errors coming from the discoveries.
+  repeated string warnings = 2;
 }
 
 message DetectedPort {
diff --git a/rpc/cc/arduino/cli/commands/v1/commands.pb.go b/rpc/cc/arduino/cli/commands/v1/commands.pb.go
index ca33b869446..859a2bf0900 100644
--- a/rpc/cc/arduino/cli/commands/v1/commands.pb.go
+++ b/rpc/cc/arduino/cli/commands/v1/commands.pb.go
@@ -2076,7 +2076,7 @@ var file_cc_arduino_cli_commands_v1_commands_proto_rawDesc = []byte{
 	0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x34, 0x0a, 0x30, 0x46, 0x41, 0x49, 0x4c, 0x45,
 	0x44, 0x5f, 0x49, 0x4e, 0x53, 0x54, 0x41, 0x4e, 0x43, 0x45, 0x5f, 0x49, 0x4e, 0x49, 0x54, 0x5f,
 	0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x44, 0x4f, 0x57,
-	0x4e, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x32, 0xd1, 0x2f,
+	0x4e, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x32, 0xfb, 0x2f,
 	0x0a, 0x12, 0x41, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x43, 0x6f, 0x72, 0x65, 0x53, 0x65, 0x72,
 	0x76, 0x69, 0x63, 0x65, 0x12, 0x61, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x29,
 	0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e,
@@ -2412,58 +2412,60 @@ var file_cc_arduino_cli_commands_v1_commands_proto_rawDesc = []byte{
 	0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76,
 	0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x43,
 	0x61, 0x63, 0x68, 0x65, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73,
-	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x77, 0x0a, 0x0e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67,
-	0x73, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x12, 0x31, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64,
-	0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
-	0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x47, 0x65, 0x74,
-	0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x63, 0x63, 0x2e,
-	0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
-	0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
-	0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x74,
-	0x0a, 0x0d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x12,
-	0x30, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69,
-	0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74,
-	0x74, 0x69, 0x6e, 0x67, 0x73, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
-	0x74, 0x1a, 0x31, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x11, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x61, 0x76, 0x65, 0x12, 0x34, 0x2e, 0x63, 0x63,
+	0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d,
+	0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75,
+	0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x61, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x1a, 0x35, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63,
+	0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x61, 0x76, 0x65,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x11, 0x43, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x70, 0x65, 0x6e, 0x12, 0x34,
+	0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e,
+	0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66,
+	0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x70, 0x65, 0x6e, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e,
+	0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76,
+	0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f,
+	0x70, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7d, 0x0a, 0x10, 0x43,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x47, 0x65, 0x74, 0x12,
+	0x33, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69,
+	0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e,
+	0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76,
+	0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x47,
+	0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x80, 0x01, 0x0a, 0x11, 0x53,
+	0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x74, 0x65,
+	0x12, 0x34, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c,
+	0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65,
+	0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75,
+	0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73,
+	0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x6e, 0x75, 0x6d,
+	0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7d, 0x0a,
+	0x10, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75,
+	0x65, 0x12, 0x33, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63,
 	0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53,
-	0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70,
-	0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7d, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
-	0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x33, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72,
-	0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
-	0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x47, 0x65,
-	0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e,
-	0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63,
-	0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x69,
-	0x6e, 0x67, 0x73, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
-	0x6e, 0x73, 0x65, 0x12, 0x7d, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x53,
-	0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x33, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64,
-	0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
-	0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x53, 0x65, 0x74,
-	0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x63,
-	0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f,
-	0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e,
-	0x67, 0x73, 0x53, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
-	0x73, 0x65, 0x12, 0x74, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x57, 0x72,
-	0x69, 0x74, 0x65, 0x12, 0x30, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f,
-	0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31,
-	0x2e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69,
+	0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75,
+	0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73,
+	0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x47, 0x65, 0x74, 0x56,
+	0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7d, 0x0a, 0x10,
+	0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x53, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65,
+	0x12, 0x33, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c,
+	0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65,
+	0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x53, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69,
 	0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e,
-	0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x57, 0x72, 0x69, 0x74, 0x65,
-	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x77, 0x0a, 0x0e, 0x53, 0x65, 0x74, 0x74,
-	0x69, 0x6e, 0x67, 0x73, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x31, 0x2e, 0x63, 0x63, 0x2e,
-	0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
-	0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
-	0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e,
-	0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63,
-	0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x69,
-	0x6e, 0x67, 0x73, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
-	0x65, 0x42, 0x48, 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
-	0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2f, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2d,
-	0x63, 0x6c, 0x69, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x63, 0x2f, 0x61, 0x72, 0x64, 0x75, 0x69,
-	0x6e, 0x6f, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2f,
-	0x76, 0x31, 0x3b, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f,
-	0x74, 0x6f, 0x33,
+	0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x53, 0x65, 0x74, 0x56, 0x61,
+	0x6c, 0x75, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x48, 0x5a, 0x46, 0x67,
+	0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e,
+	0x6f, 0x2f, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2d, 0x63, 0x6c, 0x69, 0x2f, 0x72, 0x70,
+	0x63, 0x2f, 0x63, 0x63, 0x2f, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2f, 0x63, 0x6c, 0x69,
+	0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6f, 0x6d,
+	0x6d, 0x61, 0x6e, 0x64, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -2549,12 +2551,12 @@ var file_cc_arduino_cli_commands_v1_commands_proto_goTypes = []interface{}{
 	(*DebugRequest)(nil),                              // 65: cc.arduino.cli.commands.v1.DebugRequest
 	(*IsDebugSupportedRequest)(nil),                   // 66: cc.arduino.cli.commands.v1.IsDebugSupportedRequest
 	(*GetDebugConfigRequest)(nil),                     // 67: cc.arduino.cli.commands.v1.GetDebugConfigRequest
-	(*SettingsGetAllRequest)(nil),                     // 68: cc.arduino.cli.commands.v1.SettingsGetAllRequest
-	(*SettingsMergeRequest)(nil),                      // 69: cc.arduino.cli.commands.v1.SettingsMergeRequest
-	(*SettingsGetValueRequest)(nil),                   // 70: cc.arduino.cli.commands.v1.SettingsGetValueRequest
-	(*SettingsSetValueRequest)(nil),                   // 71: cc.arduino.cli.commands.v1.SettingsSetValueRequest
-	(*SettingsWriteRequest)(nil),                      // 72: cc.arduino.cli.commands.v1.SettingsWriteRequest
-	(*SettingsDeleteRequest)(nil),                     // 73: cc.arduino.cli.commands.v1.SettingsDeleteRequest
+	(*ConfigurationSaveRequest)(nil),                  // 68: cc.arduino.cli.commands.v1.ConfigurationSaveRequest
+	(*ConfigurationOpenRequest)(nil),                  // 69: cc.arduino.cli.commands.v1.ConfigurationOpenRequest
+	(*ConfigurationGetRequest)(nil),                   // 70: cc.arduino.cli.commands.v1.ConfigurationGetRequest
+	(*SettingsEnumerateRequest)(nil),                  // 71: cc.arduino.cli.commands.v1.SettingsEnumerateRequest
+	(*SettingsGetValueRequest)(nil),                   // 72: cc.arduino.cli.commands.v1.SettingsGetValueRequest
+	(*SettingsSetValueRequest)(nil),                   // 73: cc.arduino.cli.commands.v1.SettingsSetValueRequest
 	(*BoardDetailsResponse)(nil),                      // 74: cc.arduino.cli.commands.v1.BoardDetailsResponse
 	(*BoardListResponse)(nil),                         // 75: cc.arduino.cli.commands.v1.BoardListResponse
 	(*BoardListAllResponse)(nil),                      // 76: cc.arduino.cli.commands.v1.BoardListAllResponse
@@ -2586,12 +2588,12 @@ var file_cc_arduino_cli_commands_v1_commands_proto_goTypes = []interface{}{
 	(*DebugResponse)(nil),                             // 102: cc.arduino.cli.commands.v1.DebugResponse
 	(*IsDebugSupportedResponse)(nil),                  // 103: cc.arduino.cli.commands.v1.IsDebugSupportedResponse
 	(*GetDebugConfigResponse)(nil),                    // 104: cc.arduino.cli.commands.v1.GetDebugConfigResponse
-	(*SettingsGetAllResponse)(nil),                    // 105: cc.arduino.cli.commands.v1.SettingsGetAllResponse
-	(*SettingsMergeResponse)(nil),                     // 106: cc.arduino.cli.commands.v1.SettingsMergeResponse
-	(*SettingsGetValueResponse)(nil),                  // 107: cc.arduino.cli.commands.v1.SettingsGetValueResponse
-	(*SettingsSetValueResponse)(nil),                  // 108: cc.arduino.cli.commands.v1.SettingsSetValueResponse
-	(*SettingsWriteResponse)(nil),                     // 109: cc.arduino.cli.commands.v1.SettingsWriteResponse
-	(*SettingsDeleteResponse)(nil),                    // 110: cc.arduino.cli.commands.v1.SettingsDeleteResponse
+	(*ConfigurationSaveResponse)(nil),                 // 105: cc.arduino.cli.commands.v1.ConfigurationSaveResponse
+	(*ConfigurationOpenResponse)(nil),                 // 106: cc.arduino.cli.commands.v1.ConfigurationOpenResponse
+	(*ConfigurationGetResponse)(nil),                  // 107: cc.arduino.cli.commands.v1.ConfigurationGetResponse
+	(*SettingsEnumerateResponse)(nil),                 // 108: cc.arduino.cli.commands.v1.SettingsEnumerateResponse
+	(*SettingsGetValueResponse)(nil),                  // 109: cc.arduino.cli.commands.v1.SettingsGetValueResponse
+	(*SettingsSetValueResponse)(nil),                  // 110: cc.arduino.cli.commands.v1.SettingsSetValueResponse
 }
 var file_cc_arduino_cli_commands_v1_commands_proto_depIdxs = []int32{
 	31,  // 0: cc.arduino.cli.commands.v1.CreateResponse.instance:type_name -> cc.arduino.cli.commands.v1.Instance
@@ -2657,12 +2659,12 @@ var file_cc_arduino_cli_commands_v1_commands_proto_depIdxs = []int32{
 	67,  // 60: cc.arduino.cli.commands.v1.ArduinoCoreService.GetDebugConfig:input_type -> cc.arduino.cli.commands.v1.GetDebugConfigRequest
 	24,  // 61: cc.arduino.cli.commands.v1.ArduinoCoreService.CheckForArduinoCLIUpdates:input_type -> cc.arduino.cli.commands.v1.CheckForArduinoCLIUpdatesRequest
 	26,  // 62: cc.arduino.cli.commands.v1.ArduinoCoreService.CleanDownloadCacheDirectory:input_type -> cc.arduino.cli.commands.v1.CleanDownloadCacheDirectoryRequest
-	68,  // 63: cc.arduino.cli.commands.v1.ArduinoCoreService.SettingsGetAll:input_type -> cc.arduino.cli.commands.v1.SettingsGetAllRequest
-	69,  // 64: cc.arduino.cli.commands.v1.ArduinoCoreService.SettingsMerge:input_type -> cc.arduino.cli.commands.v1.SettingsMergeRequest
-	70,  // 65: cc.arduino.cli.commands.v1.ArduinoCoreService.SettingsGetValue:input_type -> cc.arduino.cli.commands.v1.SettingsGetValueRequest
-	71,  // 66: cc.arduino.cli.commands.v1.ArduinoCoreService.SettingsSetValue:input_type -> cc.arduino.cli.commands.v1.SettingsSetValueRequest
-	72,  // 67: cc.arduino.cli.commands.v1.ArduinoCoreService.SettingsWrite:input_type -> cc.arduino.cli.commands.v1.SettingsWriteRequest
-	73,  // 68: cc.arduino.cli.commands.v1.ArduinoCoreService.SettingsDelete:input_type -> cc.arduino.cli.commands.v1.SettingsDeleteRequest
+	68,  // 63: cc.arduino.cli.commands.v1.ArduinoCoreService.ConfigurationSave:input_type -> cc.arduino.cli.commands.v1.ConfigurationSaveRequest
+	69,  // 64: cc.arduino.cli.commands.v1.ArduinoCoreService.ConfigurationOpen:input_type -> cc.arduino.cli.commands.v1.ConfigurationOpenRequest
+	70,  // 65: cc.arduino.cli.commands.v1.ArduinoCoreService.ConfigurationGet:input_type -> cc.arduino.cli.commands.v1.ConfigurationGetRequest
+	71,  // 66: cc.arduino.cli.commands.v1.ArduinoCoreService.SettingsEnumerate:input_type -> cc.arduino.cli.commands.v1.SettingsEnumerateRequest
+	72,  // 67: cc.arduino.cli.commands.v1.ArduinoCoreService.SettingsGetValue:input_type -> cc.arduino.cli.commands.v1.SettingsGetValueRequest
+	73,  // 68: cc.arduino.cli.commands.v1.ArduinoCoreService.SettingsSetValue:input_type -> cc.arduino.cli.commands.v1.SettingsSetValueRequest
 	3,   // 69: cc.arduino.cli.commands.v1.ArduinoCoreService.Create:output_type -> cc.arduino.cli.commands.v1.CreateResponse
 	5,   // 70: cc.arduino.cli.commands.v1.ArduinoCoreService.Init:output_type -> cc.arduino.cli.commands.v1.InitResponse
 	8,   // 71: cc.arduino.cli.commands.v1.ArduinoCoreService.Destroy:output_type -> cc.arduino.cli.commands.v1.DestroyResponse
@@ -2706,12 +2708,12 @@ var file_cc_arduino_cli_commands_v1_commands_proto_depIdxs = []int32{
 	104, // 109: cc.arduino.cli.commands.v1.ArduinoCoreService.GetDebugConfig:output_type -> cc.arduino.cli.commands.v1.GetDebugConfigResponse
 	25,  // 110: cc.arduino.cli.commands.v1.ArduinoCoreService.CheckForArduinoCLIUpdates:output_type -> cc.arduino.cli.commands.v1.CheckForArduinoCLIUpdatesResponse
 	27,  // 111: cc.arduino.cli.commands.v1.ArduinoCoreService.CleanDownloadCacheDirectory:output_type -> cc.arduino.cli.commands.v1.CleanDownloadCacheDirectoryResponse
-	105, // 112: cc.arduino.cli.commands.v1.ArduinoCoreService.SettingsGetAll:output_type -> cc.arduino.cli.commands.v1.SettingsGetAllResponse
-	106, // 113: cc.arduino.cli.commands.v1.ArduinoCoreService.SettingsMerge:output_type -> cc.arduino.cli.commands.v1.SettingsMergeResponse
-	107, // 114: cc.arduino.cli.commands.v1.ArduinoCoreService.SettingsGetValue:output_type -> cc.arduino.cli.commands.v1.SettingsGetValueResponse
-	108, // 115: cc.arduino.cli.commands.v1.ArduinoCoreService.SettingsSetValue:output_type -> cc.arduino.cli.commands.v1.SettingsSetValueResponse
-	109, // 116: cc.arduino.cli.commands.v1.ArduinoCoreService.SettingsWrite:output_type -> cc.arduino.cli.commands.v1.SettingsWriteResponse
-	110, // 117: cc.arduino.cli.commands.v1.ArduinoCoreService.SettingsDelete:output_type -> cc.arduino.cli.commands.v1.SettingsDeleteResponse
+	105, // 112: cc.arduino.cli.commands.v1.ArduinoCoreService.ConfigurationSave:output_type -> cc.arduino.cli.commands.v1.ConfigurationSaveResponse
+	106, // 113: cc.arduino.cli.commands.v1.ArduinoCoreService.ConfigurationOpen:output_type -> cc.arduino.cli.commands.v1.ConfigurationOpenResponse
+	107, // 114: cc.arduino.cli.commands.v1.ArduinoCoreService.ConfigurationGet:output_type -> cc.arduino.cli.commands.v1.ConfigurationGetResponse
+	108, // 115: cc.arduino.cli.commands.v1.ArduinoCoreService.SettingsEnumerate:output_type -> cc.arduino.cli.commands.v1.SettingsEnumerateResponse
+	109, // 116: cc.arduino.cli.commands.v1.ArduinoCoreService.SettingsGetValue:output_type -> cc.arduino.cli.commands.v1.SettingsGetValueResponse
+	110, // 117: cc.arduino.cli.commands.v1.ArduinoCoreService.SettingsSetValue:output_type -> cc.arduino.cli.commands.v1.SettingsSetValueResponse
 	69,  // [69:118] is the sub-list for method output_type
 	20,  // [20:69] is the sub-list for method input_type
 	20,  // [20:20] is the sub-list for extension type_name
diff --git a/rpc/cc/arduino/cli/commands/v1/commands.proto b/rpc/cc/arduino/cli/commands/v1/commands.proto
index 78660e28d5c..ce5aba9ed91 100644
--- a/rpc/cc/arduino/cli/commands/v1/commands.proto
+++ b/rpc/cc/arduino/cli/commands/v1/commands.proto
@@ -197,25 +197,28 @@ service ArduinoCoreService {
   rpc CleanDownloadCacheDirectory(CleanDownloadCacheDirectoryRequest)
       returns (CleanDownloadCacheDirectoryResponse);
 
-  // List all the settings.
-  rpc SettingsGetAll(SettingsGetAllRequest) returns (SettingsGetAllResponse);
+  // Writes the settings currently stored in memory in a YAML file
+  rpc ConfigurationSave(ConfigurationSaveRequest)
+      returns (ConfigurationSaveResponse);
 
-  // Set multiple settings values at once.
-  rpc SettingsMerge(SettingsMergeRequest) returns (SettingsMergeResponse);
+  // Read the settings from a YAML file
+  rpc ConfigurationOpen(ConfigurationOpenRequest)
+      returns (ConfigurationOpenResponse);
 
-  // Get the value of a specific setting.
+  rpc ConfigurationGet(ConfigurationGetRequest)
+      returns (ConfigurationGetResponse);
+
+  // Enumerate all the keys/values pairs available in the configuration
+  rpc SettingsEnumerate(SettingsEnumerateRequest)
+      returns (SettingsEnumerateResponse);
+
+  // Get a single configuration value
   rpc SettingsGetValue(SettingsGetValueRequest)
       returns (SettingsGetValueResponse);
 
-  // Set the value of a specific setting.
+  // Set a single configuration value
   rpc SettingsSetValue(SettingsSetValueRequest)
       returns (SettingsSetValueResponse);
-
-  // Writes to file settings currently stored in memory
-  rpc SettingsWrite(SettingsWriteRequest) returns (SettingsWriteResponse);
-
-  // Deletes an entry and rewrites the file settings
-  rpc SettingsDelete(SettingsDeleteRequest) returns (SettingsDeleteResponse);
 }
 
 message CreateRequest {}
diff --git a/rpc/cc/arduino/cli/commands/v1/commands_grpc.pb.go b/rpc/cc/arduino/cli/commands/v1/commands_grpc.pb.go
index 5021e89d488..4ebd93a59f0 100644
--- a/rpc/cc/arduino/cli/commands/v1/commands_grpc.pb.go
+++ b/rpc/cc/arduino/cli/commands/v1/commands_grpc.pb.go
@@ -77,12 +77,12 @@ const (
 	ArduinoCoreService_GetDebugConfig_FullMethodName                    = "/cc.arduino.cli.commands.v1.ArduinoCoreService/GetDebugConfig"
 	ArduinoCoreService_CheckForArduinoCLIUpdates_FullMethodName         = "/cc.arduino.cli.commands.v1.ArduinoCoreService/CheckForArduinoCLIUpdates"
 	ArduinoCoreService_CleanDownloadCacheDirectory_FullMethodName       = "/cc.arduino.cli.commands.v1.ArduinoCoreService/CleanDownloadCacheDirectory"
-	ArduinoCoreService_SettingsGetAll_FullMethodName                    = "/cc.arduino.cli.commands.v1.ArduinoCoreService/SettingsGetAll"
-	ArduinoCoreService_SettingsMerge_FullMethodName                     = "/cc.arduino.cli.commands.v1.ArduinoCoreService/SettingsMerge"
+	ArduinoCoreService_ConfigurationSave_FullMethodName                 = "/cc.arduino.cli.commands.v1.ArduinoCoreService/ConfigurationSave"
+	ArduinoCoreService_ConfigurationOpen_FullMethodName                 = "/cc.arduino.cli.commands.v1.ArduinoCoreService/ConfigurationOpen"
+	ArduinoCoreService_ConfigurationGet_FullMethodName                  = "/cc.arduino.cli.commands.v1.ArduinoCoreService/ConfigurationGet"
+	ArduinoCoreService_SettingsEnumerate_FullMethodName                 = "/cc.arduino.cli.commands.v1.ArduinoCoreService/SettingsEnumerate"
 	ArduinoCoreService_SettingsGetValue_FullMethodName                  = "/cc.arduino.cli.commands.v1.ArduinoCoreService/SettingsGetValue"
 	ArduinoCoreService_SettingsSetValue_FullMethodName                  = "/cc.arduino.cli.commands.v1.ArduinoCoreService/SettingsSetValue"
-	ArduinoCoreService_SettingsWrite_FullMethodName                     = "/cc.arduino.cli.commands.v1.ArduinoCoreService/SettingsWrite"
-	ArduinoCoreService_SettingsDelete_FullMethodName                    = "/cc.arduino.cli.commands.v1.ArduinoCoreService/SettingsDelete"
 )
 
 // ArduinoCoreServiceClient is the client API for ArduinoCoreService service.
@@ -183,18 +183,17 @@ type ArduinoCoreServiceClient interface {
 	CheckForArduinoCLIUpdates(ctx context.Context, in *CheckForArduinoCLIUpdatesRequest, opts ...grpc.CallOption) (*CheckForArduinoCLIUpdatesResponse, error)
 	// Clean the download cache directory (where archives are downloaded).
 	CleanDownloadCacheDirectory(ctx context.Context, in *CleanDownloadCacheDirectoryRequest, opts ...grpc.CallOption) (*CleanDownloadCacheDirectoryResponse, error)
-	// List all the settings.
-	SettingsGetAll(ctx context.Context, in *SettingsGetAllRequest, opts ...grpc.CallOption) (*SettingsGetAllResponse, error)
-	// Set multiple settings values at once.
-	SettingsMerge(ctx context.Context, in *SettingsMergeRequest, opts ...grpc.CallOption) (*SettingsMergeResponse, error)
-	// Get the value of a specific setting.
+	// Writes the settings currently stored in memory in a YAML file
+	ConfigurationSave(ctx context.Context, in *ConfigurationSaveRequest, opts ...grpc.CallOption) (*ConfigurationSaveResponse, error)
+	// Read the settings from a YAML file
+	ConfigurationOpen(ctx context.Context, in *ConfigurationOpenRequest, opts ...grpc.CallOption) (*ConfigurationOpenResponse, error)
+	ConfigurationGet(ctx context.Context, in *ConfigurationGetRequest, opts ...grpc.CallOption) (*ConfigurationGetResponse, error)
+	// Enumerate all the keys/values pairs available in the configuration
+	SettingsEnumerate(ctx context.Context, in *SettingsEnumerateRequest, opts ...grpc.CallOption) (*SettingsEnumerateResponse, error)
+	// Get a single configuration value
 	SettingsGetValue(ctx context.Context, in *SettingsGetValueRequest, opts ...grpc.CallOption) (*SettingsGetValueResponse, error)
-	// Set the value of a specific setting.
+	// Set a single configuration value
 	SettingsSetValue(ctx context.Context, in *SettingsSetValueRequest, opts ...grpc.CallOption) (*SettingsSetValueResponse, error)
-	// Writes to file settings currently stored in memory
-	SettingsWrite(ctx context.Context, in *SettingsWriteRequest, opts ...grpc.CallOption) (*SettingsWriteResponse, error)
-	// Deletes an entry and rewrites the file settings
-	SettingsDelete(ctx context.Context, in *SettingsDeleteRequest, opts ...grpc.CallOption) (*SettingsDeleteResponse, error)
 }
 
 type arduinoCoreServiceClient struct {
@@ -1073,54 +1072,54 @@ func (c *arduinoCoreServiceClient) CleanDownloadCacheDirectory(ctx context.Conte
 	return out, nil
 }
 
-func (c *arduinoCoreServiceClient) SettingsGetAll(ctx context.Context, in *SettingsGetAllRequest, opts ...grpc.CallOption) (*SettingsGetAllResponse, error) {
-	out := new(SettingsGetAllResponse)
-	err := c.cc.Invoke(ctx, ArduinoCoreService_SettingsGetAll_FullMethodName, in, out, opts...)
+func (c *arduinoCoreServiceClient) ConfigurationSave(ctx context.Context, in *ConfigurationSaveRequest, opts ...grpc.CallOption) (*ConfigurationSaveResponse, error) {
+	out := new(ConfigurationSaveResponse)
+	err := c.cc.Invoke(ctx, ArduinoCoreService_ConfigurationSave_FullMethodName, in, out, opts...)
 	if err != nil {
 		return nil, err
 	}
 	return out, nil
 }
 
-func (c *arduinoCoreServiceClient) SettingsMerge(ctx context.Context, in *SettingsMergeRequest, opts ...grpc.CallOption) (*SettingsMergeResponse, error) {
-	out := new(SettingsMergeResponse)
-	err := c.cc.Invoke(ctx, ArduinoCoreService_SettingsMerge_FullMethodName, in, out, opts...)
+func (c *arduinoCoreServiceClient) ConfigurationOpen(ctx context.Context, in *ConfigurationOpenRequest, opts ...grpc.CallOption) (*ConfigurationOpenResponse, error) {
+	out := new(ConfigurationOpenResponse)
+	err := c.cc.Invoke(ctx, ArduinoCoreService_ConfigurationOpen_FullMethodName, in, out, opts...)
 	if err != nil {
 		return nil, err
 	}
 	return out, nil
 }
 
-func (c *arduinoCoreServiceClient) SettingsGetValue(ctx context.Context, in *SettingsGetValueRequest, opts ...grpc.CallOption) (*SettingsGetValueResponse, error) {
-	out := new(SettingsGetValueResponse)
-	err := c.cc.Invoke(ctx, ArduinoCoreService_SettingsGetValue_FullMethodName, in, out, opts...)
+func (c *arduinoCoreServiceClient) ConfigurationGet(ctx context.Context, in *ConfigurationGetRequest, opts ...grpc.CallOption) (*ConfigurationGetResponse, error) {
+	out := new(ConfigurationGetResponse)
+	err := c.cc.Invoke(ctx, ArduinoCoreService_ConfigurationGet_FullMethodName, in, out, opts...)
 	if err != nil {
 		return nil, err
 	}
 	return out, nil
 }
 
-func (c *arduinoCoreServiceClient) SettingsSetValue(ctx context.Context, in *SettingsSetValueRequest, opts ...grpc.CallOption) (*SettingsSetValueResponse, error) {
-	out := new(SettingsSetValueResponse)
-	err := c.cc.Invoke(ctx, ArduinoCoreService_SettingsSetValue_FullMethodName, in, out, opts...)
+func (c *arduinoCoreServiceClient) SettingsEnumerate(ctx context.Context, in *SettingsEnumerateRequest, opts ...grpc.CallOption) (*SettingsEnumerateResponse, error) {
+	out := new(SettingsEnumerateResponse)
+	err := c.cc.Invoke(ctx, ArduinoCoreService_SettingsEnumerate_FullMethodName, in, out, opts...)
 	if err != nil {
 		return nil, err
 	}
 	return out, nil
 }
 
-func (c *arduinoCoreServiceClient) SettingsWrite(ctx context.Context, in *SettingsWriteRequest, opts ...grpc.CallOption) (*SettingsWriteResponse, error) {
-	out := new(SettingsWriteResponse)
-	err := c.cc.Invoke(ctx, ArduinoCoreService_SettingsWrite_FullMethodName, in, out, opts...)
+func (c *arduinoCoreServiceClient) SettingsGetValue(ctx context.Context, in *SettingsGetValueRequest, opts ...grpc.CallOption) (*SettingsGetValueResponse, error) {
+	out := new(SettingsGetValueResponse)
+	err := c.cc.Invoke(ctx, ArduinoCoreService_SettingsGetValue_FullMethodName, in, out, opts...)
 	if err != nil {
 		return nil, err
 	}
 	return out, nil
 }
 
-func (c *arduinoCoreServiceClient) SettingsDelete(ctx context.Context, in *SettingsDeleteRequest, opts ...grpc.CallOption) (*SettingsDeleteResponse, error) {
-	out := new(SettingsDeleteResponse)
-	err := c.cc.Invoke(ctx, ArduinoCoreService_SettingsDelete_FullMethodName, in, out, opts...)
+func (c *arduinoCoreServiceClient) SettingsSetValue(ctx context.Context, in *SettingsSetValueRequest, opts ...grpc.CallOption) (*SettingsSetValueResponse, error) {
+	out := new(SettingsSetValueResponse)
+	err := c.cc.Invoke(ctx, ArduinoCoreService_SettingsSetValue_FullMethodName, in, out, opts...)
 	if err != nil {
 		return nil, err
 	}
@@ -1225,18 +1224,17 @@ type ArduinoCoreServiceServer interface {
 	CheckForArduinoCLIUpdates(context.Context, *CheckForArduinoCLIUpdatesRequest) (*CheckForArduinoCLIUpdatesResponse, error)
 	// Clean the download cache directory (where archives are downloaded).
 	CleanDownloadCacheDirectory(context.Context, *CleanDownloadCacheDirectoryRequest) (*CleanDownloadCacheDirectoryResponse, error)
-	// List all the settings.
-	SettingsGetAll(context.Context, *SettingsGetAllRequest) (*SettingsGetAllResponse, error)
-	// Set multiple settings values at once.
-	SettingsMerge(context.Context, *SettingsMergeRequest) (*SettingsMergeResponse, error)
-	// Get the value of a specific setting.
+	// Writes the settings currently stored in memory in a YAML file
+	ConfigurationSave(context.Context, *ConfigurationSaveRequest) (*ConfigurationSaveResponse, error)
+	// Read the settings from a YAML file
+	ConfigurationOpen(context.Context, *ConfigurationOpenRequest) (*ConfigurationOpenResponse, error)
+	ConfigurationGet(context.Context, *ConfigurationGetRequest) (*ConfigurationGetResponse, error)
+	// Enumerate all the keys/values pairs available in the configuration
+	SettingsEnumerate(context.Context, *SettingsEnumerateRequest) (*SettingsEnumerateResponse, error)
+	// Get a single configuration value
 	SettingsGetValue(context.Context, *SettingsGetValueRequest) (*SettingsGetValueResponse, error)
-	// Set the value of a specific setting.
+	// Set a single configuration value
 	SettingsSetValue(context.Context, *SettingsSetValueRequest) (*SettingsSetValueResponse, error)
-	// Writes to file settings currently stored in memory
-	SettingsWrite(context.Context, *SettingsWriteRequest) (*SettingsWriteResponse, error)
-	// Deletes an entry and rewrites the file settings
-	SettingsDelete(context.Context, *SettingsDeleteRequest) (*SettingsDeleteResponse, error)
 	mustEmbedUnimplementedArduinoCoreServiceServer()
 }
 
@@ -1373,11 +1371,17 @@ func (UnimplementedArduinoCoreServiceServer) CheckForArduinoCLIUpdates(context.C
 func (UnimplementedArduinoCoreServiceServer) CleanDownloadCacheDirectory(context.Context, *CleanDownloadCacheDirectoryRequest) (*CleanDownloadCacheDirectoryResponse, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method CleanDownloadCacheDirectory not implemented")
 }
-func (UnimplementedArduinoCoreServiceServer) SettingsGetAll(context.Context, *SettingsGetAllRequest) (*SettingsGetAllResponse, error) {
-	return nil, status.Errorf(codes.Unimplemented, "method SettingsGetAll not implemented")
+func (UnimplementedArduinoCoreServiceServer) ConfigurationSave(context.Context, *ConfigurationSaveRequest) (*ConfigurationSaveResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method ConfigurationSave not implemented")
+}
+func (UnimplementedArduinoCoreServiceServer) ConfigurationOpen(context.Context, *ConfigurationOpenRequest) (*ConfigurationOpenResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method ConfigurationOpen not implemented")
+}
+func (UnimplementedArduinoCoreServiceServer) ConfigurationGet(context.Context, *ConfigurationGetRequest) (*ConfigurationGetResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method ConfigurationGet not implemented")
 }
-func (UnimplementedArduinoCoreServiceServer) SettingsMerge(context.Context, *SettingsMergeRequest) (*SettingsMergeResponse, error) {
-	return nil, status.Errorf(codes.Unimplemented, "method SettingsMerge not implemented")
+func (UnimplementedArduinoCoreServiceServer) SettingsEnumerate(context.Context, *SettingsEnumerateRequest) (*SettingsEnumerateResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method SettingsEnumerate not implemented")
 }
 func (UnimplementedArduinoCoreServiceServer) SettingsGetValue(context.Context, *SettingsGetValueRequest) (*SettingsGetValueResponse, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method SettingsGetValue not implemented")
@@ -1385,12 +1389,6 @@ func (UnimplementedArduinoCoreServiceServer) SettingsGetValue(context.Context, *
 func (UnimplementedArduinoCoreServiceServer) SettingsSetValue(context.Context, *SettingsSetValueRequest) (*SettingsSetValueResponse, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method SettingsSetValue not implemented")
 }
-func (UnimplementedArduinoCoreServiceServer) SettingsWrite(context.Context, *SettingsWriteRequest) (*SettingsWriteResponse, error) {
-	return nil, status.Errorf(codes.Unimplemented, "method SettingsWrite not implemented")
-}
-func (UnimplementedArduinoCoreServiceServer) SettingsDelete(context.Context, *SettingsDeleteRequest) (*SettingsDeleteResponse, error) {
-	return nil, status.Errorf(codes.Unimplemented, "method SettingsDelete not implemented")
-}
 func (UnimplementedArduinoCoreServiceServer) mustEmbedUnimplementedArduinoCoreServiceServer() {}
 
 // UnsafeArduinoCoreServiceServer may be embedded to opt out of forward compatibility for this service.
@@ -2251,110 +2249,110 @@ func _ArduinoCoreService_CleanDownloadCacheDirectory_Handler(srv interface{}, ct
 	return interceptor(ctx, in, info, handler)
 }
 
-func _ArduinoCoreService_SettingsGetAll_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
-	in := new(SettingsGetAllRequest)
+func _ArduinoCoreService_ConfigurationSave_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(ConfigurationSaveRequest)
 	if err := dec(in); err != nil {
 		return nil, err
 	}
 	if interceptor == nil {
-		return srv.(ArduinoCoreServiceServer).SettingsGetAll(ctx, in)
+		return srv.(ArduinoCoreServiceServer).ConfigurationSave(ctx, in)
 	}
 	info := &grpc.UnaryServerInfo{
 		Server:     srv,
-		FullMethod: ArduinoCoreService_SettingsGetAll_FullMethodName,
+		FullMethod: ArduinoCoreService_ConfigurationSave_FullMethodName,
 	}
 	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
-		return srv.(ArduinoCoreServiceServer).SettingsGetAll(ctx, req.(*SettingsGetAllRequest))
+		return srv.(ArduinoCoreServiceServer).ConfigurationSave(ctx, req.(*ConfigurationSaveRequest))
 	}
 	return interceptor(ctx, in, info, handler)
 }
 
-func _ArduinoCoreService_SettingsMerge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
-	in := new(SettingsMergeRequest)
+func _ArduinoCoreService_ConfigurationOpen_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(ConfigurationOpenRequest)
 	if err := dec(in); err != nil {
 		return nil, err
 	}
 	if interceptor == nil {
-		return srv.(ArduinoCoreServiceServer).SettingsMerge(ctx, in)
+		return srv.(ArduinoCoreServiceServer).ConfigurationOpen(ctx, in)
 	}
 	info := &grpc.UnaryServerInfo{
 		Server:     srv,
-		FullMethod: ArduinoCoreService_SettingsMerge_FullMethodName,
+		FullMethod: ArduinoCoreService_ConfigurationOpen_FullMethodName,
 	}
 	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
-		return srv.(ArduinoCoreServiceServer).SettingsMerge(ctx, req.(*SettingsMergeRequest))
+		return srv.(ArduinoCoreServiceServer).ConfigurationOpen(ctx, req.(*ConfigurationOpenRequest))
 	}
 	return interceptor(ctx, in, info, handler)
 }
 
-func _ArduinoCoreService_SettingsGetValue_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
-	in := new(SettingsGetValueRequest)
+func _ArduinoCoreService_ConfigurationGet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(ConfigurationGetRequest)
 	if err := dec(in); err != nil {
 		return nil, err
 	}
 	if interceptor == nil {
-		return srv.(ArduinoCoreServiceServer).SettingsGetValue(ctx, in)
+		return srv.(ArduinoCoreServiceServer).ConfigurationGet(ctx, in)
 	}
 	info := &grpc.UnaryServerInfo{
 		Server:     srv,
-		FullMethod: ArduinoCoreService_SettingsGetValue_FullMethodName,
+		FullMethod: ArduinoCoreService_ConfigurationGet_FullMethodName,
 	}
 	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
-		return srv.(ArduinoCoreServiceServer).SettingsGetValue(ctx, req.(*SettingsGetValueRequest))
+		return srv.(ArduinoCoreServiceServer).ConfigurationGet(ctx, req.(*ConfigurationGetRequest))
 	}
 	return interceptor(ctx, in, info, handler)
 }
 
-func _ArduinoCoreService_SettingsSetValue_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
-	in := new(SettingsSetValueRequest)
+func _ArduinoCoreService_SettingsEnumerate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(SettingsEnumerateRequest)
 	if err := dec(in); err != nil {
 		return nil, err
 	}
 	if interceptor == nil {
-		return srv.(ArduinoCoreServiceServer).SettingsSetValue(ctx, in)
+		return srv.(ArduinoCoreServiceServer).SettingsEnumerate(ctx, in)
 	}
 	info := &grpc.UnaryServerInfo{
 		Server:     srv,
-		FullMethod: ArduinoCoreService_SettingsSetValue_FullMethodName,
+		FullMethod: ArduinoCoreService_SettingsEnumerate_FullMethodName,
 	}
 	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
-		return srv.(ArduinoCoreServiceServer).SettingsSetValue(ctx, req.(*SettingsSetValueRequest))
+		return srv.(ArduinoCoreServiceServer).SettingsEnumerate(ctx, req.(*SettingsEnumerateRequest))
 	}
 	return interceptor(ctx, in, info, handler)
 }
 
-func _ArduinoCoreService_SettingsWrite_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
-	in := new(SettingsWriteRequest)
+func _ArduinoCoreService_SettingsGetValue_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(SettingsGetValueRequest)
 	if err := dec(in); err != nil {
 		return nil, err
 	}
 	if interceptor == nil {
-		return srv.(ArduinoCoreServiceServer).SettingsWrite(ctx, in)
+		return srv.(ArduinoCoreServiceServer).SettingsGetValue(ctx, in)
 	}
 	info := &grpc.UnaryServerInfo{
 		Server:     srv,
-		FullMethod: ArduinoCoreService_SettingsWrite_FullMethodName,
+		FullMethod: ArduinoCoreService_SettingsGetValue_FullMethodName,
 	}
 	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
-		return srv.(ArduinoCoreServiceServer).SettingsWrite(ctx, req.(*SettingsWriteRequest))
+		return srv.(ArduinoCoreServiceServer).SettingsGetValue(ctx, req.(*SettingsGetValueRequest))
 	}
 	return interceptor(ctx, in, info, handler)
 }
 
-func _ArduinoCoreService_SettingsDelete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
-	in := new(SettingsDeleteRequest)
+func _ArduinoCoreService_SettingsSetValue_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(SettingsSetValueRequest)
 	if err := dec(in); err != nil {
 		return nil, err
 	}
 	if interceptor == nil {
-		return srv.(ArduinoCoreServiceServer).SettingsDelete(ctx, in)
+		return srv.(ArduinoCoreServiceServer).SettingsSetValue(ctx, in)
 	}
 	info := &grpc.UnaryServerInfo{
 		Server:     srv,
-		FullMethod: ArduinoCoreService_SettingsDelete_FullMethodName,
+		FullMethod: ArduinoCoreService_SettingsSetValue_FullMethodName,
 	}
 	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
-		return srv.(ArduinoCoreServiceServer).SettingsDelete(ctx, req.(*SettingsDeleteRequest))
+		return srv.(ArduinoCoreServiceServer).SettingsSetValue(ctx, req.(*SettingsSetValueRequest))
 	}
 	return interceptor(ctx, in, info, handler)
 }
@@ -2455,28 +2453,28 @@ var ArduinoCoreService_ServiceDesc = grpc.ServiceDesc{
 			Handler:    _ArduinoCoreService_CleanDownloadCacheDirectory_Handler,
 		},
 		{
-			MethodName: "SettingsGetAll",
-			Handler:    _ArduinoCoreService_SettingsGetAll_Handler,
+			MethodName: "ConfigurationSave",
+			Handler:    _ArduinoCoreService_ConfigurationSave_Handler,
 		},
 		{
-			MethodName: "SettingsMerge",
-			Handler:    _ArduinoCoreService_SettingsMerge_Handler,
+			MethodName: "ConfigurationOpen",
+			Handler:    _ArduinoCoreService_ConfigurationOpen_Handler,
 		},
 		{
-			MethodName: "SettingsGetValue",
-			Handler:    _ArduinoCoreService_SettingsGetValue_Handler,
+			MethodName: "ConfigurationGet",
+			Handler:    _ArduinoCoreService_ConfigurationGet_Handler,
 		},
 		{
-			MethodName: "SettingsSetValue",
-			Handler:    _ArduinoCoreService_SettingsSetValue_Handler,
+			MethodName: "SettingsEnumerate",
+			Handler:    _ArduinoCoreService_SettingsEnumerate_Handler,
 		},
 		{
-			MethodName: "SettingsWrite",
-			Handler:    _ArduinoCoreService_SettingsWrite_Handler,
+			MethodName: "SettingsGetValue",
+			Handler:    _ArduinoCoreService_SettingsGetValue_Handler,
 		},
 		{
-			MethodName: "SettingsDelete",
-			Handler:    _ArduinoCoreService_SettingsDelete_Handler,
+			MethodName: "SettingsSetValue",
+			Handler:    _ArduinoCoreService_SettingsSetValue_Handler,
 		},
 	},
 	Streams: []grpc.StreamDesc{
diff --git a/rpc/cc/arduino/cli/commands/v1/settings.pb.go b/rpc/cc/arduino/cli/commands/v1/settings.pb.go
index 8a594bbd49f..e367998c9ab 100644
--- a/rpc/cc/arduino/cli/commands/v1/settings.pb.go
+++ b/rpc/cc/arduino/cli/commands/v1/settings.pb.go
@@ -35,17 +35,28 @@ const (
 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
 )
 
-type SettingsGetAllResponse struct {
+// Configuration to apply to the given instance.
+// Any missing field will be kept at the default value.
+type Configuration struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	// The settings, in JSON format.
-	JsonData string `protobuf:"bytes,1,opt,name=json_data,json=jsonData,proto3" json:"json_data,omitempty"`
-}
-
-func (x *SettingsGetAllResponse) Reset() {
-	*x = SettingsGetAllResponse{}
+	Directories  *Configuration_Directories  `protobuf:"bytes,1,opt,name=directories,proto3" json:"directories,omitempty"`
+	Network      *Configuration_Network      `protobuf:"bytes,2,opt,name=network,proto3" json:"network,omitempty"`
+	Sketch       *Configuration_Sketch       `protobuf:"bytes,3,opt,name=sketch,proto3" json:"sketch,omitempty"`
+	BuildCache   *Configuration_BuildCache   `protobuf:"bytes,4,opt,name=build_cache,json=buildCache,proto3" json:"build_cache,omitempty"`
+	BoardManager *Configuration_BoardManager `protobuf:"bytes,5,opt,name=board_manager,json=boardManager,proto3" json:"board_manager,omitempty"`
+	Daemon       *Configuration_Daemon       `protobuf:"bytes,6,opt,name=daemon,proto3" json:"daemon,omitempty"`
+	Output       *Configuration_Output       `protobuf:"bytes,7,opt,name=output,proto3" json:"output,omitempty"`
+	Logging      *Configuration_Logging      `protobuf:"bytes,8,opt,name=logging,proto3" json:"logging,omitempty"`
+	Library      *Configuration_Library      `protobuf:"bytes,9,opt,name=library,proto3" json:"library,omitempty"`
+	Updater      *Configuration_Updater      `protobuf:"bytes,10,opt,name=updater,proto3" json:"updater,omitempty"`
+	Locale       *string                     `protobuf:"bytes,100,opt,name=locale,proto3,oneof" json:"locale,omitempty"`
+}
+
+func (x *Configuration) Reset() {
+	*x = Configuration{}
 	if protoimpl.UnsafeEnabled {
 		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[0]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -53,13 +64,13 @@ func (x *SettingsGetAllResponse) Reset() {
 	}
 }
 
-func (x *SettingsGetAllResponse) String() string {
+func (x *Configuration) String() string {
 	return protoimpl.X.MessageStringOf(x)
 }
 
-func (*SettingsGetAllResponse) ProtoMessage() {}
+func (*Configuration) ProtoMessage() {}
 
-func (x *SettingsGetAllResponse) ProtoReflect() protoreflect.Message {
+func (x *Configuration) ProtoReflect() protoreflect.Message {
 	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[0]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -71,29 +82,96 @@ func (x *SettingsGetAllResponse) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 }
 
-// Deprecated: Use SettingsGetAllResponse.ProtoReflect.Descriptor instead.
-func (*SettingsGetAllResponse) Descriptor() ([]byte, []int) {
+// Deprecated: Use Configuration.ProtoReflect.Descriptor instead.
+func (*Configuration) Descriptor() ([]byte, []int) {
 	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{0}
 }
 
-func (x *SettingsGetAllResponse) GetJsonData() string {
+func (x *Configuration) GetDirectories() *Configuration_Directories {
+	if x != nil {
+		return x.Directories
+	}
+	return nil
+}
+
+func (x *Configuration) GetNetwork() *Configuration_Network {
+	if x != nil {
+		return x.Network
+	}
+	return nil
+}
+
+func (x *Configuration) GetSketch() *Configuration_Sketch {
+	if x != nil {
+		return x.Sketch
+	}
+	return nil
+}
+
+func (x *Configuration) GetBuildCache() *Configuration_BuildCache {
+	if x != nil {
+		return x.BuildCache
+	}
+	return nil
+}
+
+func (x *Configuration) GetBoardManager() *Configuration_BoardManager {
+	if x != nil {
+		return x.BoardManager
+	}
+	return nil
+}
+
+func (x *Configuration) GetDaemon() *Configuration_Daemon {
+	if x != nil {
+		return x.Daemon
+	}
+	return nil
+}
+
+func (x *Configuration) GetOutput() *Configuration_Output {
+	if x != nil {
+		return x.Output
+	}
+	return nil
+}
+
+func (x *Configuration) GetLogging() *Configuration_Logging {
+	if x != nil {
+		return x.Logging
+	}
+	return nil
+}
+
+func (x *Configuration) GetLibrary() *Configuration_Library {
+	if x != nil {
+		return x.Library
+	}
+	return nil
+}
+
+func (x *Configuration) GetUpdater() *Configuration_Updater {
 	if x != nil {
-		return x.JsonData
+		return x.Updater
+	}
+	return nil
+}
+
+func (x *Configuration) GetLocale() string {
+	if x != nil && x.Locale != nil {
+		return *x.Locale
 	}
 	return ""
 }
 
-type SettingsMergeRequest struct {
+type ConfigurationGetRequest struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
-
-	// The settings, in JSON format.
-	JsonData string `protobuf:"bytes,1,opt,name=json_data,json=jsonData,proto3" json:"json_data,omitempty"`
 }
 
-func (x *SettingsMergeRequest) Reset() {
-	*x = SettingsMergeRequest{}
+func (x *ConfigurationGetRequest) Reset() {
+	*x = ConfigurationGetRequest{}
 	if protoimpl.UnsafeEnabled {
 		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[1]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -101,13 +179,13 @@ func (x *SettingsMergeRequest) Reset() {
 	}
 }
 
-func (x *SettingsMergeRequest) String() string {
+func (x *ConfigurationGetRequest) String() string {
 	return protoimpl.X.MessageStringOf(x)
 }
 
-func (*SettingsMergeRequest) ProtoMessage() {}
+func (*ConfigurationGetRequest) ProtoMessage() {}
 
-func (x *SettingsMergeRequest) ProtoReflect() protoreflect.Message {
+func (x *ConfigurationGetRequest) ProtoReflect() protoreflect.Message {
 	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[1]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -119,46 +197,717 @@ func (x *SettingsMergeRequest) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 }
 
-// Deprecated: Use SettingsMergeRequest.ProtoReflect.Descriptor instead.
-func (*SettingsMergeRequest) Descriptor() ([]byte, []int) {
+// Deprecated: Use ConfigurationGetRequest.ProtoReflect.Descriptor instead.
+func (*ConfigurationGetRequest) Descriptor() ([]byte, []int) {
 	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{1}
 }
 
-func (x *SettingsMergeRequest) GetJsonData() string {
+type ConfigurationGetResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The current configuration
+	Configuration *Configuration `protobuf:"bytes,1,opt,name=configuration,proto3" json:"configuration,omitempty"`
+}
+
+func (x *ConfigurationGetResponse) Reset() {
+	*x = ConfigurationGetResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ConfigurationGetResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ConfigurationGetResponse) ProtoMessage() {}
+
+func (x *ConfigurationGetResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ConfigurationGetResponse.ProtoReflect.Descriptor instead.
+func (*ConfigurationGetResponse) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *ConfigurationGetResponse) GetConfiguration() *Configuration {
+	if x != nil {
+		return x.Configuration
+	}
+	return nil
+}
+
+type ConfigurationSaveRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The format of the encoded settings, allowed values are "json" and "yaml"
+	SettingsFormat string `protobuf:"bytes,1,opt,name=settings_format,json=settingsFormat,proto3" json:"settings_format,omitempty"`
+}
+
+func (x *ConfigurationSaveRequest) Reset() {
+	*x = ConfigurationSaveRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ConfigurationSaveRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ConfigurationSaveRequest) ProtoMessage() {}
+
+func (x *ConfigurationSaveRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ConfigurationSaveRequest.ProtoReflect.Descriptor instead.
+func (*ConfigurationSaveRequest) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *ConfigurationSaveRequest) GetSettingsFormat() string {
 	if x != nil {
-		return x.JsonData
+		return x.SettingsFormat
 	}
 	return ""
 }
 
-type SettingsGetValueResponse struct {
+type ConfigurationSaveResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The encoded settings
+	EncodedSettings string `protobuf:"bytes,1,opt,name=encoded_settings,json=encodedSettings,proto3" json:"encoded_settings,omitempty"`
+}
+
+func (x *ConfigurationSaveResponse) Reset() {
+	*x = ConfigurationSaveResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ConfigurationSaveResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ConfigurationSaveResponse) ProtoMessage() {}
+
+func (x *ConfigurationSaveResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ConfigurationSaveResponse.ProtoReflect.Descriptor instead.
+func (*ConfigurationSaveResponse) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *ConfigurationSaveResponse) GetEncodedSettings() string {
+	if x != nil {
+		return x.EncodedSettings
+	}
+	return ""
+}
+
+type ConfigurationOpenRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The encoded settings
+	EncodedSettings string `protobuf:"bytes,1,opt,name=encoded_settings,json=encodedSettings,proto3" json:"encoded_settings,omitempty"`
+	// The format of the encoded settings, allowed values are "json" and "yaml"
+	SettingsFormat string `protobuf:"bytes,2,opt,name=settings_format,json=settingsFormat,proto3" json:"settings_format,omitempty"`
+}
+
+func (x *ConfigurationOpenRequest) Reset() {
+	*x = ConfigurationOpenRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ConfigurationOpenRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ConfigurationOpenRequest) ProtoMessage() {}
+
+func (x *ConfigurationOpenRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[5]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ConfigurationOpenRequest.ProtoReflect.Descriptor instead.
+func (*ConfigurationOpenRequest) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *ConfigurationOpenRequest) GetEncodedSettings() string {
+	if x != nil {
+		return x.EncodedSettings
+	}
+	return ""
+}
+
+func (x *ConfigurationOpenRequest) GetSettingsFormat() string {
+	if x != nil {
+		return x.SettingsFormat
+	}
+	return ""
+}
+
+type ConfigurationOpenResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Warnings that occurred while opening the configuration (e.g. unknown keys,
+	// or invalid values)
+	Warnings []string `protobuf:"bytes,1,rep,name=warnings,proto3" json:"warnings,omitempty"`
+}
+
+func (x *ConfigurationOpenResponse) Reset() {
+	*x = ConfigurationOpenResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[6]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ConfigurationOpenResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ConfigurationOpenResponse) ProtoMessage() {}
+
+func (x *ConfigurationOpenResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[6]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ConfigurationOpenResponse.ProtoReflect.Descriptor instead.
+func (*ConfigurationOpenResponse) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{6}
+}
+
+func (x *ConfigurationOpenResponse) GetWarnings() []string {
+	if x != nil {
+		return x.Warnings
+	}
+	return nil
+}
+
+type SettingsGetValueRequest struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	// The key of the setting.
+	// The key to get
 	Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
-	// The setting, in JSON format.
-	JsonData string `protobuf:"bytes,2,opt,name=json_data,json=jsonData,proto3" json:"json_data,omitempty"`
+	// The format of the encoded_value (default is "json", allowed values are
+	// "json" and "yaml)
+	ValueFormat string `protobuf:"bytes,2,opt,name=value_format,json=valueFormat,proto3" json:"value_format,omitempty"`
 }
 
-func (x *SettingsGetValueResponse) Reset() {
-	*x = SettingsGetValueResponse{}
+func (x *SettingsGetValueRequest) Reset() {
+	*x = SettingsGetValueRequest{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[2]
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[7]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
 }
 
-func (x *SettingsGetValueResponse) String() string {
+func (x *SettingsGetValueRequest) String() string {
 	return protoimpl.X.MessageStringOf(x)
 }
 
-func (*SettingsGetValueResponse) ProtoMessage() {}
+func (*SettingsGetValueRequest) ProtoMessage() {}
+
+func (x *SettingsGetValueRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[7]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SettingsGetValueRequest.ProtoReflect.Descriptor instead.
+func (*SettingsGetValueRequest) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{7}
+}
+
+func (x *SettingsGetValueRequest) GetKey() string {
+	if x != nil {
+		return x.Key
+	}
+	return ""
+}
+
+func (x *SettingsGetValueRequest) GetValueFormat() string {
+	if x != nil {
+		return x.ValueFormat
+	}
+	return ""
+}
+
+type SettingsGetValueResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The value of the key (encoded)
+	EncodedValue string `protobuf:"bytes,1,opt,name=encoded_value,json=encodedValue,proto3" json:"encoded_value,omitempty"`
+}
+
+func (x *SettingsGetValueResponse) Reset() {
+	*x = SettingsGetValueResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[8]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SettingsGetValueResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SettingsGetValueResponse) ProtoMessage() {}
+
+func (x *SettingsGetValueResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[8]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SettingsGetValueResponse.ProtoReflect.Descriptor instead.
+func (*SettingsGetValueResponse) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{8}
+}
+
+func (x *SettingsGetValueResponse) GetEncodedValue() string {
+	if x != nil {
+		return x.EncodedValue
+	}
+	return ""
+}
+
+type SettingsSetValueRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The key to change
+	Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
+	// The new value (encoded), no objects, only scalar or array of scalars are
+	// allowed.
+	EncodedValue string `protobuf:"bytes,2,opt,name=encoded_value,json=encodedValue,proto3" json:"encoded_value,omitempty"`
+	// The format of the encoded_value (default is "json", allowed values are
+	// "json", "yaml" and "cli")
+	ValueFormat string `protobuf:"bytes,3,opt,name=value_format,json=valueFormat,proto3" json:"value_format,omitempty"`
+}
+
+func (x *SettingsSetValueRequest) Reset() {
+	*x = SettingsSetValueRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[9]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SettingsSetValueRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SettingsSetValueRequest) ProtoMessage() {}
+
+func (x *SettingsSetValueRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[9]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SettingsSetValueRequest.ProtoReflect.Descriptor instead.
+func (*SettingsSetValueRequest) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{9}
+}
+
+func (x *SettingsSetValueRequest) GetKey() string {
+	if x != nil {
+		return x.Key
+	}
+	return ""
+}
+
+func (x *SettingsSetValueRequest) GetEncodedValue() string {
+	if x != nil {
+		return x.EncodedValue
+	}
+	return ""
+}
+
+func (x *SettingsSetValueRequest) GetValueFormat() string {
+	if x != nil {
+		return x.ValueFormat
+	}
+	return ""
+}
+
+type SettingsSetValueResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *SettingsSetValueResponse) Reset() {
+	*x = SettingsSetValueResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[10]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SettingsSetValueResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SettingsSetValueResponse) ProtoMessage() {}
+
+func (x *SettingsSetValueResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[10]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SettingsSetValueResponse.ProtoReflect.Descriptor instead.
+func (*SettingsSetValueResponse) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{10}
+}
+
+type SettingsEnumerateRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *SettingsEnumerateRequest) Reset() {
+	*x = SettingsEnumerateRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[11]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SettingsEnumerateRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SettingsEnumerateRequest) ProtoMessage() {}
+
+func (x *SettingsEnumerateRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[11]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SettingsEnumerateRequest.ProtoReflect.Descriptor instead.
+func (*SettingsEnumerateRequest) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{11}
+}
+
+type SettingsEnumerateResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The list of key/value pairs
+	Entries []*SettingsEnumerateResponse_Entry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"`
+}
+
+func (x *SettingsEnumerateResponse) Reset() {
+	*x = SettingsEnumerateResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[12]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SettingsEnumerateResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SettingsEnumerateResponse) ProtoMessage() {}
+
+func (x *SettingsEnumerateResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[12]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SettingsEnumerateResponse.ProtoReflect.Descriptor instead.
+func (*SettingsEnumerateResponse) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{12}
+}
+
+func (x *SettingsEnumerateResponse) GetEntries() []*SettingsEnumerateResponse_Entry {
+	if x != nil {
+		return x.Entries
+	}
+	return nil
+}
+
+type Configuration_Directories struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Data directory
+	Data string `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
+	// User directory
+	User string `protobuf:"bytes,2,opt,name=user,proto3" json:"user,omitempty"`
+	// Downloads directory
+	Downloads string `protobuf:"bytes,3,opt,name=downloads,proto3" json:"downloads,omitempty"`
+	// The directory where the built-in resources are installed
+	Builtin *Configuration_Directories_Builtin `protobuf:"bytes,4,opt,name=builtin,proto3,oneof" json:"builtin,omitempty"`
+}
+
+func (x *Configuration_Directories) Reset() {
+	*x = Configuration_Directories{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[13]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Configuration_Directories) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Configuration_Directories) ProtoMessage() {}
+
+func (x *Configuration_Directories) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[13]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Configuration_Directories.ProtoReflect.Descriptor instead.
+func (*Configuration_Directories) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{0, 0}
+}
+
+func (x *Configuration_Directories) GetData() string {
+	if x != nil {
+		return x.Data
+	}
+	return ""
+}
+
+func (x *Configuration_Directories) GetUser() string {
+	if x != nil {
+		return x.User
+	}
+	return ""
+}
+
+func (x *Configuration_Directories) GetDownloads() string {
+	if x != nil {
+		return x.Downloads
+	}
+	return ""
+}
+
+func (x *Configuration_Directories) GetBuiltin() *Configuration_Directories_Builtin {
+	if x != nil {
+		return x.Builtin
+	}
+	return nil
+}
+
+type Configuration_Network struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Extra user-agent information to be appended in network requests
+	ExtraUserAgent *string `protobuf:"bytes,1,opt,name=extra_user_agent,json=extraUserAgent,proto3,oneof" json:"extra_user_agent,omitempty"`
+	// The proxy to use for network requests
+	Proxy *string `protobuf:"bytes,2,opt,name=proxy,proto3,oneof" json:"proxy,omitempty"`
+}
+
+func (x *Configuration_Network) Reset() {
+	*x = Configuration_Network{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[14]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Configuration_Network) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Configuration_Network) ProtoMessage() {}
+
+func (x *Configuration_Network) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[14]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Configuration_Network.ProtoReflect.Descriptor instead.
+func (*Configuration_Network) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{0, 1}
+}
+
+func (x *Configuration_Network) GetExtraUserAgent() string {
+	if x != nil && x.ExtraUserAgent != nil {
+		return *x.ExtraUserAgent
+	}
+	return ""
+}
+
+func (x *Configuration_Network) GetProxy() string {
+	if x != nil && x.Proxy != nil {
+		return *x.Proxy
+	}
+	return ""
+}
+
+type Configuration_Sketch struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Set to true to always export binaries to the sketch directory
+	AlwaysExportBinaries bool `protobuf:"varint,1,opt,name=always_export_binaries,json=alwaysExportBinaries,proto3" json:"always_export_binaries,omitempty"`
+}
+
+func (x *Configuration_Sketch) Reset() {
+	*x = Configuration_Sketch{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[15]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Configuration_Sketch) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Configuration_Sketch) ProtoMessage() {}
 
-func (x *SettingsGetValueResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[2]
+func (x *Configuration_Sketch) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[15]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -169,53 +918,46 @@ func (x *SettingsGetValueResponse) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 }
 
-// Deprecated: Use SettingsGetValueResponse.ProtoReflect.Descriptor instead.
-func (*SettingsGetValueResponse) Descriptor() ([]byte, []int) {
-	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{2}
-}
-
-func (x *SettingsGetValueResponse) GetKey() string {
-	if x != nil {
-		return x.Key
-	}
-	return ""
+// Deprecated: Use Configuration_Sketch.ProtoReflect.Descriptor instead.
+func (*Configuration_Sketch) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{0, 2}
 }
 
-func (x *SettingsGetValueResponse) GetJsonData() string {
+func (x *Configuration_Sketch) GetAlwaysExportBinaries() bool {
 	if x != nil {
-		return x.JsonData
+		return x.AlwaysExportBinaries
 	}
-	return ""
+	return false
 }
 
-type SettingsSetValueRequest struct {
+type Configuration_BuildCache struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	// The key of the setting.
-	Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
-	// The setting, in JSON format.
-	JsonData string `protobuf:"bytes,2,opt,name=json_data,json=jsonData,proto3" json:"json_data,omitempty"`
+	// The minimum number of compilations before the cache is purged
+	CompilationsBeforePurge uint64 `protobuf:"varint,1,opt,name=compilations_before_purge,json=compilationsBeforePurge,proto3" json:"compilations_before_purge,omitempty"`
+	// Time to live of the cache in seconds
+	TtlSecs uint64 `protobuf:"varint,2,opt,name=ttl_secs,json=ttlSecs,proto3" json:"ttl_secs,omitempty"`
 }
 
-func (x *SettingsSetValueRequest) Reset() {
-	*x = SettingsSetValueRequest{}
+func (x *Configuration_BuildCache) Reset() {
+	*x = Configuration_BuildCache{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[3]
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[16]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
 }
 
-func (x *SettingsSetValueRequest) String() string {
+func (x *Configuration_BuildCache) String() string {
 	return protoimpl.X.MessageStringOf(x)
 }
 
-func (*SettingsSetValueRequest) ProtoMessage() {}
+func (*Configuration_BuildCache) ProtoMessage() {}
 
-func (x *SettingsSetValueRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[3]
+func (x *Configuration_BuildCache) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[16]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -226,48 +968,51 @@ func (x *SettingsSetValueRequest) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 }
 
-// Deprecated: Use SettingsSetValueRequest.ProtoReflect.Descriptor instead.
-func (*SettingsSetValueRequest) Descriptor() ([]byte, []int) {
-	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{3}
+// Deprecated: Use Configuration_BuildCache.ProtoReflect.Descriptor instead.
+func (*Configuration_BuildCache) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{0, 3}
 }
 
-func (x *SettingsSetValueRequest) GetKey() string {
+func (x *Configuration_BuildCache) GetCompilationsBeforePurge() uint64 {
 	if x != nil {
-		return x.Key
+		return x.CompilationsBeforePurge
 	}
-	return ""
+	return 0
 }
 
-func (x *SettingsSetValueRequest) GetJsonData() string {
+func (x *Configuration_BuildCache) GetTtlSecs() uint64 {
 	if x != nil {
-		return x.JsonData
+		return x.TtlSecs
 	}
-	return ""
+	return 0
 }
 
-type SettingsGetAllRequest struct {
+type Configuration_BoardManager struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
+
+	// Additional URLs to be used for the board manager
+	AdditionalUrls []string `protobuf:"bytes,1,rep,name=additional_urls,json=additionalUrls,proto3" json:"additional_urls,omitempty"`
 }
 
-func (x *SettingsGetAllRequest) Reset() {
-	*x = SettingsGetAllRequest{}
+func (x *Configuration_BoardManager) Reset() {
+	*x = Configuration_BoardManager{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[4]
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[17]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
 }
 
-func (x *SettingsGetAllRequest) String() string {
+func (x *Configuration_BoardManager) String() string {
 	return protoimpl.X.MessageStringOf(x)
 }
 
-func (*SettingsGetAllRequest) ProtoMessage() {}
+func (*Configuration_BoardManager) ProtoMessage() {}
 
-func (x *SettingsGetAllRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[4]
+func (x *Configuration_BoardManager) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[17]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -278,37 +1023,44 @@ func (x *SettingsGetAllRequest) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 }
 
-// Deprecated: Use SettingsGetAllRequest.ProtoReflect.Descriptor instead.
-func (*SettingsGetAllRequest) Descriptor() ([]byte, []int) {
-	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{4}
+// Deprecated: Use Configuration_BoardManager.ProtoReflect.Descriptor instead.
+func (*Configuration_BoardManager) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{0, 4}
 }
 
-type SettingsGetValueRequest struct {
+func (x *Configuration_BoardManager) GetAdditionalUrls() []string {
+	if x != nil {
+		return x.AdditionalUrls
+	}
+	return nil
+}
+
+type Configuration_Daemon struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	// The key of the setting.
-	Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
+	// The TCP port of the daemon
+	Port string `protobuf:"bytes,1,opt,name=port,proto3" json:"port,omitempty"`
 }
 
-func (x *SettingsGetValueRequest) Reset() {
-	*x = SettingsGetValueRequest{}
+func (x *Configuration_Daemon) Reset() {
+	*x = Configuration_Daemon{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[5]
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[18]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
 }
 
-func (x *SettingsGetValueRequest) String() string {
+func (x *Configuration_Daemon) String() string {
 	return protoimpl.X.MessageStringOf(x)
 }
 
-func (*SettingsGetValueRequest) ProtoMessage() {}
+func (*Configuration_Daemon) ProtoMessage() {}
 
-func (x *SettingsGetValueRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[5]
+func (x *Configuration_Daemon) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[18]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -319,41 +1071,44 @@ func (x *SettingsGetValueRequest) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 }
 
-// Deprecated: Use SettingsGetValueRequest.ProtoReflect.Descriptor instead.
-func (*SettingsGetValueRequest) Descriptor() ([]byte, []int) {
-	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{5}
+// Deprecated: Use Configuration_Daemon.ProtoReflect.Descriptor instead.
+func (*Configuration_Daemon) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{0, 5}
 }
 
-func (x *SettingsGetValueRequest) GetKey() string {
+func (x *Configuration_Daemon) GetPort() string {
 	if x != nil {
-		return x.Key
+		return x.Port
 	}
 	return ""
 }
 
-type SettingsMergeResponse struct {
+type Configuration_Output struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
+
+	// Set to true to disable coloring of the output
+	NoColor bool `protobuf:"varint,1,opt,name=no_color,json=noColor,proto3" json:"no_color,omitempty"`
 }
 
-func (x *SettingsMergeResponse) Reset() {
-	*x = SettingsMergeResponse{}
+func (x *Configuration_Output) Reset() {
+	*x = Configuration_Output{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[6]
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[19]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
 }
 
-func (x *SettingsMergeResponse) String() string {
+func (x *Configuration_Output) String() string {
 	return protoimpl.X.MessageStringOf(x)
 }
 
-func (*SettingsMergeResponse) ProtoMessage() {}
+func (*Configuration_Output) ProtoMessage() {}
 
-func (x *SettingsMergeResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[6]
+func (x *Configuration_Output) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[19]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -364,34 +1119,48 @@ func (x *SettingsMergeResponse) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 }
 
-// Deprecated: Use SettingsMergeResponse.ProtoReflect.Descriptor instead.
-func (*SettingsMergeResponse) Descriptor() ([]byte, []int) {
-	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{6}
+// Deprecated: Use Configuration_Output.ProtoReflect.Descriptor instead.
+func (*Configuration_Output) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{0, 6}
 }
 
-type SettingsSetValueResponse struct {
+func (x *Configuration_Output) GetNoColor() bool {
+	if x != nil {
+		return x.NoColor
+	}
+	return false
+}
+
+type Configuration_Logging struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
+
+	// The logging level
+	Level string `protobuf:"bytes,1,opt,name=level,proto3" json:"level,omitempty"`
+	// The logging format
+	Format string `protobuf:"bytes,2,opt,name=format,proto3" json:"format,omitempty"`
+	// The logging file
+	File *string `protobuf:"bytes,3,opt,name=file,proto3,oneof" json:"file,omitempty"`
 }
 
-func (x *SettingsSetValueResponse) Reset() {
-	*x = SettingsSetValueResponse{}
+func (x *Configuration_Logging) Reset() {
+	*x = Configuration_Logging{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[7]
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[20]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
 }
 
-func (x *SettingsSetValueResponse) String() string {
+func (x *Configuration_Logging) String() string {
 	return protoimpl.X.MessageStringOf(x)
 }
 
-func (*SettingsSetValueResponse) ProtoMessage() {}
+func (*Configuration_Logging) ProtoMessage() {}
 
-func (x *SettingsSetValueResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[7]
+func (x *Configuration_Logging) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[20]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -402,37 +1171,59 @@ func (x *SettingsSetValueResponse) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 }
 
-// Deprecated: Use SettingsSetValueResponse.ProtoReflect.Descriptor instead.
-func (*SettingsSetValueResponse) Descriptor() ([]byte, []int) {
-	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{7}
+// Deprecated: Use Configuration_Logging.ProtoReflect.Descriptor instead.
+func (*Configuration_Logging) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{0, 7}
+}
+
+func (x *Configuration_Logging) GetLevel() string {
+	if x != nil {
+		return x.Level
+	}
+	return ""
+}
+
+func (x *Configuration_Logging) GetFormat() string {
+	if x != nil {
+		return x.Format
+	}
+	return ""
 }
 
-type SettingsWriteRequest struct {
+func (x *Configuration_Logging) GetFile() string {
+	if x != nil && x.File != nil {
+		return *x.File
+	}
+	return ""
+}
+
+type Configuration_Library struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	// Path to settings file (e.g. /path/to/arduino-cli.yaml)
-	FilePath string `protobuf:"bytes,1,opt,name=file_path,json=filePath,proto3" json:"file_path,omitempty"`
+	// Set to true to enable library installation from zip archives or git
+	// repositories
+	EnableUnsafeInstall bool `protobuf:"varint,1,opt,name=enable_unsafe_install,json=enableUnsafeInstall,proto3" json:"enable_unsafe_install,omitempty"`
 }
 
-func (x *SettingsWriteRequest) Reset() {
-	*x = SettingsWriteRequest{}
+func (x *Configuration_Library) Reset() {
+	*x = Configuration_Library{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[8]
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[21]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
 }
 
-func (x *SettingsWriteRequest) String() string {
+func (x *Configuration_Library) String() string {
 	return protoimpl.X.MessageStringOf(x)
 }
 
-func (*SettingsWriteRequest) ProtoMessage() {}
+func (*Configuration_Library) ProtoMessage() {}
 
-func (x *SettingsWriteRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[8]
+func (x *Configuration_Library) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[21]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -443,41 +1234,44 @@ func (x *SettingsWriteRequest) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 }
 
-// Deprecated: Use SettingsWriteRequest.ProtoReflect.Descriptor instead.
-func (*SettingsWriteRequest) Descriptor() ([]byte, []int) {
-	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{8}
+// Deprecated: Use Configuration_Library.ProtoReflect.Descriptor instead.
+func (*Configuration_Library) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{0, 8}
 }
 
-func (x *SettingsWriteRequest) GetFilePath() string {
+func (x *Configuration_Library) GetEnableUnsafeInstall() bool {
 	if x != nil {
-		return x.FilePath
+		return x.EnableUnsafeInstall
 	}
-	return ""
+	return false
 }
 
-type SettingsWriteResponse struct {
+type Configuration_Updater struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
+
+	// Set to true to enable notifications for updates
+	EnableNotification bool `protobuf:"varint,1,opt,name=enable_notification,json=enableNotification,proto3" json:"enable_notification,omitempty"`
 }
 
-func (x *SettingsWriteResponse) Reset() {
-	*x = SettingsWriteResponse{}
+func (x *Configuration_Updater) Reset() {
+	*x = Configuration_Updater{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[9]
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[22]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
 }
 
-func (x *SettingsWriteResponse) String() string {
+func (x *Configuration_Updater) String() string {
 	return protoimpl.X.MessageStringOf(x)
 }
 
-func (*SettingsWriteResponse) ProtoMessage() {}
+func (*Configuration_Updater) ProtoMessage() {}
 
-func (x *SettingsWriteResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[9]
+func (x *Configuration_Updater) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[22]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -488,37 +1282,44 @@ func (x *SettingsWriteResponse) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 }
 
-// Deprecated: Use SettingsWriteResponse.ProtoReflect.Descriptor instead.
-func (*SettingsWriteResponse) Descriptor() ([]byte, []int) {
-	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{9}
+// Deprecated: Use Configuration_Updater.ProtoReflect.Descriptor instead.
+func (*Configuration_Updater) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{0, 9}
+}
+
+func (x *Configuration_Updater) GetEnableNotification() bool {
+	if x != nil {
+		return x.EnableNotification
+	}
+	return false
 }
 
-type SettingsDeleteRequest struct {
+type Configuration_Directories_Builtin struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	// The key of the setting to delete.
-	Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
+	// The directory where the built-in libraries are installed
+	Libraries *string `protobuf:"bytes,1,opt,name=libraries,proto3,oneof" json:"libraries,omitempty"`
 }
 
-func (x *SettingsDeleteRequest) Reset() {
-	*x = SettingsDeleteRequest{}
+func (x *Configuration_Directories_Builtin) Reset() {
+	*x = Configuration_Directories_Builtin{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[10]
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[23]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
 }
 
-func (x *SettingsDeleteRequest) String() string {
+func (x *Configuration_Directories_Builtin) String() string {
 	return protoimpl.X.MessageStringOf(x)
 }
 
-func (*SettingsDeleteRequest) ProtoMessage() {}
+func (*Configuration_Directories_Builtin) ProtoMessage() {}
 
-func (x *SettingsDeleteRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[10]
+func (x *Configuration_Directories_Builtin) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[23]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -529,41 +1330,46 @@ func (x *SettingsDeleteRequest) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 }
 
-// Deprecated: Use SettingsDeleteRequest.ProtoReflect.Descriptor instead.
-func (*SettingsDeleteRequest) Descriptor() ([]byte, []int) {
-	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{10}
+// Deprecated: Use Configuration_Directories_Builtin.ProtoReflect.Descriptor instead.
+func (*Configuration_Directories_Builtin) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{0, 0, 0}
 }
 
-func (x *SettingsDeleteRequest) GetKey() string {
-	if x != nil {
-		return x.Key
+func (x *Configuration_Directories_Builtin) GetLibraries() string {
+	if x != nil && x.Libraries != nil {
+		return *x.Libraries
 	}
 	return ""
 }
 
-type SettingsDeleteResponse struct {
+type SettingsEnumerateResponse_Entry struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
+
+	// The key
+	Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
+	// The key type
+	Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"`
 }
 
-func (x *SettingsDeleteResponse) Reset() {
-	*x = SettingsDeleteResponse{}
+func (x *SettingsEnumerateResponse_Entry) Reset() {
+	*x = SettingsEnumerateResponse_Entry{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[11]
+		mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[24]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
 }
 
-func (x *SettingsDeleteResponse) String() string {
+func (x *SettingsEnumerateResponse_Entry) String() string {
 	return protoimpl.X.MessageStringOf(x)
 }
 
-func (*SettingsDeleteResponse) ProtoMessage() {}
+func (*SettingsEnumerateResponse_Entry) ProtoMessage() {}
 
-func (x *SettingsDeleteResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[11]
+func (x *SettingsEnumerateResponse_Entry) ProtoReflect() protoreflect.Message {
+	mi := &file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[24]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -574,9 +1380,23 @@ func (x *SettingsDeleteResponse) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 }
 
-// Deprecated: Use SettingsDeleteResponse.ProtoReflect.Descriptor instead.
-func (*SettingsDeleteResponse) Descriptor() ([]byte, []int) {
-	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{11}
+// Deprecated: Use SettingsEnumerateResponse_Entry.ProtoReflect.Descriptor instead.
+func (*SettingsEnumerateResponse_Entry) Descriptor() ([]byte, []int) {
+	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP(), []int{12, 0}
+}
+
+func (x *SettingsEnumerateResponse_Entry) GetKey() string {
+	if x != nil {
+		return x.Key
+	}
+	return ""
+}
+
+func (x *SettingsEnumerateResponse_Entry) GetType() string {
+	if x != nil {
+		return x.Type
+	}
+	return ""
 }
 
 var File_cc_arduino_cli_commands_v1_settings_proto protoreflect.FileDescriptor
@@ -586,45 +1406,178 @@ var file_cc_arduino_cli_commands_v1_settings_proto_rawDesc = []byte{
 	0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x74,
 	0x74, 0x69, 0x6e, 0x67, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x63, 0x63, 0x2e,
 	0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
-	0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x22, 0x35, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x74, 0x69,
-	0x6e, 0x67, 0x73, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
-	0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6a, 0x73, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x22, 0x33,
-	0x0a, 0x14, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x52,
-	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x64,
-	0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6a, 0x73, 0x6f, 0x6e, 0x44,
-	0x61, 0x74, 0x61, 0x22, 0x49, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x47,
-	0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
-	0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65,
-	0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6a, 0x73, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x22, 0x48,
-	0x0a, 0x17, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x53, 0x65, 0x74, 0x56, 0x61, 0x6c,
-	0x75, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6a,
-	0x73, 0x6f, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
-	0x6a, 0x73, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x22, 0x17, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x74,
-	0x69, 0x6e, 0x67, 0x73, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
-	0x74, 0x22, 0x2b, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x47, 0x65, 0x74,
-	0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03,
-	0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x17,
-	0x0a, 0x15, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x4d, 0x65, 0x72, 0x67, 0x65, 0x52,
+	0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x22, 0xbd, 0x0d, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x66,
+	0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x57, 0x0a, 0x0b, 0x64, 0x69, 0x72,
+	0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35,
+	0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e,
+	0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66,
+	0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74,
+	0x6f, 0x72, 0x69, 0x65, 0x73, 0x52, 0x0b, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69,
+	0x65, 0x73, 0x12, 0x4b, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f,
+	0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31,
+	0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4e,
+	0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12,
+	0x48, 0x0a, 0x06, 0x73, 0x6b, 0x65, 0x74, 0x63, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x30, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69,
+	0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x6b, 0x65, 0x74, 0x63,
+	0x68, 0x52, 0x06, 0x73, 0x6b, 0x65, 0x74, 0x63, 0x68, 0x12, 0x55, 0x0a, 0x0b, 0x62, 0x75, 0x69,
+	0x6c, 0x64, 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x34,
+	0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e,
+	0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66,
+	0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x43,
+	0x61, 0x63, 0x68, 0x65, 0x52, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x61, 0x63, 0x68, 0x65,
+	0x12, 0x5b, 0x0a, 0x0d, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
+	0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64,
+	0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
+	0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x2e, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x52,
+	0x0c, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x12, 0x48, 0x0a,
+	0x06, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e,
+	0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63,
+	0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+	0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x52,
+	0x06, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75,
+	0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64,
+	0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
+	0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75,
+	0x74, 0x12, 0x4b, 0x0a, 0x07, 0x6c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x18, 0x08, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x31, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e,
+	0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4c, 0x6f,
+	0x67, 0x67, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x12, 0x4b,
+	0x0a, 0x07, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x31, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69,
+	0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4c, 0x69, 0x62, 0x72, 0x61,
+	0x72, 0x79, 0x52, 0x07, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, 0x4b, 0x0a, 0x07, 0x75,
+	0x70, 0x64, 0x61, 0x74, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x63,
+	0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f,
+	0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x72, 0x52,
+	0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x06, 0x6c, 0x6f, 0x63, 0x61,
+	0x6c, 0x65, 0x18, 0x64, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x06, 0x6c, 0x6f, 0x63, 0x61,
+	0x6c, 0x65, 0x88, 0x01, 0x01, 0x1a, 0xf9, 0x01, 0x0a, 0x0b, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74,
+	0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x73, 0x65,
+	0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x1c, 0x0a,
+	0x09, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x09, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x12, 0x5c, 0x0a, 0x07, 0x62,
+	0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x63,
+	0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f,
+	0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72,
+	0x69, 0x65, 0x73, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x48, 0x00, 0x52, 0x07, 0x62,
+	0x75, 0x69, 0x6c, 0x74, 0x69, 0x6e, 0x88, 0x01, 0x01, 0x1a, 0x3a, 0x0a, 0x07, 0x42, 0x75, 0x69,
+	0x6c, 0x74, 0x69, 0x6e, 0x12, 0x21, 0x0a, 0x09, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65,
+	0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x09, 0x6c, 0x69, 0x62, 0x72, 0x61,
+	0x72, 0x69, 0x65, 0x73, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x6c, 0x69, 0x62, 0x72,
+	0x61, 0x72, 0x69, 0x65, 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x74, 0x69,
+	0x6e, 0x1a, 0x72, 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x2d, 0x0a, 0x10,
+	0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x74,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0e, 0x65, 0x78, 0x74, 0x72, 0x61, 0x55,
+	0x73, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x70,
+	0x72, 0x6f, 0x78, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x05, 0x70, 0x72,
+	0x6f, 0x78, 0x79, 0x88, 0x01, 0x01, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x65, 0x78, 0x74, 0x72, 0x61,
+	0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f,
+	0x70, 0x72, 0x6f, 0x78, 0x79, 0x1a, 0x3e, 0x0a, 0x06, 0x53, 0x6b, 0x65, 0x74, 0x63, 0x68, 0x12,
+	0x34, 0x0a, 0x16, 0x61, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x5f, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74,
+	0x5f, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,
+	0x14, 0x61, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x69, 0x6e,
+	0x61, 0x72, 0x69, 0x65, 0x73, 0x1a, 0x63, 0x0a, 0x0a, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x61,
+	0x63, 0x68, 0x65, 0x12, 0x3a, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x73, 0x5f, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x5f, 0x70, 0x75, 0x72, 0x67, 0x65,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x17, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x73, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x50, 0x75, 0x72, 0x67, 0x65, 0x12,
+	0x19, 0x0a, 0x08, 0x74, 0x74, 0x6c, 0x5f, 0x73, 0x65, 0x63, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x04, 0x52, 0x07, 0x74, 0x74, 0x6c, 0x53, 0x65, 0x63, 0x73, 0x1a, 0x37, 0x0a, 0x0c, 0x42, 0x6f,
+	0x61, 0x72, 0x64, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x61, 0x64,
+	0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x75, 0x72, 0x6c, 0x73, 0x18, 0x01, 0x20,
+	0x03, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x55,
+	0x72, 0x6c, 0x73, 0x1a, 0x1c, 0x0a, 0x06, 0x44, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x12, 0x12, 0x0a,
+	0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x6f, 0x72,
+	0x74, 0x1a, 0x23, 0x0a, 0x06, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6e,
+	0x6f, 0x5f, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x6e,
+	0x6f, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x1a, 0x59, 0x0a, 0x07, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e,
+	0x67, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x6d, 0x61,
+	0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12,
+	0x17, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52,
+	0x04, 0x66, 0x69, 0x6c, 0x65, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x66, 0x69, 0x6c,
+	0x65, 0x1a, 0x3d, 0x0a, 0x07, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, 0x32, 0x0a, 0x15,
+	0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x75, 0x6e, 0x73, 0x61, 0x66, 0x65, 0x5f, 0x69, 0x6e,
+	0x73, 0x74, 0x61, 0x6c, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x65, 0x6e, 0x61,
+	0x62, 0x6c, 0x65, 0x55, 0x6e, 0x73, 0x61, 0x66, 0x65, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c,
+	0x1a, 0x3a, 0x0a, 0x07, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x13, 0x65,
+	0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65,
+	0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x09, 0x0a, 0x07,
+	0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x65, 0x22, 0x19, 0x0a, 0x17, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+	0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x22, 0x6b, 0x0a, 0x18, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f,
+	0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69,
+	0x6e, 0x6f, 0x2e, 0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e,
+	0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22,
+	0x43, 0x0a, 0x18, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x53, 0x61, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x73,
+	0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x6f,
+	0x72, 0x6d, 0x61, 0x74, 0x22, 0x46, 0x0a, 0x19, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x61, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+	0x65, 0x12, 0x29, 0x0a, 0x10, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x74,
+	0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x65, 0x6e, 0x63,
+	0x6f, 0x64, 0x65, 0x64, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x6e, 0x0a, 0x18,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x70, 0x65,
+	0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x65, 0x6e, 0x63, 0x6f,
+	0x64, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x0f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x53, 0x65, 0x74, 0x74, 0x69,
+	0x6e, 0x67, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x5f,
+	0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65,
+	0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x22, 0x37, 0x0a, 0x19,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x70, 0x65,
+	0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x61, 0x72,
+	0x6e, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x77, 0x61, 0x72,
+	0x6e, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x4e, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67,
+	0x73, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
+	0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x6d,
+	0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x46,
+	0x6f, 0x72, 0x6d, 0x61, 0x74, 0x22, 0x3f, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67,
+	0x73, 0x47, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+	0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x5f, 0x76, 0x61, 0x6c,
+	0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65,
+	0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x73, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e,
+	0x67, 0x73, 0x53, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
+	0x6b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x5f, 0x76,
+	0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x6e, 0x63, 0x6f,
+	0x64, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x76, 0x61, 0x6c, 0x75,
+	0x65, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
+	0x76, 0x61, 0x6c, 0x75, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x22, 0x1a, 0x0a, 0x18, 0x53,
+	0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x53, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52,
 	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x74, 0x69,
-	0x6e, 0x67, 0x73, 0x53, 0x65, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
-	0x6e, 0x73, 0x65, 0x22, 0x33, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x57,
-	0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x66,
-	0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
-	0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x22, 0x17, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x74,
-	0x69, 0x6e, 0x67, 0x73, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
-	0x65, 0x22, 0x29, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x44, 0x65, 0x6c,
-	0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
-	0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x18, 0x0a, 0x16,
-	0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65,
-	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x48, 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
-	0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2f, 0x61, 0x72, 0x64,
-	0x75, 0x69, 0x6e, 0x6f, 0x2d, 0x63, 0x6c, 0x69, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x63, 0x2f,
-	0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x63, 0x6f, 0x6d, 0x6d,
-	0x61, 0x6e, 0x64, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73,
-	0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x6e, 0x67, 0x73, 0x45, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x22, 0xa1, 0x01, 0x0a, 0x19, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
+	0x45, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+	0x65, 0x12, 0x55, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x63, 0x63, 0x2e, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2e,
+	0x63, 0x6c, 0x69, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2e, 0x76, 0x31, 0x2e,
+	0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x74,
+	0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52,
+	0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x1a, 0x2d, 0x0a, 0x05, 0x45, 0x6e, 0x74, 0x72,
+	0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
+	0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x42, 0x48, 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, 0x75,
+	0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2f, 0x61, 0x72,
+	0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2d, 0x63, 0x6c, 0x69, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x63, 0x63,
+	0x2f, 0x61, 0x72, 0x64, 0x75, 0x69, 0x6e, 0x6f, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x63, 0x6f, 0x6d,
+	0x6d, 0x61, 0x6e, 0x64, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64,
+	0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -639,27 +1592,53 @@ func file_cc_arduino_cli_commands_v1_settings_proto_rawDescGZIP() []byte {
 	return file_cc_arduino_cli_commands_v1_settings_proto_rawDescData
 }
 
-var file_cc_arduino_cli_commands_v1_settings_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
+var file_cc_arduino_cli_commands_v1_settings_proto_msgTypes = make([]protoimpl.MessageInfo, 25)
 var file_cc_arduino_cli_commands_v1_settings_proto_goTypes = []interface{}{
-	(*SettingsGetAllResponse)(nil),   // 0: cc.arduino.cli.commands.v1.SettingsGetAllResponse
-	(*SettingsMergeRequest)(nil),     // 1: cc.arduino.cli.commands.v1.SettingsMergeRequest
-	(*SettingsGetValueResponse)(nil), // 2: cc.arduino.cli.commands.v1.SettingsGetValueResponse
-	(*SettingsSetValueRequest)(nil),  // 3: cc.arduino.cli.commands.v1.SettingsSetValueRequest
-	(*SettingsGetAllRequest)(nil),    // 4: cc.arduino.cli.commands.v1.SettingsGetAllRequest
-	(*SettingsGetValueRequest)(nil),  // 5: cc.arduino.cli.commands.v1.SettingsGetValueRequest
-	(*SettingsMergeResponse)(nil),    // 6: cc.arduino.cli.commands.v1.SettingsMergeResponse
-	(*SettingsSetValueResponse)(nil), // 7: cc.arduino.cli.commands.v1.SettingsSetValueResponse
-	(*SettingsWriteRequest)(nil),     // 8: cc.arduino.cli.commands.v1.SettingsWriteRequest
-	(*SettingsWriteResponse)(nil),    // 9: cc.arduino.cli.commands.v1.SettingsWriteResponse
-	(*SettingsDeleteRequest)(nil),    // 10: cc.arduino.cli.commands.v1.SettingsDeleteRequest
-	(*SettingsDeleteResponse)(nil),   // 11: cc.arduino.cli.commands.v1.SettingsDeleteResponse
+	(*Configuration)(nil),                     // 0: cc.arduino.cli.commands.v1.Configuration
+	(*ConfigurationGetRequest)(nil),           // 1: cc.arduino.cli.commands.v1.ConfigurationGetRequest
+	(*ConfigurationGetResponse)(nil),          // 2: cc.arduino.cli.commands.v1.ConfigurationGetResponse
+	(*ConfigurationSaveRequest)(nil),          // 3: cc.arduino.cli.commands.v1.ConfigurationSaveRequest
+	(*ConfigurationSaveResponse)(nil),         // 4: cc.arduino.cli.commands.v1.ConfigurationSaveResponse
+	(*ConfigurationOpenRequest)(nil),          // 5: cc.arduino.cli.commands.v1.ConfigurationOpenRequest
+	(*ConfigurationOpenResponse)(nil),         // 6: cc.arduino.cli.commands.v1.ConfigurationOpenResponse
+	(*SettingsGetValueRequest)(nil),           // 7: cc.arduino.cli.commands.v1.SettingsGetValueRequest
+	(*SettingsGetValueResponse)(nil),          // 8: cc.arduino.cli.commands.v1.SettingsGetValueResponse
+	(*SettingsSetValueRequest)(nil),           // 9: cc.arduino.cli.commands.v1.SettingsSetValueRequest
+	(*SettingsSetValueResponse)(nil),          // 10: cc.arduino.cli.commands.v1.SettingsSetValueResponse
+	(*SettingsEnumerateRequest)(nil),          // 11: cc.arduino.cli.commands.v1.SettingsEnumerateRequest
+	(*SettingsEnumerateResponse)(nil),         // 12: cc.arduino.cli.commands.v1.SettingsEnumerateResponse
+	(*Configuration_Directories)(nil),         // 13: cc.arduino.cli.commands.v1.Configuration.Directories
+	(*Configuration_Network)(nil),             // 14: cc.arduino.cli.commands.v1.Configuration.Network
+	(*Configuration_Sketch)(nil),              // 15: cc.arduino.cli.commands.v1.Configuration.Sketch
+	(*Configuration_BuildCache)(nil),          // 16: cc.arduino.cli.commands.v1.Configuration.BuildCache
+	(*Configuration_BoardManager)(nil),        // 17: cc.arduino.cli.commands.v1.Configuration.BoardManager
+	(*Configuration_Daemon)(nil),              // 18: cc.arduino.cli.commands.v1.Configuration.Daemon
+	(*Configuration_Output)(nil),              // 19: cc.arduino.cli.commands.v1.Configuration.Output
+	(*Configuration_Logging)(nil),             // 20: cc.arduino.cli.commands.v1.Configuration.Logging
+	(*Configuration_Library)(nil),             // 21: cc.arduino.cli.commands.v1.Configuration.Library
+	(*Configuration_Updater)(nil),             // 22: cc.arduino.cli.commands.v1.Configuration.Updater
+	(*Configuration_Directories_Builtin)(nil), // 23: cc.arduino.cli.commands.v1.Configuration.Directories.Builtin
+	(*SettingsEnumerateResponse_Entry)(nil),   // 24: cc.arduino.cli.commands.v1.SettingsEnumerateResponse.Entry
 }
 var file_cc_arduino_cli_commands_v1_settings_proto_depIdxs = []int32{
-	0, // [0:0] is the sub-list for method output_type
-	0, // [0:0] is the sub-list for method input_type
-	0, // [0:0] is the sub-list for extension type_name
-	0, // [0:0] is the sub-list for extension extendee
-	0, // [0:0] is the sub-list for field type_name
+	13, // 0: cc.arduino.cli.commands.v1.Configuration.directories:type_name -> cc.arduino.cli.commands.v1.Configuration.Directories
+	14, // 1: cc.arduino.cli.commands.v1.Configuration.network:type_name -> cc.arduino.cli.commands.v1.Configuration.Network
+	15, // 2: cc.arduino.cli.commands.v1.Configuration.sketch:type_name -> cc.arduino.cli.commands.v1.Configuration.Sketch
+	16, // 3: cc.arduino.cli.commands.v1.Configuration.build_cache:type_name -> cc.arduino.cli.commands.v1.Configuration.BuildCache
+	17, // 4: cc.arduino.cli.commands.v1.Configuration.board_manager:type_name -> cc.arduino.cli.commands.v1.Configuration.BoardManager
+	18, // 5: cc.arduino.cli.commands.v1.Configuration.daemon:type_name -> cc.arduino.cli.commands.v1.Configuration.Daemon
+	19, // 6: cc.arduino.cli.commands.v1.Configuration.output:type_name -> cc.arduino.cli.commands.v1.Configuration.Output
+	20, // 7: cc.arduino.cli.commands.v1.Configuration.logging:type_name -> cc.arduino.cli.commands.v1.Configuration.Logging
+	21, // 8: cc.arduino.cli.commands.v1.Configuration.library:type_name -> cc.arduino.cli.commands.v1.Configuration.Library
+	22, // 9: cc.arduino.cli.commands.v1.Configuration.updater:type_name -> cc.arduino.cli.commands.v1.Configuration.Updater
+	0,  // 10: cc.arduino.cli.commands.v1.ConfigurationGetResponse.configuration:type_name -> cc.arduino.cli.commands.v1.Configuration
+	24, // 11: cc.arduino.cli.commands.v1.SettingsEnumerateResponse.entries:type_name -> cc.arduino.cli.commands.v1.SettingsEnumerateResponse.Entry
+	23, // 12: cc.arduino.cli.commands.v1.Configuration.Directories.builtin:type_name -> cc.arduino.cli.commands.v1.Configuration.Directories.Builtin
+	13, // [13:13] is the sub-list for method output_type
+	13, // [13:13] is the sub-list for method input_type
+	13, // [13:13] is the sub-list for extension type_name
+	13, // [13:13] is the sub-list for extension extendee
+	0,  // [0:13] is the sub-list for field type_name
 }
 
 func init() { file_cc_arduino_cli_commands_v1_settings_proto_init() }
@@ -669,7 +1648,7 @@ func file_cc_arduino_cli_commands_v1_settings_proto_init() {
 	}
 	if !protoimpl.UnsafeEnabled {
 		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SettingsGetAllResponse); i {
+			switch v := v.(*Configuration); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -681,7 +1660,7 @@ func file_cc_arduino_cli_commands_v1_settings_proto_init() {
 			}
 		}
 		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SettingsMergeRequest); i {
+			switch v := v.(*ConfigurationGetRequest); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -693,7 +1672,7 @@ func file_cc_arduino_cli_commands_v1_settings_proto_init() {
 			}
 		}
 		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SettingsGetValueResponse); i {
+			switch v := v.(*ConfigurationGetResponse); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -705,7 +1684,7 @@ func file_cc_arduino_cli_commands_v1_settings_proto_init() {
 			}
 		}
 		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SettingsSetValueRequest); i {
+			switch v := v.(*ConfigurationSaveRequest); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -717,7 +1696,7 @@ func file_cc_arduino_cli_commands_v1_settings_proto_init() {
 			}
 		}
 		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SettingsGetAllRequest); i {
+			switch v := v.(*ConfigurationSaveResponse); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -729,7 +1708,7 @@ func file_cc_arduino_cli_commands_v1_settings_proto_init() {
 			}
 		}
 		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SettingsGetValueRequest); i {
+			switch v := v.(*ConfigurationOpenRequest); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -741,7 +1720,7 @@ func file_cc_arduino_cli_commands_v1_settings_proto_init() {
 			}
 		}
 		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SettingsMergeResponse); i {
+			switch v := v.(*ConfigurationOpenResponse); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -753,7 +1732,7 @@ func file_cc_arduino_cli_commands_v1_settings_proto_init() {
 			}
 		}
 		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SettingsSetValueResponse); i {
+			switch v := v.(*SettingsGetValueRequest); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -765,7 +1744,7 @@ func file_cc_arduino_cli_commands_v1_settings_proto_init() {
 			}
 		}
 		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SettingsWriteRequest); i {
+			switch v := v.(*SettingsGetValueResponse); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -777,7 +1756,7 @@ func file_cc_arduino_cli_commands_v1_settings_proto_init() {
 			}
 		}
 		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SettingsWriteResponse); i {
+			switch v := v.(*SettingsSetValueRequest); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -789,7 +1768,7 @@ func file_cc_arduino_cli_commands_v1_settings_proto_init() {
 			}
 		}
 		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SettingsDeleteRequest); i {
+			switch v := v.(*SettingsSetValueResponse); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -801,7 +1780,163 @@ func file_cc_arduino_cli_commands_v1_settings_proto_init() {
 			}
 		}
 		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SettingsDeleteResponse); i {
+			switch v := v.(*SettingsEnumerateRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SettingsEnumerateResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Configuration_Directories); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Configuration_Network); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Configuration_Sketch); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Configuration_BuildCache); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Configuration_BoardManager); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Configuration_Daemon); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Configuration_Output); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Configuration_Logging); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Configuration_Library); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Configuration_Updater); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Configuration_Directories_Builtin); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SettingsEnumerateResponse_Entry); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -813,13 +1948,18 @@ func file_cc_arduino_cli_commands_v1_settings_proto_init() {
 			}
 		}
 	}
+	file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[0].OneofWrappers = []interface{}{}
+	file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[13].OneofWrappers = []interface{}{}
+	file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[14].OneofWrappers = []interface{}{}
+	file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[20].OneofWrappers = []interface{}{}
+	file_cc_arduino_cli_commands_v1_settings_proto_msgTypes[23].OneofWrappers = []interface{}{}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_cc_arduino_cli_commands_v1_settings_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   12,
+			NumMessages:   25,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/rpc/cc/arduino/cli/commands/v1/settings.proto b/rpc/cc/arduino/cli/commands/v1/settings.proto
index 05a6ea16a22..c55a2c122f3 100644
--- a/rpc/cc/arduino/cli/commands/v1/settings.proto
+++ b/rpc/cc/arduino/cli/commands/v1/settings.proto
@@ -19,51 +19,149 @@ package cc.arduino.cli.commands.v1;
 
 option go_package = "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1;commands";
 
-message SettingsGetAllResponse {
-  // The settings, in JSON format.
-  string json_data = 1;
+// Configuration to apply to the given instance.
+// Any missing field will be kept at the default value.
+message Configuration {
+  message Directories {
+    message Builtin {
+      // The directory where the built-in libraries are installed
+      optional string libraries = 1;
+    }
+    // Data directory
+    string data = 1;
+    // User directory
+    string user = 2;
+    // Downloads directory
+    string downloads = 3;
+    // The directory where the built-in resources are installed
+    optional Builtin builtin = 4;
+  };
+  message Network {
+    // Extra user-agent information to be appended in network requests
+    optional string extra_user_agent = 1;
+    // The proxy to use for network requests
+    optional string proxy = 2;
+  };
+  message Sketch {
+    // Set to true to always export binaries to the sketch directory
+    bool always_export_binaries = 1;
+  }
+  message BuildCache {
+    // The minimum number of compilations before the cache is purged
+    uint64 compilations_before_purge = 1;
+    // Time to live of the cache in seconds
+    uint64 ttl_secs = 2;
+  }
+  message BoardManager {
+    // Additional URLs to be used for the board manager
+    repeated string additional_urls = 1;
+  }
+  message Daemon {
+    // The TCP port of the daemon
+    string port = 1;
+  }
+  message Output {
+    // Set to true to disable coloring of the output
+    bool no_color = 1;
+  }
+  message Logging {
+    // The logging level
+    string level = 1;
+    // The logging format
+    string format = 2;
+    // The logging file
+    optional string file = 3;
+  }
+  message Library {
+    // Set to true to enable library installation from zip archives or git
+    // repositories
+    bool enable_unsafe_install = 1;
+  }
+  message Updater {
+    // Set to true to enable notifications for updates
+    bool enable_notification = 1;
+  }
+
+  Directories directories = 1;
+  Network network = 2;
+  Sketch sketch = 3;
+  BuildCache build_cache = 4;
+  BoardManager board_manager = 5;
+  Daemon daemon = 6;
+  Output output = 7;
+  Logging logging = 8;
+  Library library = 9;
+  Updater updater = 10;
+
+  optional string locale = 100;
 }
 
-message SettingsMergeRequest {
-  // The settings, in JSON format.
-  string json_data = 1;
+message ConfigurationGetRequest {}
+
+message ConfigurationGetResponse {
+  // The current configuration
+  Configuration configuration = 1;
 }
 
-message SettingsGetValueResponse {
-  // The key of the setting.
-  string key = 1;
-  // The setting, in JSON format.
-  string json_data = 2;
+message ConfigurationSaveRequest {
+  // The format of the encoded settings, allowed values are "json" and "yaml"
+  string settings_format = 1;
 }
 
-message SettingsSetValueRequest {
-  // The key of the setting.
-  string key = 1;
-  // The setting, in JSON format.
-  string json_data = 2;
+message ConfigurationSaveResponse {
+  // The encoded settings
+  string encoded_settings = 1;
 }
 
-message SettingsGetAllRequest {}
+message ConfigurationOpenRequest {
+  // The encoded settings
+  string encoded_settings = 1;
+  // The format of the encoded settings, allowed values are "json" and "yaml"
+  string settings_format = 2;
+}
+
+message ConfigurationOpenResponse {
+  // Warnings that occurred while opening the configuration (e.g. unknown keys,
+  // or invalid values)
+  repeated string warnings = 1;
+}
 
 message SettingsGetValueRequest {
-  // The key of the setting.
+  // The key to get
   string key = 1;
+  // The format of the encoded_value (default is "json", allowed values are
+  // "json" and "yaml)
+  string value_format = 2;
 }
 
-message SettingsMergeResponse {}
+message SettingsGetValueResponse {
+  // The value of the key (encoded)
+  string encoded_value = 1;
+}
+
+message SettingsSetValueRequest {
+  // The key to change
+  string key = 1;
+  // The new value (encoded), no objects, only scalar or array of scalars are
+  // allowed.
+  string encoded_value = 2;
+  // The format of the encoded_value (default is "json", allowed values are
+  // "json", "yaml" and "cli")
+  string value_format = 3;
+}
 
 message SettingsSetValueResponse {}
 
-message SettingsWriteRequest {
-  // Path to settings file (e.g. /path/to/arduino-cli.yaml)
-  string file_path = 1;
-}
+message SettingsEnumerateRequest {}
 
-message SettingsWriteResponse {}
+message SettingsEnumerateResponse {
+  message Entry {
+    // The key
+    string key = 1;
+    // The key type
+    string type = 2;
+  }
 
-message SettingsDeleteRequest {
-  // The key of the setting to delete.
-  string key = 1;
+  // The list of key/value pairs
+  repeated Entry entries = 1;
 }
-
-message SettingsDeleteResponse {}
diff --git a/rpc/internal/client_example/main.go b/rpc/internal/client_example/main.go
index dd23b0ec8d5..9fe750f36ea 100644
--- a/rpc/internal/client_example/main.go
+++ b/rpc/internal/client_example/main.go
@@ -72,40 +72,24 @@ func main() {
 	callLoadSketch(client)
 
 	// Use SetValue to configure the arduino-cli directories.
-	log.Println("calling SetValue")
-	callSetValue(client)
+	callSetValue(client, "directories.data", `"`+dataDir+`"`)
+	callSetValue(client, "directories.downloads", `"`+path.Join(dataDir, "staging")+`"`)
+	callSetValue(client, "directories.user", `"`+path.Join(dataDir, "sketchbook")+`"`)
 
 	// List all settings
-	log.Println("calling SettingsGetAll()")
-	callGetAll(client)
+	callConfigurationSave(client)
 
 	// Merge applies multiple settings values at once.
-	log.Println("calling SettingsMerge()")
-	callMerge(client, `{"foo": {"value": "bar"}, "daemon":{"port":"422"}, "board_manager": {"additional_urls":["https://example.com"]}}`)
+	callSetValue(client, "daemon.port", `"422"`)
+	callSetValue(client, "board_manager.additional_urls", `[ "https://example.com" ]`)
 
-	log.Println("calling SettingsGetAll()")
-	callGetAll(client)
+	callConfigurationSave(client)
 
-	log.Println("calling SettingsMerge()")
-	callMerge(client, `{"foo": {} }`)
+	callSetValue(client, "daemon.port", "")
+	callGetValue(client, "daemon.port")
+	callGetValue(client, "directories.data")
 
-	log.Println("calling SettingsGetAll()")
-	callGetAll(client)
-
-	log.Println("calling SettingsMerge()")
-	callMerge(client, `{"foo": "bar" }`)
-
-	// Get the value of the foo key.
-	log.Println("calling SettingsGetValue(foo)")
-	callGetValue(client)
-
-	// List all settings
-	log.Println("calling SettingsGetAll()")
-	callGetAll(client)
-
-	// Write settings to file.
-	log.Println("calling Write()")
-	callWrite(client)
+	callConfigurationSave(client)
 
 	// Before we can do anything with the CLI, an "instance" must be created.
 	// We keep a reference to the created instance because we will need it to
@@ -244,11 +228,12 @@ func callVersion(client rpc.ArduinoCoreServiceClient) {
 	log.Printf("arduino-cli version: %v", versionResp.GetVersion())
 }
 
-func callSetValue(client rpc.ArduinoCoreServiceClient) {
+func callSetValue(client rpc.ArduinoCoreServiceClient, key, jsonValue string) {
+	log.Printf("Calling SetValue: %s = %s", key, jsonValue)
 	_, err := client.SettingsSetValue(context.Background(),
 		&rpc.SettingsSetValueRequest{
-			Key:      "directories",
-			JsonData: `{"data": "` + dataDir + `", "downloads": "` + path.Join(dataDir, "staging") + `", "user": "` + path.Join(dataDir, "sketchbook") + `"}`,
+			Key:          key,
+			EncodedValue: jsonValue,
 		})
 
 	if err != nil {
@@ -259,8 +244,8 @@ func callSetValue(client rpc.ArduinoCoreServiceClient) {
 func callSetProxy(client rpc.ArduinoCoreServiceClient) {
 	_, err := client.SettingsSetValue(context.Background(),
 		&rpc.SettingsSetValueRequest{
-			Key:      "network.proxy",
-			JsonData: `"http://localhost:3128"`,
+			Key:          "network.proxy",
+			EncodedValue: `"http://localhost:3128"`,
 		})
 
 	if err != nil {
@@ -271,8 +256,7 @@ func callSetProxy(client rpc.ArduinoCoreServiceClient) {
 func callUnsetProxy(client rpc.ArduinoCoreServiceClient) {
 	_, err := client.SettingsSetValue(context.Background(),
 		&rpc.SettingsSetValueRequest{
-			Key:      "network.proxy",
-			JsonData: `""`,
+			Key: "network.proxy",
 		})
 
 	if err != nil {
@@ -280,49 +264,30 @@ func callUnsetProxy(client rpc.ArduinoCoreServiceClient) {
 	}
 }
 
-func callMerge(client rpc.ArduinoCoreServiceClient, jsonData string) {
-	_, err := client.SettingsMerge(context.Background(),
-		&rpc.SettingsMergeRequest{
-			JsonData: jsonData,
-		})
-
-	if err != nil {
-		log.Fatalf("Error merging settings: %s", err)
-	}
-}
-
-func callGetValue(client rpc.ArduinoCoreServiceClient) {
+func callGetValue(client rpc.ArduinoCoreServiceClient, key string) {
 	getValueResp, err := client.SettingsGetValue(context.Background(),
 		&rpc.SettingsGetValueRequest{
-			Key: "foo",
+			Key: key,
 		})
 
 	if err != nil {
 		log.Fatalf("Error getting settings value: %s", err)
 	}
 
-	log.Printf("Value: %s: %s", getValueResp.GetKey(), getValueResp.GetJsonData())
+	log.Printf("%s = %s", key, getValueResp.GetEncodedValue())
 }
 
-func callGetAll(client rpc.ArduinoCoreServiceClient) {
-	getAllResp, err := client.SettingsGetAll(context.Background(), &rpc.SettingsGetAllRequest{})
+func callConfigurationSave(client rpc.ArduinoCoreServiceClient) {
+	log.Println("calling ConfigurationSave() >>")
+	getAllResp, err := client.ConfigurationSave(context.Background(), &rpc.ConfigurationSaveRequest{
+		SettingsFormat: "json",
+	})
 
 	if err != nil {
 		log.Fatalf("Error getting settings: %s", err)
 	}
 
-	log.Printf("Settings: %s", getAllResp.GetJsonData())
-}
-
-func callWrite(client rpc.ArduinoCoreServiceClient) {
-	_, err := client.SettingsWrite(context.Background(),
-		&rpc.SettingsWriteRequest{
-			FilePath: path.Join(dataDir, "written-rpc.Settingsyml"),
-		})
-
-	if err != nil {
-		log.Fatalf("Error writing settings: %s", err)
-	}
+	log.Printf("Settings follow:\n\n%s", getAllResp.GetEncodedSettings())
 }
 
 func createInstance(client rpc.ArduinoCoreServiceClient) *rpc.Instance {
diff --git a/version/version.go b/version/version.go
index 576b48d1b66..abb0f75cc20 100644
--- a/version/version.go
+++ b/version/version.go
@@ -16,9 +16,6 @@
 package version
 
 import (
-	"os"
-	"path/filepath"
-
 	"github.com/arduino/arduino-cli/internal/i18n"
 )
 
@@ -70,5 +67,5 @@ func init() {
 		versionString = defaultVersionString
 	}
 
-	VersionInfo = NewInfo(filepath.Base(os.Args[0]))
+	VersionInfo = NewInfo("arduino-cli")
 }