diff --git a/go.mod b/go.mod index f514ac1..121badc 100644 --- a/go.mod +++ b/go.mod @@ -1,31 +1,39 @@ module github.com/note-cli -go 1.15 +go 1.24.0 replace github.com/blues/note-cli/lib => ./lib -// uncomment this for easier testing locally -// replace github.com/blues/note-go => ../hub/note-go - require ( - github.com/blues/note-cli/lib v0.0.0-20240515194341-6ba45582741d - github.com/blues/note-go v1.7.4 - github.com/fatih/color v1.17.0 + github.com/blues/note-cli/lib v0.0.0-20251120160051-d509bdf52531 + github.com/blues/note-go v1.7.5 + github.com/fatih/color v1.18.0 github.com/peterh/liner v1.2.2 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 ) +require ( + github.com/clipperhouse/stringish v0.1.1 // indirect + github.com/clipperhouse/uax29/v2 v2.5.0 // indirect + github.com/creack/goselect v0.1.3 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/stretchr/testify v1.11.1 // indirect + github.com/tklauser/numcpus v0.11.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + periph.io/x/conn/v3 v3.7.2 // indirect +) + require ( github.com/go-ole/go-ole v1.3.0 // indirect - github.com/golang/snappy v0.0.4 - github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/golang/snappy v1.0.0 + github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect + github.com/mattn/go-runewidth v0.0.19 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/rivo/uniseg v0.4.7 // indirect - github.com/shirou/gopsutil/v3 v3.24.4 // indirect + github.com/shirou/gopsutil/v3 v3.24.5 // indirect github.com/shoenig/go-m1cpu v0.1.7 // indirect - github.com/tklauser/go-sysconf v0.3.14 // indirect - go.bug.st/serial v1.6.2 - golang.org/x/sys v0.20.0 // indirect - periph.io/x/host/v3 v3.8.2 // indirect + github.com/tklauser/go-sysconf v0.3.16 // indirect + go.bug.st/serial v1.6.4 + golang.org/x/sys v0.40.0 // indirect + periph.io/x/host/v3 v3.8.5 // indirect ) diff --git a/go.sum b/go.sum index 54c87ec..79a5838 100644 --- a/go.sum +++ b/go.sum @@ -1,116 +1,89 @@ github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/blues/note-go v1.5.0/go.mod h1:F66ZqObdOhxRRXIwn9+YhVGqB93jMAnqlO2ibwMa998= -github.com/blues/note-go v1.7.4 h1:AqeU6HXkCa7FwDsAao49H6DdTTtNNGJYjGwevZi4Shc= -github.com/blues/note-go v1.7.4/go.mod h1:GfslvbmFus7z05P1YykcbMedTKTuDNTf8ryBb1Qjq/4= -github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= +github.com/blues/note-go v1.7.5 h1:Vzx//F4haI6XPkYDqadN61NkXKwBfsODynjjf8YODF0= +github.com/blues/note-go v1.7.5/go.mod h1:GfslvbmFus7z05P1YykcbMedTKTuDNTf8ryBb1Qjq/4= +github.com/blues/note-go v1.8.0 h1:7h9xVXREnFK0bT7xcYyXq19s1yPcFhZjrkerqFV0TLg= +github.com/blues/note-go v1.8.0/go.mod h1:GfslvbmFus7z05P1YykcbMedTKTuDNTf8ryBb1Qjq/4= +github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= +github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= +github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U= +github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= +github.com/creack/goselect v0.1.3 h1:MaGNMclRo7P2Jl21hBpR1Cn33ITSbKP6E49RtfblLKc= +github.com/creack/goselect v0.1.3/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= -github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI= -github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= +github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= +github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k= +github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= +github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw= github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/shirou/gopsutil/v3 v3.21.6/go.mod h1:JfVbDpIBLVzT8oKbvMg9P3wEIMDDpVn+LwHTKj0ST88= -github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU= -github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8= -github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= github.com/shoenig/go-m1cpu v0.1.7 h1:C76Yd0ObKR82W4vhfjZiCp0HxcSZ8Nqd84v+HZ0qyI0= github.com/shoenig/go-m1cpu v0.1.7/go.mod h1:KkDOw6m3ZJQAPHbrzkZki4hnx+pDRR1Lo+ldA56wD5w= -github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shoenig/test v1.7.0 h1:eWcHtTXa6QLnBvm0jgEabMRN/uJ4DMV3M8xUGgRkZmk= github.com/shoenig/test v1.7.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= -github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= -github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.bug.st/serial v1.3.4/go.mod h1:z8CesKorE90Qr/oRSJiEuvzYRKol9r/anJZEb5kt304= go.bug.st/serial v1.6.1/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= -go.bug.st/serial v1.6.2 h1:kn9LRX3sdm+WxWKufMlIRndwGfPWsH1/9lCWXQCasq8= -go.bug.st/serial v1.6.2/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= +go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A= +go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -periph.io/x/conn/v3 v3.7.0 h1:f1EXLn4pkf7AEWwkol2gilCNZ0ElY+bxS4WE2PQXfrA= periph.io/x/conn/v3 v3.7.0/go.mod h1:ypY7UVxgDbP9PJGwFSVelRRagxyXYfttVh7hJZUHEhg= +periph.io/x/conn/v3 v3.7.2 h1:qt9dE6XGP5ljbFnCKRJ9OOCoiOyBGlw7JZgoi72zZ1s= +periph.io/x/conn/v3 v3.7.2/go.mod h1:Ao0b4sFRo4QOx6c1tROJU1fLJN1hUIYggjOrkIVnpGg= periph.io/x/d2xx v0.1.0/go.mod h1:OflHQcWZ4LDP/2opGYbdXSP/yvWSnHVFO90KRoyobWY= periph.io/x/host/v3 v3.8.0/go.mod h1:rzOLH+2g9bhc6pWZrkCrmytD4igwQ2vxFw6Wn6ZOlLY= -periph.io/x/host/v3 v3.8.2 h1:ayKUDzgUCN0g8+/xM9GTkWaOBhSLVcVHGTfjAOi8OsQ= -periph.io/x/host/v3 v3.8.2/go.mod h1:yFL76AesNHR68PboofSWYaQTKmvPXsQH2Apvp/ls/K4= -periph.io/x/periph v3.6.2+incompatible/go.mod h1:EWr+FCIU2dBWz5/wSWeiIUJTriYv9v2j2ENBmgYyy7Y= +periph.io/x/host/v3 v3.8.5 h1:g4g5xE1XZtDiGl1UAJaUur1aT7uNiFLMkyMEiZ7IHII= +periph.io/x/host/v3 v3.8.5/go.mod h1:hPq8dISZIc+UNfWoRj+bPH3XEBQqJPdFdx218W92mdc= diff --git a/notecard/echo.go b/notecard/echo.go index aac00f3..5f5d41a 100644 --- a/notecard/echo.go +++ b/notecard/echo.go @@ -6,8 +6,8 @@ package main import ( "bytes" + "crypto/rand" "fmt" - "math/rand" "github.com/blues/note-go/notecard" ) diff --git a/notecard/main.go b/notecard/main.go index d905539..ce66293 100644 --- a/notecard/main.go +++ b/notecard/main.go @@ -97,6 +97,15 @@ func getFlagGroups() []lib.FlagGroup { lib.GetFlagByName("pcap"), }, }, + { + Name: "upload", + Description: "File Upload", + Flags: []*flag.Flag{ + lib.GetFlagByName("upload"), + lib.GetFlagByName("route"), + lib.GetFlagByName("target"), + }, + }, { Name: "notefile", Description: "Notefile Management", @@ -236,6 +245,14 @@ func main() { var actionPcap string flag.StringVar(&actionPcap, "pcap", "", "enable PCAP mode and stream packets to output file (required: 'usb' or 'aux')") + // Upload flags - for efficient binary file upload via web.post + var actionUpload string + flag.StringVar(&actionUpload, "upload", "", "upload a file to Notehub via a proxy route using efficient binary transfer") + var actionRoute string + flag.StringVar(&actionRoute, "route", "", "Notehub proxy route alias for upload (required with -upload)") + var actionTarget string + flag.StringVar(&actionTarget, "target", "", "optional URL path appended to the route (becomes 'name' in web.post); use [filename] for the uploaded filename") + // Parse these flags and also the note tool config flags err := lib.FlagParse(true, false) if err != nil { @@ -254,6 +271,12 @@ func main() { lib.PrintGroupedFlags(getFlagGroups(), "notecard") config.Print() fmt.Printf("\n") + fmt.Printf("Upload Usage:\n") + fmt.Printf(" notecard -upload -route \n") + fmt.Printf(" notecard -upload -route -target \n") + fmt.Printf(" Example: notecard -upload ./data.bin -route MyRoute\n") + fmt.Printf(" Example: notecard -upload ./firmware.bin -route Upload -target /devices/[filename]\n") + fmt.Printf("\n") fmt.Printf("PCAP Usage:\n") fmt.Printf(" notecard -port -pcap -output \n") fmt.Printf(" notecard -port -pcap -portconfig -output \n") @@ -354,7 +377,7 @@ func main() { fmt.Printf("%s\n", err) break } - if strings.Contains(rsp.Status, note.ErrTransportDisconnected) { + if strings.Contains(rsp.Status, note.StatusTransportDisconnected) { break } fmt.Printf("%s\n", rsp.Status) @@ -719,6 +742,10 @@ func main() { err = dfuSideload(actionSideload, actionVerbose) } + if err == nil && actionUpload != "" { + err = uploadFile(actionUpload, actionRoute, actionTarget) + } + if err == nil && actionDFUPackage != "" { err = dfuPackage(actionVerbose, actionOutput, actionDFUPackage, flag.Args()) actionRequest = "" diff --git a/notecard/upload.go b/notecard/upload.go new file mode 100644 index 0000000..f343f5c --- /dev/null +++ b/notecard/upload.go @@ -0,0 +1,367 @@ +// Copyright 2026 Blues Inc. All rights reserved. +// Use of this source code is governed by licenses granted by the +// copyright holder including that found in the LICENSE file. + +// upload.go implements efficient binary file uploads to a Notehub proxy route +// using the Notecard's binary buffer (card.binary) and web.post API. +// +// This module serves as a reference implementation for high-performance file +// uploads through the Notecard. It demonstrates best practices for: +// - Querying the Notecard's binary buffer capacity +// - Chunking large files to fit within buffer constraints +// - Using COBS encoding for reliable binary transfer +// - MD5 verification for data integrity +// - Tracking upload progress with offset/total fields +// - Performance monitoring and statistics +// +// The upload process works as follows: +// 1. Query card.binary to determine the Notecard's maximum buffer size +// 2. Read the source file and calculate its total size +// 3. For each chunk that fits in the binary buffer: +// a. COBS-encode the chunk for safe serial transmission +// b. Stage the chunk in the Notecard's binary buffer via card.binary.put +// c. Verify the chunk was received correctly via card.binary +// d. Issue web.post with binary:true to send the chunk to Notehub +// 4. Report per-chunk and cumulative performance statistics to stderr +// +// The content type used is application/octet-stream for binary uploads. + +package main + +import ( + "crypto/md5" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/blues/note-go/note" + "github.com/blues/note-go/notecard" +) + +// maxUploadChunkBytes is the maximum chunk size we'll use for uploads, +// regardless of what the Notecard reports as its buffer capacity. +// The only reason to lower this below the Notecard's capacity (which +// is about 250KB for a v2 (black) Notecard) is if the communications +// between the host and notecard might be slow and you want to reduce +// the size of each individual COBS transfer for host responsiveness. +const maxUploadChunkBytes = 0 + +// uploadFile performs a binary file upload to a Notehub proxy route. +// +// Parameters: +// - filename: Path to the file to upload +// - route: The Notehub proxy route alias (required) +// - target: Optional URL path appended to the route (becomes "name" in web.post) +// +// The function uploads the file in chunks sized to the Notecard's binary buffer +// capacity. Each chunk is verified via MD5 checksum before transmission to Notehub. +// Progress statistics are written to stderr after each chunk. +// +// Returns an error if the upload fails at any stage. +func uploadFile(filename string, route string, target string) error { + + // ========================================================================= + // STEP 1: Validate required parameters + // ========================================================================= + // The route parameter is mandatory as it specifies the Notehub proxy route + // that will receive the uploaded data. + if route == "" { + return fmt.Errorf("upload requires -route to be specified") + } + + // ========================================================================= + // STEP 2: Read the file into memory + // ========================================================================= + // We read the entire file upfront to: + // - Fail early if the file doesn't exist or isn't readable + // - Know the total size for progress calculations and offset/total fields + // - Simplify chunk extraction during the upload loop + fileData, err := os.ReadFile(filename) + if err != nil { + return fmt.Errorf("failed to read file '%s': %w", filename, err) + } + + totalSize := len(fileData) + if totalSize == 0 { + return fmt.Errorf("file '%s' is empty", filename) + } + + // Extract just the filename for display purposes (strip directory path) + displayName := filepath.Base(filename) + + fmt.Fprintf(os.Stderr, "uploading '%s' (%d bytes) to route '%s'\n", displayName, totalSize, route) + + // ========================================================================= + // STEP 3: Query the Notecard's binary buffer capacity + // ========================================================================= + // The card.binary request returns information about the Notecard's binary + // buffer, including the maximum size it can accept. This value is fixed + // for a given Notecard type and doesn't change, so we only query it once. + // Note that the "reset" is essential so that it terminates any previous + // binary upload that may still be in progress from the notecard's perspective. + // + // The response includes: + // - max: Maximum number of bytes the binary buffer can hold + // - length: Current number of bytes in the buffer (should be 0) + rsp, err := card.TransactionRequest(notecard.Request{Req: "card.binary", Reset: true}) + if err != nil { + return fmt.Errorf("failed to query card.binary capacity: %w", err) + } + + binaryMax := int(rsp.Max) + if binaryMax == 0 { + return fmt.Errorf("notecard does not support binary transfers (card.binary returned max=0)") + } + + // Use the smaller of the notecard's buffer capacity or our configured max + chunkMax := binaryMax + if maxUploadChunkBytes != 0 && maxUploadChunkBytes < chunkMax { + chunkMax = maxUploadChunkBytes + } + + fmt.Fprintf(os.Stderr, "notecard binary buffer capacity: %d bytes, using chunk size: %d bytes\n", binaryMax, chunkMax) + + // ========================================================================= + // STEP 4: Set content type for binary upload + // ========================================================================= + // The content type application/octet-stream indicates raw binary data. + contentType := "application/octet-stream" + + // ========================================================================= + // STEP 5: Initialize upload state and statistics + // ========================================================================= + offset := 0 // Current byte offset in the file + chunkNumber := 0 // Current chunk number (1-based for display) + totalChunks := (totalSize + chunkMax - 1) / chunkMax // Ceiling division + uploadStartTime := time.Now() + + // ========================================================================= + // STEP 6: Upload loop - process file in chunks + // ========================================================================= + for offset < totalSize { + chunkNumber++ + chunkStartTime := time.Now() + + // --------------------------------------------------------------------- + // 6a: Calculate chunk boundaries + // --------------------------------------------------------------------- + // Determine how many bytes to send in this chunk. The last chunk may + // be smaller than chunkMax if the file size isn't evenly divisible. + chunkSize := chunkMax + remaining := totalSize - offset + if remaining < chunkSize { + chunkSize = remaining + } + + // Extract the chunk data from the file buffer + chunkData := fileData[offset : offset+chunkSize] + + // --------------------------------------------------------------------- + // 6b: Calculate MD5 checksum for this chunk + // --------------------------------------------------------------------- + // The MD5 checksum serves two purposes: + // 1. Verify the chunk was correctly staged in the Notecard's buffer + // 2. Allow Notehub to verify the chunk wasn't corrupted in transit + chunkMD5 := fmt.Sprintf("%x", md5.Sum(chunkData)) + + // --------------------------------------------------------------------- + // 6c: COBS-encode the chunk for serial transmission + // --------------------------------------------------------------------- + // COBS (Consistent Overhead Byte Stuffing) encoding ensures the binary + // data can be safely transmitted over the serial connection without + // conflicting with the newline character used as a packet delimiter. + // + // Using CobsEncodeAppend to encode and append newline in one operation, + // which avoids a reallocation that would occur with separate encode + append. + encodedDataWithNewline, err := notecard.CobsEncodeAppend(chunkData, byte('\n'), byte('\n')) + if err != nil { + return fmt.Errorf("chunk %d: COBS encoding failed: %w", chunkNumber, err) + } + + // Length of encoded data without the trailing newline (for card.binary.put) + encodedLen := len(encodedDataWithNewline) - 1 + + // --------------------------------------------------------------------- + // 6d-6f: Transfer binary and send via web.post with retry logic + // --------------------------------------------------------------------- + // This section handles both binary transfer to the Notecard and the + // subsequent web.post to Notehub. Both operations have retry logic: + // - Binary transfer errors ({bad-bin}, {io}) retry indefinitely + // - web.post errors wait 15 seconds, re-transfer binary, and retry + // + // We use a labeled loop so web.post failures can restart the entire + // chunk upload process (binary transfer + web.post). + + chunkRetry: + for { + // ----------------------------------------------------------------- + // Binary transfer with retry on {bad-bin}/{io} errors + // ----------------------------------------------------------------- + for { + // Stage the chunk in the Notecard's binary buffer + req := notecard.Request{Req: "card.binary.put"} + req.Cobs = int32(encodedLen) + + _, err = card.TransactionRequest(req) + if err != nil { + if note.ErrorContains(err, note.ErrCardIo) { + fmt.Fprintf(os.Stderr, "binary transfer error, retrying: %s\n", err) + continue + } + return fmt.Errorf("chunk %d: card.binary.put failed: %w", chunkNumber, err) + } + + // Send the COBS-encoded data followed by a newline delimiter + err = card.SendBytes(encodedDataWithNewline) + if err != nil { + if note.ErrorContains(err, note.ErrCardIo) { + fmt.Fprintf(os.Stderr, "binary transfer error, retrying: %s\n", err) + continue + } + return fmt.Errorf("chunk %d: SendBytes failed: %w", chunkNumber, err) + } + + // Verify the chunk was received correctly by the Notecard + verifyRsp, err := card.TransactionRequest(notecard.Request{Req: "card.binary"}) + if err != nil { + if note.ErrorContains(err, note.ErrCardIo) { + fmt.Fprintf(os.Stderr, "binary transfer error, retrying: %s\n", err) + continue + } + return fmt.Errorf("chunk %d: card.binary verification failed: %w", chunkNumber, err) + } + + // Check for error in response (e.g., "binary receive prematurely terminated {bad-bin}{io}") + if verifyRsp.Err != "" { + if strings.Contains(verifyRsp.Err, "{bad-bin}") || strings.Contains(verifyRsp.Err, "{io}") { + fmt.Fprintf(os.Stderr, "binary transfer error, retrying: %s\n", verifyRsp.Err) + continue + } + return fmt.Errorf("chunk %d: card.binary error: %s", chunkNumber, verifyRsp.Err) + } + + // Verify size matches + if int(verifyRsp.Length) != chunkSize { + fmt.Fprintf(os.Stderr, "chunk %d: size mismatch (sent %d, received %d), retrying\n", + chunkNumber, chunkSize, verifyRsp.Length) + continue + } + + break // Binary transfer successful + } + + // ----------------------------------------------------------------- + // Send the chunk to Notehub via web.post + // ----------------------------------------------------------------- + // Now that the chunk is staged in the Notecard's binary buffer, we + // issue a web.post request to send it to Notehub. + webReq := notecard.Request{Req: "web.post"} + webReq.RouteUID = route + webReq.Binary = true + webReq.Content = contentType + webReq.Offset = int32(offset) + webReq.Status = chunkMD5 + webReq.Name = target + webReq.Label = displayName + + // If (and only if) we require more than one chunk, set the total field which + // indicates to the notehub that we want to do segmented upload. + if totalChunks > 1 { + webReq.Total = int32(totalSize) + } + + webRsp, err := card.TransactionRequest(webReq) + if err != nil { + fmt.Fprintf(os.Stderr, "web.post failed: %s, waiting 15s then retrying\n", err) + time.Sleep(15 * time.Second) + continue chunkRetry // Re-transfer binary and retry web.post + } + + // Check for HTTP-level errors (3xx, 4xx, 5xx) + if webRsp.Result >= 300 { + errMsg := webRsp.Err + if errMsg == "" { + // Try to extract error from response body + if webRsp.Body != nil { + if errField, ok := (*webRsp.Body)["err"].(string); ok && errField != "" { + errMsg = errField + } + } + } + if errMsg != "" { + fmt.Fprintf(os.Stderr, "server returned HTTP %d: \"%s\"\nwaiting 15s then retrying\n", webRsp.Result, errMsg) + } else { + fmt.Fprintf(os.Stderr, "server returned HTTP %d, waiting 15s then retrying\n", webRsp.Result) + } + time.Sleep(15 * time.Second) + continue chunkRetry // Re-transfer binary and retry web.post + } + + break chunkRetry // Chunk upload successful + } + + // --------------------------------------------------------------------- + // 6g: Calculate and display performance statistics + // --------------------------------------------------------------------- + chunkDuration := time.Since(chunkStartTime) + totalDuration := time.Since(uploadStartTime) + + // Calculate throughput for this chunk (bytes per second) + chunkBytesPerSec := float64(chunkSize) / chunkDuration.Seconds() + + // Calculate cumulative progress + bytesCompleted := offset + chunkSize + percentComplete := float64(bytesCompleted) * 100.0 / float64(totalSize) + + // Calculate overall throughput (bytes per second) + overallBytesPerSec := float64(bytesCompleted) / totalDuration.Seconds() + + // Estimate time remaining based on current throughput + bytesRemaining := totalSize - bytesCompleted + var etaStr string + if overallBytesPerSec > 0 && bytesRemaining > 0 { + etaSeconds := float64(bytesRemaining) / overallBytesPerSec + etaStr = fmt.Sprintf("ETA %s", (time.Duration(etaSeconds) * time.Second).Round(time.Second)) + } else { + etaStr = "complete" + } + + // Output one line per chunk to stderr with comprehensive statistics + // Format: chunk X/Y: BYTES bytes (XX.X%) @ XX.X KB/s (avg XX.X KB/s) ETA Xm Xs + if totalChunks == 1 { + fmt.Fprintf(os.Stderr, "%d bytes @ %.1f KB/s\n", bytesCompleted, overallBytesPerSec/1024.0) + } else { + fmt.Fprintf(os.Stderr, "chunk %d/%d: %d/%d bytes (%.1f%%) @ %.1f KB/s (avg %.1f KB/s) %s\n", + chunkNumber, + totalChunks, + bytesCompleted, + totalSize, + percentComplete, + chunkBytesPerSec/1024.0, + overallBytesPerSec/1024.0, + etaStr, + ) + } + + // --------------------------------------------------------------------- + // 6h: Advance to the next chunk + // --------------------------------------------------------------------- + offset += chunkSize + } + + // ========================================================================= + // STEP 7: Upload complete - display summary + // ========================================================================= + totalDuration := time.Since(uploadStartTime) + overallBytesPerSec := float64(totalSize) / totalDuration.Seconds() + + fmt.Fprintf(os.Stderr, "upload complete: %d bytes in %s (%.1f KB/s average)\n", + totalSize, + totalDuration.Round(time.Second), + overallBytesPerSec/1024.0, + ) + + return nil +}