{"id":15653308,"url":"https://github.com/nikolaydubina/watchhttp","last_synced_at":"2025-08-24T12:32:31.373Z","repository":{"id":142258153,"uuid":"612687988","full_name":"nikolaydubina/watchhttp","owner":"nikolaydubina","description":"🌺 Run command periodically and expose latest STDOUT as HTTP endpoint","archived":false,"fork":false,"pushed_at":"2024-03-21T12:42:27.000Z","size":13618,"stargazers_count":29,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-05-01T14:35:03.079Z","etag":null,"topics":["command-line","dashboard","dashboards","http","json","kubernertes","linux","live-data","tooling","unix","visualization","watch","yaml"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nikolaydubina.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"nikolaydubina"}},"created_at":"2023-03-11T17:26:41.000Z","updated_at":"2024-05-09T06:22:02.806Z","dependencies_parsed_at":"2024-05-09T06:22:02.423Z","dependency_job_id":"2a08978d-7a40-4ce3-a514-569cf96e5f6f","html_url":"https://github.com/nikolaydubina/watchhttp","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikolaydubina%2Fwatchhttp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikolaydubina%2Fwatchhttp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikolaydubina%2Fwatchhttp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikolaydubina%2Fwatchhttp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nikolaydubina","download_url":"https://codeload.github.com/nikolaydubina/watchhttp/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242710902,"owners_count":20173244,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["command-line","dashboard","dashboards","http","json","kubernertes","linux","live-data","tooling","unix","visualization","watch","yaml"],"created_at":"2024-10-03T12:45:19.016Z","updated_at":"2025-03-09T15:30:34.276Z","avatar_url":"https://github.com/nikolaydubina.png","language":"Go","funding_links":["https://github.com/sponsors/nikolaydubina"],"categories":["Utilities","公用事业公司"],"sub_categories":["Utility/Miscellaneous","实用程序/Miscellaneous"],"readme":"# 🌺 watchhttp\n\n[![Go Report Card](https://goreportcard.com/badge/github.com/nikolaydubina/watchhttp)](https://goreportcard.com/report/github.com/nikolaydubina/watchhttp)\n[![codecov](https://codecov.io/gh/nikolaydubina/watchhttp/branch/master/graph/badge.svg?token=PMs70WQIVI)](https://codecov.io/gh/nikolaydubina/watchhttp)\n[![Go Reference](https://pkg.go.dev/badge/github.com/nikolaydubina/watchhttp.svg)](https://pkg.go.dev/github.com/nikolaydubina/watchhttp)\n[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go)\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/nikolaydubina/watchhttp/badge)](https://securityscorecards.dev/viewer/?uri=github.com/nikolaydubina/watchhttp)\n\n\u003e Run command periodically and expose latest STDOUT as HTTP endpoint\n\n* 300LOC\n* pure Go\n* automatic browser refresh via `Refresh` header\n* animations\n* minimal dependencies\n* no Javascript\n* no WebAssembly\n\n```bash\ngo install github.com/nikolaydubina/watchhttp@latest\n```\n\n### Live animated delta of from transformed cURL\n\nJSON\n```bash\nwatchhttp -t 5s -json -d -- /bin/sh -c 'curl \"https://api.open-meteo.com/v1/forecast?latitude=52.52\u0026longitude=13.41\u0026current_weather=true\u0026hourly=temperature_2m,relativehumidity_2m,windspeed_10m\" | jq \"del(.hourly)\"'\n```\n\nhttps://user-images.githubusercontent.com/2933061/226150800-d591624e-dc2d-482f-8b1f-c2295699fac7.mov\n\nYAML\n```bash\nwatchhttp -t 5s -yaml -d -- /bin/sh -c 'curl \"https://api.open-meteo.com/v1/forecast?latitude=52.52\u0026longitude=13.41\u0026current_weather=true\u0026hourly=temperature_2m,relativehumidity_2m,windspeed_10m\" | jq \"del(.hourly)\" | yq -P'\n```\n\nhttps://user-images.githubusercontent.com/2933061/227462275-fcc5c49f-ce6b-4892-80fe-4c6e0ac16310.mov\n\n### Fetch and transform periodically with `curl` and `jq`\n\n```bash\nwatchhttp -t 5s -json -- /bin/sh -c 'curl \"https://api.open-meteo.com/v1/forecast?latitude=52.52\u0026longitude=13.41\u0026current_weather=true\u0026hourly=temperature_2m,relativehumidity_2m,windspeed_10m\" | jq \"del(.hourly)\"'\n```\n\n```bash\n$ curl localhost:9000/\n{\n  \"latitude\": 52.52,\n  \"longitude\": 13.419998,\n  \"generationtime_ms\": 0.635981559753418,\n  \"utc_offset_seconds\": 0,\n  \"timezone\": \"GMT\",\n  \"timezone_abbreviation\": \"GMT\",\n  \"elevation\": 38,\n  \"current_weather\": {\n    \"temperature\": 1.3,\n    \"windspeed\": 8.4,\n    \"winddirection\": 220,\n    \"weathercode\": 3,\n    \"time\": \"2023-03-12T09:00\"\n  },\n  \"hourly_units\": {\n    \"time\": \"iso8601\",\n    \"temperature_2m\": \"°C\",\n    \"relativehumidity_2m\": \"%\",\n    \"windspeed_10m\": \"km/h\"\n  }\n}\n```\n\n```bash\nwatchhttp -t 2s -json -- /bin/sh -c \"curl --location 'api.coincap.io/v2/rates/bitcoin' | jq\"\n```\n\n![](./doc/example-btc-static.png)\n\n### Live graph of Kubernetes resources with `kubectl` and `graphviz`\n\n```bash\ngo install github.com/nikolaydubina/jsonl-graph@latest\n```\n\n```bash\nwatchhttp -t 3s -p 9000 -- /bin/sh -c \"kubectl get pods -o json | jq '.items[] | {to: (.kind + \\\":\\\" + .metadata.name), from: (.metadata.ownerReferences[].kind + \\\":\\\" + .metadata.ownerReferences[].name)}' | jsonl-graph | dot -Tsvg\"\n```\n\n![](./doc/example-k8s-graph.png)\n\n### Live status of Kubernetes resource with `kubectl`\n\n```bash\nwatchhttp -t 3s -p 9000 -- kubectl describe deployment hello-minikube\n```\n\n![](./doc/example-k8s-describe-static.png)\n\n### Live system metrics with `vmstat`\n\n```bash\nwatchhttp vmstat\n```\n\n```bash\n$ curl localhost:9000/\nMach Virtual Memory Statistics: (page size of 16384 bytes)\nPages free:                             1616097.\nPages active:                            914186.\nPages inactive:                          579357.\nPages speculative:                       829846.\nPages throttled:                              0.\nPages wired down:                        164337.\nPages purgeable:                         125376.\n\"Translation faults\":                2059157267.\nPages copy-on-write:                   58902729.\nPages zero filled:                   1573609407.\nPages reactivated:                       577992.\nPages purged:                          17881481.\nFile-backed pages:                      1300312.\nAnonymous pages:                        1023077.\nPages stored in compressor:              202931.\nPages occupied by compressor:             35364.\nDecompressions:                          973775.\nCompressions:                           1404319.\nPageins:                               23222205.\nPageouts:                                 12866.\nSwapins:                                 435185.\nSwapouts:                                477406.\n```\n\n### Watch file `tail` or `cat`\n\n```bash\nwatchhttp tail /var/log/system.log\n```\n\n```bash\n$ curl localhost:9000/            \nMar 12 16:16:07 Nikolays-MacBook-Pro syslogd[532]: ASL Sender Statistics\nMar 12 16:32:06 Nikolays-MacBook-Pro syslogd[532]: ASL Sender Statistics\nMar 12 16:48:15 Nikolays-MacBook-Pro syslogd[532]: ASL Sender Statistics\nMar 12 17:04:22 Nikolays-MacBook-Pro syslogd[532]: ASL Sender Statistics\nMar 12 17:22:24 Nikolays-MacBook-Pro syslogd[532]: ASL Sender Statistics\nMar 12 17:33:45 Nikolays-MacBook-Pro MobileDeviceUpdater[4765]: Entered:_AMMuxedVersion2DeviceConnected, mux-device:4541\nMar 12 17:33:45 Nikolays-MacBook-Pro syslogd[532]: ASL Sender Statistics\nMar 12 17:33:45 Nikolays-MacBook-Pro AMPDeviceDiscoveryAgent[1146]: Entered:_AMMuxedVersion2DeviceConnected, mux-device:4541\nMar 12 17:33:47 Nikolays-MacBook-Pro AMPDevicesAgent[67221]: Entered:_AMMuxedVersion2DeviceConnected, mux-device:4541\nMar 12 17:44:54 Nikolays-MacBook-Pro syslogd[532]: ASL Sender Statistics\n```\n\n---\n\n### Appendix A: Related Work\n\n- as of 2023-03-12, [awesome-go](http://github.com/avelino/awesome-go) does not mention any tools that can do this\n- `netcat` can not do this\n- [mantyr/jsonhtml](https://github.com/mantyr/jsonhtml) is experimental JSON to HTML renderer, assignment, no go mod \n- [shovon/json-pretty-printer](https://github.com/shovon/json-pretty-printer) is experimental JSON to HTML renderer, no reflect, direct JSON string parsing, assignment, no go mod\n- [https://github.com/marchoy/pretty-printer](https://github.com/marchoy/pretty-printer) is experimental JSON prettifier to HTML renderer, adds syntax highlighting, no reflect, direct JSON string parsing, no tests, no go mod\n\n### Appendix B: Alternative with file server + bash\n\nSimilar effect can be achieved with file server and bash, albeit headers would not be set.\n\n```go\npackage main\n\nimport \"net/http\"\n\nfunc main() { http.ListenAndServe(\":9000\", http.FileServer(http.Dir(\".\"))) }\n```\n```bash\n$ while sleep 5; do \u003csomething\u003e \u003e \u003cfile\u003e ; done\n```\n\n### Appendix C: Paths Not Taken\n\n\u003e Expose STDIN as HTTP endpoint\n\nThe problem is how to differentate separate responses?\nIs empty line valid separator of responses?\nThere are clearly interesting eaxmples with UNIX pipes and `tail -f`, logs, WebSockets.\nHowever, that may need separate tool.\n\n\u003e Stream `top`, k9s, [datadash](https://github.com/keithknott26/datadash) to browser in HTML\n\nFirst, those tools re-render terminal output at their own interval.\nIt would take some effort, but effectivelly this is emulating terminal rendering in browser.\nIssue of converting terminal escaped ASCII to HTML output colors.\nAs next step, you would want to also pass to STDIN through browser too.\nOverall, this is separate problem of exposing terminal throuhg browser.\n\n### Appendix D: Interesting free real time data sources\n\n* https://github.com/public-apis/public-apis\n\n#### Stock Sentiment in `/r/Wallstreetbets`\n* https://tradestie.com/apps/reddit/api/\n* `curl -XGET 'https://tradestie.com/api/v1/apps/reddit'`\n* no auth\n* no limit\n\n#### Weather forecast\n* `curl https://api.open-meteo.com/v1/forecast?latitude=52.52\u0026longitude=13.41\u0026current_weather=true\u0026hourly=temperature_2m,relativehumidity_2m,windspeed_10m`\n* no auth\n* no limit\n\n#### Bitcoin rates\n* https://docs.coincap.io/#intro\n* `curl --location 'api.coincap.io/v2/rates/bitcoin'`\n* no auth\n* no limit \n\n### Appenidx E: Inspiration and Other Tools\n\nCharles Schwab quick stock buy liver ticker\n\n\u003cimg width=\"341\" alt=\"Screenshot 2023-07-24 at 21 34 09\" src=\"https://github.com/nikolaydubina/watchhttp/assets/2933061/e7641155-fc5b-469d-8b2b-48e36e579988\"\u003e\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnikolaydubina%2Fwatchhttp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnikolaydubina%2Fwatchhttp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnikolaydubina%2Fwatchhttp/lists"}