{"id":13491186,"url":"https://github.com/babashka/pods","last_synced_at":"2025-12-12T01:14:52.051Z","repository":{"id":40329139,"uuid":"262537952","full_name":"babashka/pods","owner":"babashka","description":"Pods support for JVM and babashka","archived":false,"fork":false,"pushed_at":"2024-05-08T12:17:43.000Z","size":194,"stargazers_count":124,"open_issues_count":6,"forks_count":12,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-11T02:21:43.549Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/babashka.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-05-09T09:41:14.000Z","updated_at":"2025-02-02T19:58:11.000Z","dependencies_parsed_at":"2025-03-28T08:32:42.737Z","dependency_job_id":"633526e0-d2fd-4af8-8f07-aa532814a1a1","html_url":"https://github.com/babashka/pods","commit_stats":null,"previous_names":["babashka/babashka.pods"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/babashka/pods","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babashka%2Fpods","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babashka%2Fpods/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babashka%2Fpods/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babashka%2Fpods/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/babashka","download_url":"https://codeload.github.com/babashka/pods/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babashka%2Fpods/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260595742,"owners_count":23033789,"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":[],"created_at":"2024-07-31T19:00:54.375Z","updated_at":"2025-12-12T01:14:47.024Z","avatar_url":"https://github.com/babashka.png","language":"Clojure","funding_links":[],"categories":["Clojure"],"sub_categories":[],"readme":"# Babashka pods\n\n[![Clojars Project](https://img.shields.io/clojars/v/babashka/babashka.pods.svg)](https://clojars.org/babashka/babashka.pods)\n\nBabashka pods are programs that can be used as Clojure libraries by babashka.\n\nThis is the library to load babashka pods. It is used by\n[babashka](https://github.com/babashka/babashka/) but also usable from the JVM\nand [sci](https://github.com/borkdude/sci)-based projects other than babashka.\n\n\u003ca title=\"Wa17gs / CC BY-SA (https://creativecommons.org/licenses/by-sa/4.0)\" href=\"https://commons.wikimedia.org/wiki/File:Below_Golden_Gate_Bridge.jpeg\"\u003e\u003cimg width=\"512\" alt=\"Below Golden Gate Bridge\" src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Below_Golden_Gate_Bridge.jpeg/512px-Below_Golden_Gate_Bridge.jpeg\"\u003e\u003c/a\u003e\n\u003cbr\u003e\n\u003cem\u003eThe word pod means bridge in Romanian.\u003c/em\u003e\n\n## Introduction\n\nPods are standalone programs that can expose namespaces with vars to babashka or\na JVM. Pods can be built in Clojure, but also in languages that don't run on the\nJVM.\n\nSome terminology:\n\n- _pod_: a program that exposes namespaces with vars via the _pod protocol_.\n- _pod client_: the program invoking a pod. When babashka invokes a pod,\nbabashka is the pod client. When a JVM invokes a pod, the JVM is the pod client.\n- _message_: a message sent from the pod client to the pod or vice versa,\n  encoded in [bencode](https://en.wikipedia.org/wiki/Bencode) format.\n- _payload_: a particular field of a _message_ encoded in a _payload format_\n  (currently JSON, EDN or Transit JSON). Examples are `args`, `value` and `ex-data`.  _\n-  _pod protocol_: the documented way of exchanging messages between a _pod\n  client_ and _pod_.\n\nPods can be created independently from pod clients. Any program can be invoked\nas a pod as long as it implements the _pod protocol_. This protocol is\ninfluenced by and built upon battle-tested technologies:\n\n- the [nREPL](https://nrepl.org/) and [LSP](https://microsoft.github.io/language-server-protocol/) protocols\n- [bencode](https://en.wikipedia.org/wiki/Bencode)\n- [JSON](https://www.json.org/json-en.html)\n- [EDN](https://github.com/edn-format/edn)\n- composition of UNIX command line tools in via good old stdin and stdout\n\nThe name pod is inspired by [boot's pod\nfeature](https://github.com/boot-clj/boot/wiki/Pods). It means _underneath_ or\n_below_ in Polish and Russian. In Romanian it means _bridge_\n([source](https://en.wiktionary.org/wiki/pod)).\n\n## Available pods\n\nFor a list of available pods, take a look\n[here](https://github.com/babashka/babashka/blob/master/doc/projects.md#pods).\n\n## Status\n\nThe protocol should be considered alpha. Breaking changes may occur at this\nphase and will be documented in `CHANGELOG.md`.\n\n## Usage\n\nUsing [pod-babashka-hsqldb](https://github.com/borkdude/pod-babashka-hsqldb) as\nan example pod.\n\nOn the JVM:\n\n``` clojure\n(require '[babashka.pods :as pods])\n(pods/load-pod \"pod-babashka-hsqldb\")\n(require '[pod.babashka.hsqldb :as sql])\n\n(def db \"jdbc:hsqldb:mem:testdb;sql.syntax_mys=true\")\n(sql/execute! db [\"create table foo ( foo int );\"])\n;;=\u003e [#:next.jdbc{:update-count 0}]\n```\n\n### Where does the pod come from?\n\nWhen calling `load-pod` with a string or vector of strings (or declaring it in your `bb.edn`),\nthe pod is looked up on the local file system (either using the PATH, or using an absolute path).\nWhen it is called with a qualified symbol and a version - like `(load-pod 'org.babashka/aws \"0.0.5\")`\nthen it will be looked up in and downloaded from the [pod-registry](https://github.com/babashka/pod-registry). You can customize the file system location that `load-pod` will use by setting the `BABASHKA_PODS_DIR` environment variable.\n\nBy default babashka will search for a pod binary matching your system's OS and arch. If you want to download\npods for a different OS / arch (e.g. for deployment to servers), you can set one or both of the following\nenvironment variables:\n\n- `BABASHKA_PODS_OS_NAME=Linux` (or `Mac OS X` or any other value returned by Java's `os.name` property)\n- `BABASHKA_PODS_OS_ARCH=aarch64` (or `amd64` or any other value returned by Java's `os.arch` property)\n\n### In a babashka project\n\nAs of babashka 0.8.0 you can declare the pods your babashka project uses in your `bb.edn` file like so:\n\n```clojure\n:pods {org.babashka/hsqldb {:version \"0.1.0\"} ; will be downloaded from the babashka pod registry\n       my.local/pod {:path \"../pod-my-local/my-pod-binary\"\n                     :cache false}} ; optionally disable namespace caching if you're actively working on this pod\n```\n\nThen you can just require the pods in your code like any other clojure lib:\n\n```clojure\n(ns my.project\n  (:require [pod.babashka.hsqldb :as sql]\n            [my.local.pod :as my-pod]))\n\n(def db \"jdbc:hsqldb:mem:testdb;sql.syntax_mys=true\")\n(sql/execute! db [\"create table foo ( foo int );\"])\n;;=\u003e [#:next.jdbc{:update-count 0}]\n\n(my-pod/do-a-thing \"foo\")\n;;=\u003e \"something\"\n```\n\nThe pods will then be loaded on demand when you require them. No need to call `load-pod` explicitly.\n\n## Sci\n\nTo use pods in a [sci](https://github.com/borkdude/sci) based project, see\n[test/babashka/pods/sci_test.clj](test/babashka/pods/sci_test.clj).\n\n## Why JVM support?\n\n- Babashka pods allow you to leverage functionality from other programs\nregardless of the technology they were implemented in. As such, pods can be a\nlight weight replacement for native interop (JNI, JNA, etc.).\n\n- When developing pods, this library can be used to test them on the JVM.\n\n## Implementing your own pod\n\n### Examples\n\nBeyond the already available pods mentioned above, educational examples of pods\ncan be found [here](examples):\n\n- [pod-lispyclouds-sqlite](examples/pod-lispyclouds-sqlite): a pod that\n  allows you to create and fire queries at a [sqlite](https://www.sqlite.org/)\n  database. Implemented in Python.\n\n### Libraries\n\nIf you are looking for libraries to deal with bencode, JSON or EDN, take a look\nat the existing pods or [nREPL](https://nrepl.org/nrepl/beyond_clojure.html)\nimplementations for various languages.\n\n### Naming\n\nWhen choosing a name for your pod, we suggest the following naming scheme:\n\n```\npod-\u003cuser-id\u003e-\u003cpod-name\u003e\n```\n\nwhere `\u003cuser-id\u003e` is your Github or Gitlab handle and `\u003cpod-name\u003e` describes\nwhat your pod is about.\n\nExamples:\n\n- [pod-lispyclouds-sqlite](examples/pod-lispyclouds-sqlite): a pod to\n  communicate with [sqlite](https://www.sqlite.org/), provided by\n  [@lispyclouds](https://github.com/lispyclouds).\n\nPods created by the babashka maintainers use the identifier `babashka`:\n\n- [pod-babashka-hsqldb](https://github.com/borkdude/pod-babashka-hsqldb): a pod\n  to communicate with [HSQLDB](http://www.hsqldb.org/)\n\n### The protocol\n\n#### Message and payload format\n\nExchange of _messages_ between pod client and the pod happens in the\n[bencode](https://en.wikipedia.org/wiki/Bencode) format. Bencode is a bare-bones\nformat that only has four types:\n\n- integers\n- lists\n- dictionaries (maps)\n- byte strings\n\nAdditionally, _payloads_ like `args` (arguments) or `value` (a function return\nvalue) are encoded in either EDN, JSON or Transit JSON.\n\nSo remember: messages are in bencode, payloads (particular fields in the\nmessage) are in either EDN, JSON or Transit JSON.\n\nBencode is chosen as the message format because it is a light-weight format\nwhich can be implemented in 200-300 lines of code in most languages. If pods are\nimplemented in Clojure, they only need to depend on the\n[bencode](https://github.com/nrepl/bencode) library and use `pr-str` and\n`edn/read-string` for encoding and decoding payloads.\n\nSo we use bencode as the first encoding and choose one of multiple richer encodings on top of this, similar to how the nREPL protocol is implemented. More\npayload formats might be added in the future.\nOther languages typically use a bencode library + a JSON library to encode payloads.\n\nWhen calling the `babashka.pods/load-pod` function, the pod client will start\nthe pod and leave the pod running throughout the duration of a babashka script.\n\n#### describe\n\nThe first message that the pod client will send to the pod on its stdin is:\n\n``` clojure\n{\"op\" \"describe\"}\n```\n\nEncoded in bencode this looks like:\n\n``` clojure\n(bencode/write-bencode System/out {\"op\" \"describe\"})\n;;=\u003e d2:op8:describee\n```\n\nThe pod should reply to this request with a message similar to:\n\n``` clojure\n{\"format\" \"json\"\n \"namespaces\"\n [{\"name\" \"pod.lispyclouds.sqlite\"\n   \"vars\" [{\"name\" \"execute!\"}]}]\n \"ops\" {\"shutdown\" {}}}\n```\n\nIn this reply, the pod declares that payloads will be encoded and decoded using\nJSON. It also declares that the pod exposes one namespace,\n`pod.lispyclouds.sqlite` with one var `execute!`.\n\nTo encode payloads in EDN use `\"edn\"` and for Transit JSON use `\"transit+json\"`.\n\nThe pod encodes the above map to bencode and writes it to stdout. The pod client\nreads this message from the pod's stdout.\n\nUpon receiving this message, the pod client creates these namespaces and vars.\n\nThe optional `ops` value communicates which ops the pod supports, beyond\n`describe` and `invoke`. It is a map of op names to option maps. In the above\nexample the pod declares that it supports the `shutdown` op. Since the\n`shutdown` op does not need any additional options right now, the value is an\nempty map.\n\nAs a pod user, you can load the pod with:\n\n``` clojure\n(require '[babashka.pods :as pods])\n(pods/load-pod \"pod-lispyclouds-sqlite\")\n(some? (find-ns 'pod.lispyclouds.sqlite)) ;;=\u003e true\n;; yay, the namespace exists!\n\n;; let's give the namespace an alias\n(require '[pod.lispyclouds.sqlite :as sql])\n```\n\n#### invoke\n\nWhen invoking a var that is related to the pod, let's call it a _proxy var_, the\npod client reaches out to the pod with the arguments encoded in EDN, JSON or Transit JSON. The\npod will then respond with a return value encoded in EDN, JSON or Transit JSON. The pod client\nwill then decode the return value and present the user with that.\n\nExample: the user invokes `(sql/execute! \"select * from foo\")`. The pod client\nsends this message to the pod:\n\n``` clojure\n{\"id\" \"1d17f8fe-4f70-48bf-b6a9-dc004e52d056\"\n \"var\" \"pod.lispyclouds.sqlite/execute!\"\n \"args\" \"[\\\"select * from foo\\\"]\"\n```\n\nThe `id` is unique identifier generated by the pod client which correlates this\nrequest with a response from the pod.\n\nAn example response from the pod could look like:\n\n``` clojure\n{\"id\" \"1d17f8fe-4f70-48bf-b6a9-dc004e52d056\"\n \"value\" \"[[1] [2]]\"\n \"status\" \"[\\\"done\\\"]\"}\n```\n\nHere, the `value` payload is the return value of the function invocation. The\nfield `status` contains `\"done\"`. This tells the pod client that this is the last\nmessage related to the request with `id` `1d17f8fe-4f70-48bf-b6a9-dc004e52d056`.\n\nNow you know most there is to know about the pod protocol!\n\n#### shutdown\n\nWhen the pod client is about to exit, it sends an `{\"op\" \"shutdown\"}` message, if the\npod has declared that it supports it in the `describe` response. Then it waits\nfor the pod process to end. This gives the pod a chance to clean up resources\nbefore it exits. If the pod does not support the `shutdown` op, the pod process\nis killed by the pod client.\n\n#### out and err\n\nPods may send messages with an `out` and `err` string value. The Pod Client prints\nthese messages to `*out*` and `*err*`. Stderr from the pod is redirected to\n`System/err`.\n\n``` clojure\n{\"id\" \"1d17f8fe-4f70-48bf-b6a9-dc004e52d056\"\n \"out\" \"hello\"}\n```\n\n``` clojure\n{\"id\" \"1d17f8fe-4f70-48bf-b6a9-dc004e52d056\"\n \"err\" \"debug\"}\n```\n\n#### readers\n\nIf `format` is `edn` then the pod may describe reader functions:\n\n``` clojure\n{\"readers\" {\"my/tag\" \"clojure.core/identity\"}}\n```\n\nso payloads containing tagged values like `#my/tag[1 2 3]` are read correctly as\n`[1 2 3]`.\n\n#### Error handling\n\nResponses may contain an `ex-message` string and `ex-data` payload string (JSON\nor EDN) along with an `\"error\"` value in `status`. This will cause the pod client to\nthrow an `ex-info` with the associated values.\n\nExample:\n\n``` clojure\n{\"id\" \"1d17f8fe-4f70-48bf-b6a9-dc004e52d056\"\n \"ex-message\" \"Illegal input\"\n \"ex-data\" \"{\\\"input\\\": 10}\n \"status\" \"[\\\"done\\\", \\\"error\\\"]\"}\n```\n\n#### Debugging\n\nTo debug your pod, you can write to stderr of the pod's process or write to a\nlog file. Currently, stderr is sent to stderr of the pod client.\n\n#### Environment\n\nThe pod client will set the `BABASHKA_POD` environment variable to `true` when\ninvoking the pod. This can be used by the invoked program to determine whether\nit should behave as a pod or not.\n\nAdded in v0.0.94.\n\n#### Client side code\n\nPods may implement functions and macros by sending arbitrary code to the pod\nclient in a `\"code\"` field as part of a `\"var\"` section. The code is evaluated\nby the pod client inside the declared namespace.\n\nFor example, a pod can define a macro called `do-twice`:\n\n``` clojure\n{\"format\" \"json\"\n \"namespaces\"\n [{\"name\" \"pod.babashka.demo\"\n   \"vars\" [{\"name\" \"do-twice\" \"code\" \"(defmacro do-twice [x] `(do ~x ~x))\"}]}]}\n```\n\nIn the pod client:\n\n``` clojure\n(pods/load-pod \"pod-babashka-demo\")\n(require '[pod.babashka.demo :as demo])\n(demo/do-twice (prn :foo))\n;;=\u003e\n:foo\n:foo\nnil\n```\n\n#### Metadata\n\n**From pod to pod client**\n\n*Fixed Metadata on vars*\n\nPods may attach metadata to functions and macros by sending data to the pod client\nin a `\"meta\"` field as part of a `\"var\"` section. The metadata must be an appropriate\nmap, encoded as an EDN string. This is only applicable to vars in the pod and will be\nignored if the var refers to Client-side code, since metadata can already be defined\nin those code blocks (see 'Dynamic Metadata' below to enable the encoding of metadata).\n\nFor example, a pod can define a function called `add`:\n\n``` clojure\n{\"format\" \"json\"\n \"namespaces\"\n [{\"name\" \"pod.babashka.demo\"\n   \"vars\" [{\"name\" \"add\"\n            \"meta\" \"{:doc \\\"arithmetic addition of 2 arguments\\\" :arglists ([a b])}\"}]}]}\n```\n\n*Dynamic Metadata*\n\nPods may send metadata on values returned to the client if metadata encoding is enabled\nfor the particular transport format used by the pod.\n\nFor example, if your pod uses `:transit+json` as its format, you can enable metadata\nencoding by adding `:transform transit/write-meta` (or whatever transit is aliased to)\nto the optional map passed to `transit/writer`. e.g.:\n\n````clojure\n(transit/writer baos :json {:transform transit/write-meta})\n````\n\n##### From pod client to pod\n\nCurrently sending metadata on arguments passed to a pod function is available only for the\n`transit+json` format and can be enabled on a per var basis.\n\nA pod can enable metadata to be read on arguments by sending the \"arg-meta\" field to \"true\"\nfor the var representing that function. For example:\n\n````clojure\n{:format :transit+json\n    :namespaces [{:name \"pod.babashka.demo\"\n                  :vars [{\"name\" \"round-trip\" \"arg-meta\" \"true\"}]}]}\n````\n\n#### Deferred namespace loading\n\nWhen your pod exposes multiple namespaces that can be used independently from\neach other, consider implementing the `load-ns` op which allows the pod client\nto load the namespace and process the client side code when it is loaded using\n`require`. This will speed up the initial setup of the pod in `load-pod`.\n\nIn `describe` the pod will mark the namespaces as deferred:\n\n``` clojure\n{\"name\" \"pod.lispyclouds.deferred-ns\"\n \"defer\" \"true\"}\n```\n\nWhen the user requires the namespace with `(require\n'[pod.lispyclouds.deferred-ns])` the pod client will then send a message:\n\n``` clojure\n{\"op\" \"load-ns\"\n \"ns\" \"pod.lispyclouds.deferred-ns\"\n \"id  \"...\"}\n```\n\nupon which the pod will reply with the namespace data:\n\n``` clojure\n{\"name\" \"pod.lispyclouds.deferred-ns\"\n \"vars\" [{\"name\" \"myfunc\" \"code\" \"(defn my-func [])\"}]\n \"id\" \"...\"}\n```\n\nIf a deferred namespace depends on another deferred namespace, provide explicit\n`require`s in `code` segments:\n\n``` clojure\n{\"name\" \"pod.lispyclouds.another-deferred-ns\"\n \"vars\"\n [{\"name\" \"myfunc\"\n   \"code\" \"(require '[pod.lispyclouds.deferred-ns :as dns])\n           (defn my-func [] (dns/x))\"}]\n \"id\" \"...\"}\n```\n\n#### Async\n\nAsynchronous functions can be implemented using callbacks.\n\nThe pod will first declare a wrapper function accepting user provided callbacks\nas client side code. An example from the\n[filewatcher](https://github.com/babashka/pod-babashka-filewatcher) pod:\n\n``` clojure\n(defn watch\n  ([path cb] (watch path cb {}))\n  ([path cb opts]\n   (babashka.pods/invoke\n    \"pod.babashka.filewatcher\"\n    'pod.babashka.filewatcher/watch*\n    [path opts]\n    {:handlers {:success (fn [event] (cb (update event :type keyword)))\n                :error (fn [{:keys [:ex-message :ex-data]}]\n                         (binding [*out* *err*]\n                           (println \"ERROR:\" ex-message)))}})\n   nil))\n```\n\nThe wrapper function will then invoke `babashka.pods/invoke`, a lower level\nfunction to invoke a pod var with callbacks.\n\nThe arguments to `babashka.pods/invoke` are:\n\n- a pod identifier string derived from the first described namespace.\n- the symbol of the var to invoke\n- the arguments to the var\n- an opts map containing `:handlers` containing callback functions: `:success`, `:error` and `:done`\n\nThe return value of `babashka.pods/invoke` is a map containing `:result`. When\nnot using callbacks, this is the return value from the pod var invocation. When\nusing callbacks, this value is undefined.\n\nThe callback `:success` is called with a map containing a return value from the\npod invocation. The pod can potentially return multiple values. The callback\nwill be called with every value individually.\n\nThe callback `:error` is called in case the pod sends an error, a map\ncontaining:\n\n- `:ex-message`: an error message\n- `:ex-data`: an arbitrary additional error data map. Typically it will contain\n  `:type` describing the type of exception that happened in the pod.\n\nIf desired, `:ex-message` and `:ex-data` can be reified into a\n`java.lang.Exception` using `ex-info`.\n\nThe callback `:done` is a 0-arg function. This callback can be used to determine\nif the pod is done sending values, in case it wants to send multiple. The\ncallback is only called if no errors were sent by the pod.\n\nIn the above example the wrapper function calls the pod identified by\n`\"pod.babashka.filewatcher\"`. It calls the var\n`pod.babashka.filewatcher/watch*`. In `:success` it pulls out received\nvalues, passing them to the user-provided callback. Additionally, it prints any\nerrors received from the pod library in `:error` to `*err*`.\n\nA user will then use `pod.babashka.filewatcher/watch` like this:\n\n``` clojure\n$ clj\nClojure 1.10.1\nuser=\u003e (require '[babashka.pods :as pods])\nnil\nuser=\u003e (pods/load-pod \"pod-babashka-filewatcher\")\nnil\nuser=\u003e (require '[pod.babashka.filewatcher :as fw])\nnil\nuser=\u003e (fw/watch \"/tmp\" (fn [result] (prn \"result\" result)))\nnil\nuser=\u003e (spit \"/tmp/foobar123.txt\" \"foo\")\nnil\nuser=\u003e \"result\" {:path \"/private/tmp/foobar123.txt\", :type :create}\n```\n\n## Run tests\n\nTo run the tests for the pods library:\n\n```\n$ script/test\n```\n\n## License\n\nCopyright © 2020 Michiel Borkent\n\nDistributed under the EPL License. See LICENSE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbabashka%2Fpods","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbabashka%2Fpods","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbabashka%2Fpods/lists"}