diff --git a/.gitignore b/.gitignore
index 20441aa155e..924c75aaadd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,4 +10,9 @@ coverage_*.txt
 __pycache__
 venv
 .pytest_cache
-/dist
\ No newline at end of file
+/dist
+
+# gRPC client example folder
+/client_example/client_example
+*.bin
+*.elf
diff --git a/README.md b/README.md
index e00d5f074b8..ceea0621852 100644
--- a/README.md
+++ b/README.md
@@ -446,3 +446,8 @@ Further help can be found in [this comment](https://github.com/arduino/arduino-c
 
 For a deeper understanding of how FQBN works, you should understand Arduino Hardware specification.
 You can find more information in this [arduino/Arduino wiki page](https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5-3rd-party-Hardware-specification)
+
+## Using the gRPC interface
+
+The [client_example](./client_example) folder contains a sample program that
+shows how to use gRPC interface of the CLI.
diff --git a/client_example/go.mod b/client_example/go.mod
new file mode 100644
index 00000000000..da515291670
--- /dev/null
+++ b/client_example/go.mod
@@ -0,0 +1,8 @@
+module github.com/arduino/arduino-cli/client_example
+
+go 1.12
+
+require (
+	github.com/arduino/arduino-cli v0.0.0-20190826141027-35722fda467d
+	google.golang.org/grpc v1.23.0
+)
diff --git a/client_example/go.sum b/client_example/go.sum
new file mode 100644
index 00000000000..1d71971c641
--- /dev/null
+++ b/client_example/go.sum
@@ -0,0 +1,124 @@
+bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg=
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/arduino/arduino-cli v0.0.0-20190826141027-35722fda467d h1:1Rwlmz0+4ofl3wKC29IlgAwb/66f2afOTUWsGgh5NLk=
+github.com/arduino/arduino-cli v0.0.0-20190826141027-35722fda467d/go.mod h1:XFDg7LwMgVGiJAkVLQ0AM3Gei5LjfuDIKwOcrU9rnsI=
+github.com/arduino/board-discovery v0.0.0-20180823133458-1ba29327fb0c/go.mod h1:HK7SpkEax/3P+0w78iRQx1sz1vCDYYw9RXwHjQTB5i8=
+github.com/arduino/go-paths-helper v0.0.0-20190214132331-c3c98d1bf2e1/go.mod h1:OGL+FS3aTrS01YsBAJJhkGuxtEGsFRSgZYo8b3vefdc=
+github.com/arduino/go-properties-orderedmap v0.0.0-20181003091528-89278049acd3/go.mod h1:kiSuHm7yz3chiy8rb2MphC7ECn3MlkQFAIe4SXmQg6o=
+github.com/arduino/go-timeutils v0.0.0-20171220113728-d1dd9e313b1b/go.mod h1:uwGy5PpN4lqW97FiLnbcx+xx8jly5YuPMJWfVwwjJiQ=
+github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b/go.mod h1:iIPnclBMYm1g32Q5kXoqng4jLhMStReIP7ZxaoUC2y8=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cmaglie/pb v1.0.27 h1:ynGj8vBXR+dtj4B7Q/W/qGt31771Ux5iFfRQBnwdQiA=
+github.com/cmaglie/pb v1.0.27/go.mod h1:GilkKZMXYjBA4NxItWFfO+lwkp59PLHQ+IOW/b/kmZI=
+github.com/codeclysm/cc v1.2.2/go.mod h1:XtW4ArCNgQwFphcRGG9+sPX5WM1J6/u0gMy5ZdV3obA=
+github.com/codeclysm/extract v2.2.0+incompatible/go.mod h1:2nhFMPHiU9At61hz+12bfrlpXSUrOnK+wR+KlGO4Uks=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/creack/goselect v0.0.0-20180328191401-176c667f75aa/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fluxio/iohelpers v0.0.0-20160419043813-3a4dd67a94d2/go.mod h1:c7sGIpDbBo0JZZ1tKyC1p5smWf8QcUjK4bFtZjHAecg=
+github.com/fluxio/multierror v0.0.0-20160419044231-9c68d39025e5/go.mod h1:BEUDl7FG1cc76sM0J0x8dqr6RhiL4uqvk6oFkwuNyuM=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gosuri/uitable v0.0.0-20160404203958-36ee7e946282/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
+github.com/h2non/filetype v1.0.8/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
+github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
+github.com/juju/testing v0.0.0-20190429233213-dfc56b8c09fc/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
+github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/miekg/dns v1.0.5/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/oleksandr/bonjour v0.0.0-20160508152359-5dcf00d8b228/go.mod h1:MGuVJ1+5TX1SCoO2Sx0eAnjpdRytYla2uC1YIZfkC9c=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmylund/sortutil v0.0.0-20120526081524-abeda66eb583/go.mod h1:sFPiU/UgDcsQVu3vkqpZLCXWFwUoQRpHGu9ATihPAl0=
+github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
+github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+go.bug.st/cleanup v1.0.0/go.mod h1:EqVmTg2IBk4znLbPD28xne3abjsJftMdqqJEjhn70bk=
+go.bug.st/downloader v1.1.0 h1:LipC9rqRCe8kwa+ah3ZDfCqneVaf34cB/TKjXZiZt54=
+go.bug.st/downloader v1.1.0/go.mod h1:l+RPbNbrTB+MoAIp8nrZsP22nRPDy26XJZQqmm4gNT4=
+go.bug.st/relaxed-semver v0.0.0-20181022103824-0265409c5852/go.mod h1:WWVH9tve4kargu9fsX18qW/UHxE37QcgPXRtE/xSvxY=
+go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45/go.mod h1:dRSl/CVCTf56CkXgJMDOdSwNfo2g1orOGE/gBGdvjZw=
+golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190327125643-d831d65fe17d h1:XB2jc5XQ9uhizGTS2vWcN01bc4dI6z3C4KY5MQm8SS8=
+google.golang.org/genproto v0.0.0-20190327125643-d831d65fe17d/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/client_example/hello.ino b/client_example/hello.ino
new file mode 100644
index 00000000000..df272bd539c
--- /dev/null
+++ b/client_example/hello.ino
@@ -0,0 +1,7 @@
+void setup() {
+  // put your setup code here, to run once:
+}
+
+void loop() {
+  // put your main code here, to run repeatedly:
+}
\ No newline at end of file
diff --git a/client_example/main.go b/client_example/main.go
new file mode 100644
index 00000000000..5008efd96fd
--- /dev/null
+++ b/client_example/main.go
@@ -0,0 +1,714 @@
+// This file is part of arduino-cli.
+//
+// Copyright 2019 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 main
+
+import (
+	"context"
+	"io"
+	"io/ioutil"
+	"log"
+	"os"
+	"path/filepath"
+	"time"
+
+	rpc "github.com/arduino/arduino-cli/rpc/commands"
+	"google.golang.org/grpc"
+)
+
+var (
+	dataDir string
+)
+
+// The main function implements an example workflow to show how to interact
+// with the gRPC Api exposed by arduino-cli when running in daemon mode.
+func main() {
+
+	// Establish a connection with the gRPC server, started with the command:
+	// arduino-cli daemon
+	conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure(), grpc.WithBlock(), grpc.WithTimeout(100*time.Millisecond))
+	if err != nil {
+		log.Fatal("error connecting to arduino-cli rpc server, you can start it by running `arduino-cli daemon`")
+	}
+	defer conn.Close()
+
+	// To avoid polluting an existing arduino-cli installation, the example
+	// client uses a temp folder to keep cores, libraries and the likes.
+	// You can point `dataDir` to a location that better fits your needs.
+	dataDir, err = ioutil.TempDir("", "arduino-rpc-client")
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer os.RemoveAll(dataDir)
+
+	// Create an instance of the gRPC client.
+	client := rpc.NewArduinoCoreClient(conn)
+
+	// Now we can call various methods of the API...
+
+	// `Version` can be called without any setup or init procedure.
+	log.Println("calling Version")
+	callVersion(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
+	// run subsequent commands.
+	log.Println("calling Init")
+	instance := initInstance(client)
+
+	// With a brand new instance, the first operation should always be updatating
+	// the index.
+	log.Println("calling UpdateIndex")
+	callUpdateIndex(client, instance)
+
+	// Let's search for a platform (also known as 'core') called 'samd'.
+	log.Println("calling PlatformSearch(samd)")
+	callPlatformSearch(client, instance)
+
+	// Install arduino:samd@1.6.19
+	log.Println("calling PlatformInstall(arduino:samd@1.6.19)")
+	callPlatformInstall(client, instance)
+
+	// Now list the installed platforms to double check previous installation
+	// went right.
+	log.Println("calling PlatformList()")
+	callPlatformList(client, instance)
+
+	// Upgrade the installed platform to the latest version.
+	log.Println("calling PlatformUpgrade(arduino:samd)")
+	callPlatformUpgrade(client, instance)
+
+	// Query board details for a mkr1000
+	log.Println("calling BoardDetails(arduino:samd:mkr1000)")
+	callBoardsDetails(client, instance)
+
+	// Attach a board to a sketch.
+	// Uncomment if you do have an actual board connected.
+	// log.Println("calling BoardAttach(serial:///dev/ttyACM0)")
+	// callBoardAttach(client, instance)
+
+	// Compile a sketch
+	log.Println("calling Compile(arduino:samd:mkr1000, VERBOSE, hello.ino)")
+	callCompile(client, instance)
+
+	// Upload a sketch
+	// Uncomment if you do have an actual board connected.
+	// log.Println("calling Upload(arduino:samd:mkr1000, /dev/ttyACM0, VERBOSE, hello.ino)")
+	// callUpload(client, instance)
+
+	// List all boards
+	log.Println("calling BoardListAll(mkr)")
+	callListAll(client, instance)
+
+	// List connected boards
+	log.Println("calling BoardList()")
+	callBoardList(client, instance)
+
+	// Uninstall a platform
+	log.Println("calling PlatformUninstall(arduino:samd)")
+	callPlatformUnInstall(client, instance)
+
+	// Update the Library index
+	log.Println("calling UpdateLibrariesIndex()")
+	callUpdateLibraryIndex(client, instance)
+
+	// Download a library
+	log.Println("calling LibraryDownload(WiFi101@0.15.2)")
+	callLibDownload(client, instance)
+
+	// Install a library
+	log.Println("calling LibraryInstall(WiFi101@0.15.1)")
+	callLibInstall(client, instance, "0.15.1")
+
+	// Replace the previous version
+	log.Println("calling LibraryInstall(WiFi101@0.15.2)")
+	callLibInstall(client, instance, "0.15.2")
+
+	// Upgrade all libs to latest
+	log.Println("calling LibraryUpgradeAll()")
+	callLibUpgradeAll(client, instance)
+
+	// Search for a lib using the 'audio' keyword
+	log.Println("calling LibrarySearch(audio)")
+	callLibSearch(client, instance)
+
+	// List installed libraries
+	log.Println("calling LibraryList")
+	callLibList(client, instance)
+
+	// Uninstall a library
+	log.Println("calling LibraryUninstall(WiFi101)")
+	callLibUninstall(client, instance)
+}
+
+func callVersion(client rpc.ArduinoCoreClient) {
+	versionResp, err := client.Version(context.Background(), &rpc.VersionReq{})
+	if err != nil {
+		log.Fatalf("Error getting version: %s", err)
+	}
+
+	log.Printf("arduino-cli version: %v", versionResp.GetVersion())
+}
+
+func initInstance(client rpc.ArduinoCoreClient) *rpc.Instance {
+	// The configuration for this example client only contains the path to
+	// the data folder.
+	initRespStream, err := client.Init(context.Background(), &rpc.InitReq{
+		Configuration: &rpc.Configuration{
+			DataDir: dataDir,
+		},
+	})
+	if err != nil {
+		log.Fatalf("Error creating server instance: %s", err)
+
+	}
+
+	var instance *rpc.Instance
+	// Loop and consume the server stream until all the setup procedures are done.
+	for {
+		initResp, err := initRespStream.Recv()
+		// The server is done.
+		if err == io.EOF {
+			break
+		}
+
+		// There was an error.
+		if err != nil {
+			log.Fatalf("Init error: %s", err)
+		}
+
+		// The server sent us a valid instance, let's print its ID.
+		if initResp.GetInstance() != nil {
+			instance = initResp.GetInstance()
+			log.Printf("Got a new instance with ID: %v", instance.GetId())
+		}
+
+		// When a download is ongoing, log the progress
+		if initResp.GetDownloadProgress() != nil {
+			log.Printf("DOWNLOAD: %s", initResp.GetDownloadProgress())
+		}
+
+		// When an overall task is ongoing, log the progress
+		if initResp.GetTaskProgress() != nil {
+			log.Printf("TASK: %s", initResp.GetTaskProgress())
+		}
+	}
+
+	return instance
+}
+
+func callUpdateIndex(client rpc.ArduinoCoreClient, instance *rpc.Instance) {
+	uiRespStream, err := client.UpdateIndex(context.Background(), &rpc.UpdateIndexReq{
+		Instance: instance,
+	})
+	if err != nil {
+		log.Fatalf("Error updating index: %s", err)
+	}
+
+	// Loop and consume the server stream until all the operations are done.
+	for {
+		uiResp, err := uiRespStream.Recv()
+
+		// the server is done
+		if err == io.EOF {
+			log.Print("Update index done")
+			break
+		}
+
+		// there was an error
+		if err != nil {
+			log.Fatalf("Update error: %s", err)
+		}
+
+		// operations in progress
+		if uiResp.GetDownloadProgress() != nil {
+			log.Printf("DOWNLOAD: %s", uiResp.GetDownloadProgress())
+		}
+	}
+}
+
+func callPlatformSearch(client rpc.ArduinoCoreClient, instance *rpc.Instance) {
+	searchResp, err := client.PlatformSearch(context.Background(), &rpc.PlatformSearchReq{
+		Instance:   instance,
+		SearchArgs: "samd",
+	})
+
+	if err != nil {
+		log.Fatalf("Search error: %s", err)
+	}
+
+	platforms := searchResp.GetSearchOutput()
+	for _, plat := range platforms {
+		// We only print ID and version of the platforms found but you can look
+		// at the definition for the rpc.Platform struct for more fields.
+		log.Printf("Search result: %+v - %+v", plat.GetID(), plat.GetLatest())
+	}
+}
+
+func callPlatformInstall(client rpc.ArduinoCoreClient, instance *rpc.Instance) {
+	installRespStream, err := client.PlatformInstall(context.Background(),
+		&rpc.PlatformInstallReq{
+			Instance:        instance,
+			PlatformPackage: "arduino",
+			Architecture:    "samd",
+			Version:         "1.6.19",
+		})
+
+	if err != nil {
+		log.Fatalf("Error installing platform: %s", err)
+	}
+
+	// Loop and consume the server stream until all the operations are done.
+	for {
+		installResp, err := installRespStream.Recv()
+
+		// The server is done.
+		if err == io.EOF {
+			log.Printf("Install done")
+			break
+		}
+
+		// There was an error.
+		if err != nil {
+			log.Fatalf("Install error: %s", err)
+		}
+
+		// When a download is ongoing, log the progress
+		if installResp.GetProgress() != nil {
+			log.Printf("DOWNLOAD: %s", installResp.GetProgress())
+		}
+
+		// When an overall task is ongoing, log the progress
+		if installResp.GetTaskProgress() != nil {
+			log.Printf("TASK: %s", installResp.GetTaskProgress())
+		}
+	}
+}
+
+func callPlatformList(client rpc.ArduinoCoreClient, instance *rpc.Instance) {
+	listResp, err := client.PlatformList(context.Background(),
+		&rpc.PlatformListReq{Instance: instance})
+
+	if err != nil {
+		log.Fatalf("List error: %s", err)
+	}
+
+	for _, plat := range listResp.GetInstalledPlatform() {
+		// We only print ID and version of the installed platforms but you can look
+		// at the definition for the rpc.Platform struct for more fields.
+		log.Printf("Installed platform: %s - %s", plat.GetID(), plat.GetInstalled())
+	}
+}
+
+func callPlatformUpgrade(client rpc.ArduinoCoreClient, instance *rpc.Instance) {
+	upgradeRespStream, err := client.PlatformUpgrade(context.Background(),
+		&rpc.PlatformUpgradeReq{
+			Instance:        instance,
+			PlatformPackage: "arduino",
+			Architecture:    "samd",
+		})
+
+	if err != nil {
+		log.Fatalf("Error upgrading platform: %s", err)
+	}
+
+	// Loop and consume the server stream until all the operations are done.
+	for {
+		upgradeResp, err := upgradeRespStream.Recv()
+
+		// The server is done.
+		if err == io.EOF {
+			log.Printf("Upgrade done")
+			break
+		}
+
+		// There was an error.
+		if err != nil {
+			log.Fatalf("Upgrade error: %s", err)
+		}
+
+		// When a download is ongoing, log the progress
+		if upgradeResp.GetProgress() != nil {
+			log.Printf("DOWNLOAD: %s", upgradeResp.GetProgress())
+		}
+
+		// When an overall task is ongoing, log the progress
+		if upgradeResp.GetTaskProgress() != nil {
+			log.Printf("TASK: %s", upgradeResp.GetTaskProgress())
+		}
+	}
+}
+
+func callBoardsDetails(client rpc.ArduinoCoreClient, instance *rpc.Instance) {
+	details, err := client.BoardDetails(context.Background(),
+		&rpc.BoardDetailsReq{
+			Instance: instance,
+			Fqbn:     "arduino:samd:mkr1000",
+		})
+
+	if err != nil {
+		log.Fatalf("Error getting board data: %s\n", err)
+	}
+
+	log.Printf("Board details for %s", details.GetName())
+	log.Printf("Required tools: %s", details.GetRequiredTools())
+	log.Printf("Config options: %s", details.GetConfigOptions())
+}
+
+func callBoardAttach(client rpc.ArduinoCoreClient, instance *rpc.Instance) {
+	boardattachresp, err := client.BoardAttach(context.Background(),
+		&rpc.BoardAttachReq{
+			Instance:   instance,
+			BoardUri:   "/dev/ttyACM0",
+			SketchPath: filepath.Join(dataDir, "hello.ino"),
+		})
+
+	if err != nil {
+		log.Fatalf("Attach error: %s", err)
+	}
+
+	// Loop and consume the server stream until all the operations are done.
+	for {
+		attachResp, err := boardattachresp.Recv()
+
+		// The server is done.
+		if err == io.EOF {
+			log.Print("Attach done")
+			break
+		}
+
+		// There was an error.
+		if err != nil {
+			log.Fatalf("Attach error: %s\n", err)
+		}
+
+		// When an overall task is ongoing, log the progress
+		if attachResp.GetTaskProgress() != nil {
+			log.Printf("TASK: %s", attachResp.GetTaskProgress())
+		}
+	}
+}
+
+func callCompile(client rpc.ArduinoCoreClient, instance *rpc.Instance) {
+	compRespStream, err := client.Compile(context.Background(),
+		&rpc.CompileReq{
+			Instance:   instance,
+			Fqbn:       "arduino:samd:mkr1000",
+			SketchPath: "hello.ino",
+			Verbose:    true,
+		})
+
+	if err != nil {
+		log.Fatalf("Compile error: %s\n", err)
+	}
+
+	// Loop and consume the server stream until all the operations are done.
+	for {
+		compResp, err := compRespStream.Recv()
+
+		// The server is done.
+		if err == io.EOF {
+			log.Print("Compilation done")
+			break
+		}
+
+		// There was an error.
+		if err != nil {
+			log.Fatalf("Compile error: %s\n", err)
+		}
+
+		// When an operation is ongoing you can get its output
+		if resp := compResp.GetOutStream(); resp != nil {
+			log.Printf("STDOUT: %s", resp)
+		}
+		if resperr := compResp.GetErrStream(); resperr != nil {
+			log.Printf("STDERR: %s", resperr)
+		}
+	}
+}
+
+func callUpload(client rpc.ArduinoCoreClient, instance *rpc.Instance) {
+	uplRespStream, err := client.Upload(context.Background(),
+		&rpc.UploadReq{
+			Instance:   instance,
+			Fqbn:       "arduino:samd:mkr1000",
+			SketchPath: "hello.ino",
+			Port:       "/dev/ttyACM0",
+			Verbose:    true,
+		})
+
+	if err != nil {
+		log.Fatalf("Upload error: %s\n", err)
+	}
+
+	for {
+		uplResp, err := uplRespStream.Recv()
+		if err == io.EOF {
+			log.Printf("Upload done")
+			break
+		}
+
+		if err != nil {
+			log.Fatalf("Upload error: %s", err)
+			break
+		}
+
+		// When an operation is ongoing you can get its output
+		if resp := uplResp.GetOutStream(); resp != nil {
+			log.Printf("STDOUT: %s", resp)
+		}
+		if resperr := uplResp.GetErrStream(); resperr != nil {
+			log.Printf("STDERR: %s", resperr)
+		}
+	}
+}
+
+func callListAll(client rpc.ArduinoCoreClient, instance *rpc.Instance) {
+	boardListAllResp, err := client.BoardListAll(context.Background(),
+		&rpc.BoardListAllReq{
+			Instance:   instance,
+			SearchArgs: []string{"mkr"},
+		})
+
+	if err != nil {
+		log.Fatalf("Board list-all error: %s", err)
+	}
+
+	for _, board := range boardListAllResp.GetBoards() {
+		log.Printf("%s: %s", board.GetName(), board.GetFQBN())
+	}
+}
+
+func callBoardList(client rpc.ArduinoCoreClient, instance *rpc.Instance) {
+	boardListResp, err := client.BoardList(context.Background(),
+		&rpc.BoardListReq{Instance: instance})
+
+	if err != nil {
+		log.Fatalf("Board list error: %s\n", err)
+	}
+
+	for _, port := range boardListResp.GetPorts() {
+		log.Printf("port: %s, boards: %+v\n", port.GetAddress(), port.GetBoards())
+	}
+}
+
+func callPlatformUnInstall(client rpc.ArduinoCoreClient, instance *rpc.Instance) {
+	uninstallRespStream, err := client.PlatformUninstall(context.Background(),
+		&rpc.PlatformUninstallReq{
+			Instance:        instance,
+			PlatformPackage: "arduino",
+			Architecture:    "samd",
+		})
+
+	if err != nil {
+		log.Fatalf("Uninstall error: %s", err)
+	}
+
+	// Loop and consume the server stream until all the operations are done.
+	for {
+		uninstallResp, err := uninstallRespStream.Recv()
+		if err == io.EOF {
+			log.Print("Uninstall done")
+			break
+		}
+
+		if err != nil {
+			log.Fatalf("Uninstall error: %s\n", err)
+		}
+
+		if uninstallResp.GetTaskProgress() != nil {
+			log.Printf("TASK: %s\n", uninstallResp.GetTaskProgress())
+		}
+	}
+}
+
+func callUpdateLibraryIndex(client rpc.ArduinoCoreClient, instance *rpc.Instance) {
+	libIdxUpdateStream, err := client.UpdateLibrariesIndex(context.Background(),
+		&rpc.UpdateLibrariesIndexReq{Instance: instance})
+
+	if err != nil {
+		log.Fatalf("Error updating libraries index: %s\n", err)
+	}
+
+	// Loop and consume the server stream until all the operations are done.
+	for {
+		resp, err := libIdxUpdateStream.Recv()
+		if err == io.EOF {
+			log.Print("Library index update done")
+			break
+		}
+
+		if err != nil {
+			log.Fatalf("Error updating libraries index: %s", err)
+		}
+
+		if resp.GetDownloadProgress() != nil {
+			log.Printf("DOWNLOAD: %s", resp.GetDownloadProgress())
+		}
+	}
+}
+
+func callLibDownload(client rpc.ArduinoCoreClient, instance *rpc.Instance) {
+	downloadRespStream, err := client.LibraryDownload(context.Background(),
+		&rpc.LibraryDownloadReq{
+			Instance: instance,
+			Name:     "WiFi101",
+			Version:  "0.15.2",
+		})
+
+	if err != nil {
+		log.Fatalf("Error downloading library: %s", err)
+	}
+
+	// Loop and consume the server stream until all the operations are done.
+	for {
+		downloadResp, err := downloadRespStream.Recv()
+		if err == io.EOF {
+			log.Print("Lib download done")
+			break
+		}
+
+		if err != nil {
+			log.Fatalf("Download error: %s", err)
+		}
+
+		if downloadResp.GetProgress() != nil {
+			log.Printf("DOWNLOAD: %s", downloadResp.GetProgress())
+		}
+	}
+}
+
+func callLibInstall(client rpc.ArduinoCoreClient, instance *rpc.Instance, version string) {
+	installRespStream, err := client.LibraryInstall(context.Background(),
+		&rpc.LibraryInstallReq{
+			Instance: instance,
+			Name:     "WiFi101",
+			Version:  version,
+		})
+
+	if err != nil {
+		log.Fatalf("Error installing library: %s", err)
+	}
+
+	for {
+		installResp, err := installRespStream.Recv()
+		if err == io.EOF {
+			log.Print("Lib install done")
+			break
+		}
+
+		if err != nil {
+			log.Fatalf("Install error: %s", err)
+		}
+
+		if installResp.GetProgress() != nil {
+			log.Printf("DOWNLOAD: %s\n", installResp.GetProgress())
+		}
+		if installResp.GetTaskProgress() != nil {
+			log.Printf("TASK: %s\n", installResp.GetTaskProgress())
+		}
+	}
+}
+
+func callLibUpgradeAll(client rpc.ArduinoCoreClient, instance *rpc.Instance) {
+	libUpgradeAllRespStream, err := client.LibraryUpgradeAll(context.Background(),
+		&rpc.LibraryUpgradeAllReq{
+			Instance: instance,
+		})
+
+	if err != nil {
+		log.Fatalf("Error upgrading all: %s\n", err)
+	}
+
+	for {
+		resp, err := libUpgradeAllRespStream.Recv()
+		if err == io.EOF {
+			log.Printf("Lib upgrade all done")
+			break
+		}
+
+		if err != nil {
+			log.Fatalf("Upgrading error: %s", err)
+		}
+
+		if resp.GetProgress() != nil {
+			log.Printf("DOWNLOAD: %s\n", resp.GetProgress())
+		}
+		if resp.GetTaskProgress() != nil {
+			log.Printf("TASK: %s\n", resp.GetTaskProgress())
+		}
+	}
+}
+
+func callLibSearch(client rpc.ArduinoCoreClient, instance *rpc.Instance) {
+	libSearchResp, err := client.LibrarySearch(context.Background(),
+		&rpc.LibrarySearchReq{
+			Instance: instance,
+			Query:    "audio",
+		})
+
+	if err != nil {
+		log.Fatalf("Error searching for library: %s", err)
+	}
+
+	for _, res := range libSearchResp.GetLibraries() {
+		log.Printf("Result: %s - %s", res.GetName(), res.GetLatest().GetVersion())
+	}
+}
+
+func callLibList(client rpc.ArduinoCoreClient, instance *rpc.Instance) {
+	libLstResp, err := client.LibraryList(context.Background(),
+		&rpc.LibraryListReq{
+			Instance:  instance,
+			All:       false,
+			Updatable: false,
+		})
+
+	if err != nil {
+		log.Fatalf("Error List Library: %s", err)
+	}
+
+	for _, res := range libLstResp.GetInstalledLibrary() {
+		log.Printf("%s - %s", res.GetLibrary().GetName(), res.GetLibrary().GetVersion())
+	}
+}
+
+func callLibUninstall(client rpc.ArduinoCoreClient, instance *rpc.Instance) {
+	libUninstallRespStream, err := client.LibraryUninstall(context.Background(),
+		&rpc.LibraryUninstallReq{
+			Instance: instance,
+			Name:     "WiFi101",
+		})
+
+	if err != nil {
+		log.Fatalf("Error uninstalling: %s", err)
+	}
+
+	for {
+		uninstallResp, err := libUninstallRespStream.Recv()
+		if err == io.EOF {
+			log.Printf("Lib uninstall done")
+			break
+		}
+
+		if err != nil {
+			log.Fatalf("Uninstall error: %s", err)
+		}
+
+		if uninstallResp.GetTaskProgress() != nil {
+			log.Printf("TASK: %s", uninstallResp.GetTaskProgress())
+		}
+	}
+}
diff --git a/commands/daemon/client_test.go b/commands/daemon/client_test.go
deleted file mode 100644
index 75a78cdef67..00000000000
--- a/commands/daemon/client_test.go
+++ /dev/null
@@ -1,549 +0,0 @@
-//
-// This file is part of arduino-cli.
-//
-// Copyright 2018 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_test
-
-import (
-	"context"
-	"fmt"
-	"io"
-	"io/ioutil"
-	"os"
-	"testing"
-	"time"
-
-	rpc "github.com/arduino/arduino-cli/rpc/commands"
-	"google.golang.org/grpc"
-)
-
-func TestWithClientE2E(t *testing.T) {
-	if testing.Short() {
-		t.Skip("rpc client test must be run manually, skip...")
-	}
-
-	conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure(), grpc.WithBlock(), grpc.WithTimeout(100*time.Millisecond))
-	if err != nil {
-		t.Skip("error connecting to arduino-cli rpc server, skip...")
-	}
-	defer conn.Close()
-
-	datadir, err := ioutil.TempDir("", "arduino-rpc-client")
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer os.RemoveAll(datadir)
-
-	client := rpc.NewArduinoCoreClient(conn)
-
-	// VERSION
-	fmt.Println("=== calling Version")
-	versionResp, err := client.Version(context.Background(), &rpc.VersionReq{})
-	if err != nil {
-		fmt.Printf("Error getting version: %s\n", err)
-		os.Exit(1)
-	}
-	fmt.Printf("---> %+v\n\v", versionResp)
-
-	// INIT
-	fmt.Println("=== calling Init")
-	initRespStream, err := client.Init(context.Background(), &rpc.InitReq{
-		Configuration: &rpc.Configuration{
-			DataDir: datadir,
-		},
-	})
-	if err != nil {
-		fmt.Printf("Error creating server instance: %s\n", err)
-		os.Exit(1)
-	}
-	var instance *rpc.Instance
-	for {
-		initResp, err := initRespStream.Recv()
-		if err == io.EOF {
-			fmt.Println()
-			break
-		}
-		if err != nil {
-			fmt.Printf("Init error: %s\n", err)
-			os.Exit(1)
-		}
-		if initResp.GetInstance() != nil {
-			instance = initResp.GetInstance()
-			fmt.Printf("---> %+v\n", initResp)
-		}
-		if initResp.GetDownloadProgress() != nil {
-			fmt.Printf(">> DOWNLOAD: %s\n", initResp.GetDownloadProgress())
-		}
-		if initResp.GetTaskProgress() != nil {
-			fmt.Printf(">> TASK: %s\n", initResp.GetTaskProgress())
-		}
-	}
-
-	// UPDATE-INDEX
-	fmt.Println("=== calling UpdateIndex")
-	uiRespStream, err := client.UpdateIndex(context.Background(), &rpc.UpdateIndexReq{
-		Instance: instance,
-	})
-	if err != nil {
-		fmt.Printf("Error updating index: %s\n", err)
-		os.Exit(1)
-	}
-	for {
-		uiResp, err := uiRespStream.Recv()
-		if err == io.EOF {
-			fmt.Printf("---> %+v\n", uiResp)
-			fmt.Println()
-			break
-		}
-		if err != nil {
-			fmt.Printf("Update error: %s\n", err)
-			os.Exit(1)
-		}
-		if uiResp.GetDownloadProgress() != nil {
-			fmt.Printf(">> DOWNLOAD: %s\n", uiResp.GetDownloadProgress())
-		}
-	}
-
-	// PLATFORM SEARCH
-	fmt.Println("=== calling PlatformSearch(samd)")
-	searchResp, err := client.PlatformSearch(context.Background(), &rpc.PlatformSearchReq{
-		Instance:   instance,
-		SearchArgs: "uno",
-	})
-	if err != nil {
-		fmt.Printf("Search error: %s\n", err)
-		os.Exit(1)
-	}
-	serchedOutput := searchResp.GetSearchOutput()
-	for _, outsearch := range serchedOutput {
-		fmt.Printf(">> SEARCH: %+v\n", outsearch)
-	}
-	fmt.Printf("---> %+v\n", searchResp)
-	fmt.Println()
-
-	// PLATFORM INSTALL
-	install := func() {
-		fmt.Println("=== calling PlatformInstall(arduino:samd@1.6.19)")
-		installRespStream, err := client.PlatformInstall(context.Background(), &rpc.PlatformInstallReq{
-			Instance:        instance,
-			PlatformPackage: "arduino",
-			Architecture:    "samd",
-			Version:         "1.6.19",
-		})
-		if err != nil {
-			fmt.Printf("Error installing platform: %s\n", err)
-			os.Exit(1)
-		}
-		for {
-			installResp, err := installRespStream.Recv()
-			if err == io.EOF {
-				fmt.Printf("---> %+v\n", installResp)
-				fmt.Println()
-				break
-			}
-			if err != nil {
-				fmt.Printf("Install error: %s\n", err)
-				os.Exit(1)
-			}
-			if installResp.GetProgress() != nil {
-				fmt.Printf(">> DOWNLOAD: %s\n", installResp.GetProgress())
-			}
-			if installResp.GetTaskProgress() != nil {
-				fmt.Printf(">> TASK: %s\n", installResp.GetTaskProgress())
-			}
-		}
-	}
-
-	install()
-	install()
-
-	// PLATFORM LIST
-	fmt.Println("=== calling PlatformList()")
-	listResp, err := client.PlatformList(context.Background(), &rpc.PlatformListReq{
-		Instance: instance,
-	})
-	if err != nil {
-		fmt.Printf("List error: %s\n", err)
-		os.Exit(1)
-	}
-	Installedplatforms := listResp.GetInstalledPlatform()
-	for _, listpfm := range Installedplatforms {
-		fmt.Printf("---> LIST: %+v\n", listpfm)
-	}
-	fmt.Println()
-
-	// PLATFORM UPGRADE
-	fmt.Println("=== calling PlatformUpgrade(arduino:samd)")
-	upgradeRespStream, err := client.PlatformUpgrade(context.Background(), &rpc.PlatformUpgradeReq{
-		Instance:        instance,
-		PlatformPackage: "arduino",
-		Architecture:    "samd",
-	})
-	if err != nil {
-		fmt.Printf("Error upgrading platform: %s\n", err)
-		os.Exit(1)
-	}
-	for {
-		upgradeResp, err := upgradeRespStream.Recv()
-		if err == io.EOF {
-			fmt.Printf("---> %+v\n", upgradeResp)
-			fmt.Println()
-			break
-		}
-		if err != nil {
-			fmt.Printf("Upgrade error: %s\n", err)
-			os.Exit(1)
-		}
-		if upgradeResp.GetProgress() != nil {
-			fmt.Printf(">> DOWNLOAD: %s\n", upgradeResp.GetProgress())
-		}
-		if upgradeResp.GetTaskProgress() != nil {
-			fmt.Printf(">> TASK: %s\n", upgradeResp.GetTaskProgress())
-		}
-	}
-
-	// BOARDS DETAILS
-	fmt.Println("=== calling BoardDetails(arduino:samd:mkr1000)")
-	details, err := client.BoardDetails(context.Background(), &rpc.BoardDetailsReq{
-		Instance: instance,
-		Fqbn:     "arduino:samd:mkr1000",
-	})
-	if err != nil {
-		fmt.Printf("Error getting board data: %s\n", err)
-	}
-	fmt.Printf("---> %+v\n", details)
-	fmt.Println()
-
-	// BOARDS ATTACH
-	fmt.Println("=== calling BoardAttach(serial:///dev/ttyACM0)")
-	boardattachresp, err := client.BoardAttach(context.Background(), &rpc.BoardAttachReq{
-		Instance:   instance,
-		BoardUri:   "/dev/ttyACM0",
-		SketchPath: os.Args[2],
-	})
-	if err != nil {
-		fmt.Printf("Attach error: %s\n", err)
-		os.Exit(1)
-	}
-	for {
-		attachResp, err := boardattachresp.Recv()
-		if err == io.EOF {
-			fmt.Printf("---> %+v\n", attachResp)
-			fmt.Println()
-			break
-		}
-		if err != nil {
-			fmt.Printf("Attach error: %s\n", err)
-			os.Exit(1)
-		}
-		if attachResp.GetTaskProgress() != nil {
-			fmt.Printf(">> TASK: %s\n", attachResp.GetTaskProgress())
-		}
-	}
-
-	// COMPILE
-	fmt.Println("=== calling Compile(arduino:samd:mkr1000, VERBOSE, " + os.Args[2] + ")")
-	compRespStream, err := client.Compile(context.Background(), &rpc.CompileReq{
-		Instance:   instance,
-		Fqbn:       "arduino:samd:mkr1000",
-		SketchPath: os.Args[2],
-		Verbose:    true,
-	})
-	if err != nil {
-		fmt.Printf("Compile error: %s\n", err)
-		os.Exit(1)
-	}
-	for {
-		compResp, err := compRespStream.Recv()
-		if err == io.EOF {
-			fmt.Printf("---> %+v\n", compResp)
-			fmt.Println()
-			break
-		}
-		if err != nil {
-			fmt.Printf("Compile error: %s\n", err)
-			os.Exit(1)
-		}
-		if resp := compResp.GetOutStream(); resp != nil {
-			fmt.Printf(">> STDOUT: %s", resp)
-		}
-		if resperr := compResp.GetErrStream(); resperr != nil {
-			fmt.Printf(">> STDERR: %s", resperr)
-		}
-	}
-
-	// UPLOAD
-	fmt.Println("=== calling Upload(arduino:samd:mkr1000, /dev/ttyACM0, VERBOSE, " + os.Args[2] + ")")
-	uplRespStream, err := client.Upload(context.Background(), &rpc.UploadReq{
-		Instance:   instance,
-		Fqbn:       "arduino:samd:mkr1000",
-		SketchPath: os.Args[2],
-		Port:       "/dev/ttyACM0",
-		Verbose:    true,
-	})
-	if err != nil {
-		fmt.Printf("Upload error: %s\n", err)
-		os.Exit(1)
-	}
-	for {
-		uplResp, err := uplRespStream.Recv()
-		if err == io.EOF {
-			fmt.Printf("---> %+v\n", uplResp)
-			fmt.Println()
-			break
-		}
-		if err != nil {
-			fmt.Printf("Upload error: %s\n", err)
-			// os.Exit(1)
-			break
-		}
-		if resp := uplResp.GetOutStream(); resp != nil {
-			fmt.Printf(">> STDOUT: %s", resp)
-		}
-		if resperr := uplResp.GetErrStream(); resperr != nil {
-			fmt.Printf(">> STDERR: %s", resperr)
-		}
-	}
-
-	// BOARD LIST ALL
-	fmt.Println("=== calling BoardListAll(mkr)")
-	boardListAllResp, err := client.BoardListAll(context.Background(), &rpc.BoardListAllReq{
-		Instance:   instance,
-		SearchArgs: []string{"mkr"},
-	})
-	if err != nil {
-		fmt.Printf("Board list-all error: %s\n", err)
-		os.Exit(1)
-	}
-	fmt.Printf("---> %+v\n", boardListAllResp)
-	fmt.Println()
-
-	// BOARD LIST
-	fmt.Println("=== calling BoardList()")
-	boardListResp, err := client.BoardList(context.Background(), &rpc.BoardListReq{Instance: instance})
-	if err != nil {
-		fmt.Printf("Board list error: %s\n", err)
-		os.Exit(1)
-	}
-	fmt.Printf("---> %+v\n", boardListResp)
-	fmt.Println()
-
-	// PLATFORM UNINSTALL
-	fmt.Println("=== calling PlatformUninstall(arduino:samd)")
-	uninstallRespStream, err := client.PlatformUninstall(context.Background(), &rpc.PlatformUninstallReq{
-		Instance:        instance,
-		PlatformPackage: "arduino",
-		Architecture:    "samd",
-	})
-	if err != nil {
-		fmt.Printf("Uninstall error: %s\n", err)
-		os.Exit(1)
-	}
-	for {
-		uninstallResp, err := uninstallRespStream.Recv()
-		if err == io.EOF {
-			fmt.Printf("---> %+v\n", uninstallResp)
-			fmt.Println()
-			break
-		}
-		if err != nil {
-			fmt.Printf("Uninstall error: %s\n", err)
-			os.Exit(1)
-		}
-		if uninstallResp.GetTaskProgress() != nil {
-			fmt.Printf(">> TASK: %s\n", uninstallResp.GetTaskProgress())
-		}
-	}
-
-	// LIB UPDATE INDEX
-	fmt.Println("=== calling UpdateLibrariesIndex()")
-	libIdxUpdateStream, err := client.UpdateLibrariesIndex(context.Background(), &rpc.UpdateLibrariesIndexReq{Instance: instance})
-	if err != nil {
-		fmt.Printf("Error updating libraries index: %s\n", err)
-		os.Exit(1)
-	}
-	for {
-		resp, err := libIdxUpdateStream.Recv()
-		if err == io.EOF {
-			fmt.Printf("---> %+v\n", resp)
-			fmt.Println()
-			break
-		}
-		if err != nil {
-			fmt.Printf("Error updating libraries index: %s\n", err)
-			os.Exit(1)
-		}
-		if resp.GetDownloadProgress() != nil {
-			fmt.Printf(">> DOWNLOAD: %s\n", resp.GetDownloadProgress())
-		}
-	}
-
-	// LIB DOWNLOAD
-	fmt.Println("=== calling LibraryDownload(WiFi101@0.15.2)")
-	downloadRespStream, err := client.LibraryDownload(context.Background(), &rpc.LibraryDownloadReq{
-		Instance: instance,
-		Name:     "WiFi101",
-		Version:  "0.15.2",
-	})
-	if err != nil {
-		fmt.Printf("Error downloading library: %s\n", err)
-		os.Exit(1)
-	}
-	for {
-		downloadResp, err := downloadRespStream.Recv()
-		if err == io.EOF {
-			fmt.Printf("---> %+v\n", downloadResp)
-			fmt.Println()
-			break
-		}
-		if err != nil {
-			fmt.Printf("Download error: %s\n", err)
-			os.Exit(1)
-		}
-		if downloadResp.GetProgress() != nil {
-			fmt.Printf(">> DOWNLOAD: %s\n", downloadResp.GetProgress())
-		}
-	}
-
-	libInstall := func(version string) {
-		// LIB INSTALL
-		fmt.Println("=== calling LibraryInstall(WiFi101@" + version + ")")
-		installRespStream, err := client.LibraryInstall(context.Background(), &rpc.LibraryInstallReq{
-			Instance: instance,
-			Name:     "WiFi101",
-			Version:  version,
-		})
-		if err != nil {
-			fmt.Printf("Error installing library: %s\n", err)
-			os.Exit(1)
-		}
-		for {
-			installResp, err := installRespStream.Recv()
-			if err == io.EOF {
-				fmt.Printf("---> %+v\n", installResp)
-				fmt.Println()
-				break
-			}
-			if err != nil {
-				fmt.Printf("Install error: %s\n", err)
-				os.Exit(1)
-			}
-			if installResp.GetProgress() != nil {
-				fmt.Printf(">> DOWNLOAD: %s\n", installResp.GetProgress())
-			}
-			if installResp.GetTaskProgress() != nil {
-				fmt.Printf(">> TASK: %s\n", installResp.GetTaskProgress())
-			}
-		}
-	}
-
-	libInstall("0.15.1") // Install
-	libInstall("0.15.2") // Replace
-
-	// LIB UPGRADE
-	fmt.Println("=== calling LibraryUpgradeAll()")
-	libUpgradeAllRespStream, err := client.LibraryUpgradeAll(context.Background(), &rpc.LibraryUpgradeAllReq{
-		Instance: instance,
-	})
-	if err != nil {
-		fmt.Printf("Error upgrading all: %s\n", err)
-		os.Exit(1)
-	}
-	for {
-		resp, err := libUpgradeAllRespStream.Recv()
-		if err == io.EOF {
-			fmt.Printf("---> %+v\n", resp)
-			fmt.Println()
-			break
-		}
-		if err != nil {
-			fmt.Printf("Upgrading error: %s\n", err)
-			os.Exit(1)
-		}
-		if resp.GetProgress() != nil {
-			fmt.Printf(">> DOWNLOAD: %s\n", resp.GetProgress())
-		}
-		if resp.GetTaskProgress() != nil {
-			fmt.Printf(">> TASK: %s\n", resp.GetTaskProgress())
-		}
-	}
-
-	// LIB SEARCH
-	fmt.Println("=== calling LibrarySearch(audio)")
-	libSearchResp, err := client.LibrarySearch(context.Background(), &rpc.LibrarySearchReq{
-		Instance: instance,
-		Query:    "audio",
-	})
-	if err != nil {
-		fmt.Printf("Error searching for library: %v", err)
-		os.Exit(1)
-	}
-	fmt.Printf("---> %+v\n", libSearchResp)
-	fmt.Println()
-
-	// LIB LIST
-	fmt.Println("=== calling LibraryList")
-	libLstResp, err := client.LibraryList(context.Background(), &rpc.LibraryListReq{
-		Instance:  instance,
-		All:       false,
-		Updatable: false,
-	})
-	if err != nil {
-		fmt.Printf("Error List Library: %v", err)
-		os.Exit(1)
-	}
-	fmt.Printf("---> %+v\n", libLstResp)
-	fmt.Println()
-
-	// LIB UNINSTALL
-	fmt.Println("=== calling LibraryUninstall(WiFi101)")
-	libUninstallRespStream, err := client.LibraryUninstall(context.Background(), &rpc.LibraryUninstallReq{
-		Instance: instance,
-		Name:     "WiFi101",
-	})
-	if err != nil {
-		fmt.Printf("Error uninstalling: %s\n", err)
-		os.Exit(1)
-	}
-	for {
-		uninstallResp, err := libUninstallRespStream.Recv()
-		if err == io.EOF {
-			fmt.Printf("---> %+v\n", uninstallResp)
-			fmt.Println()
-			break
-		}
-		if err != nil {
-			fmt.Printf("Uninstall error: %s\n", err)
-			os.Exit(1)
-		}
-		if uninstallResp.GetTaskProgress() != nil {
-			fmt.Printf(">> TASK: %s\n", uninstallResp.GetTaskProgress())
-		}
-	}
-
-	// DESTROY
-	fmt.Println("=== calling Destroy()")
-	destroyResp, err := client.Destroy(context.Background(), &rpc.DestroyReq{
-		Instance: instance,
-	})
-	if err != nil {
-		fmt.Printf("Error closing server instance: %s\n", err)
-	} else {
-		fmt.Println("Successfully closed server instance")
-	}
-	fmt.Printf("---> %+v\n", destroyResp)
-	fmt.Println()
-}