Something went wrong. Try again.
Clojure SDK for Pocketenv
Something went wrong. Try again.
14 kB · 357 lines
Clojure
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358(ns pocketenv-io.api "Internal HTTP layer. Not part of the public API. Consumers should use pocketenv-io.pocketenv and pocketenv-io.sandbox." (:require [pocketenv-io.client :as client] [pocketenv-io.crypto :as crypto] [pocketenv-io.sandbox :as types] [clojure.string :as str]))
(def ^:private default-base "at://did:plc:aturpi2ls3yvsmhc6wybomun/io.pocketenv.sandbox/openclaw")
;; ---------------------------------------------------------------------------;; Helpers;; ---------------------------------------------------------------------------
(defn- maybe-assoc [m k v] (if (some? v) (assoc m k v) m))
(defn- redact-tailscale-key [auth-key] (if (> (count auth-key) 14) (str (subs auth-key 0 11) (apply str (repeat (- (count auth-key) 14) "*")) (subs auth-key (- (count auth-key) 3))) auth-key))
(defn- redact-ssh-private-key [private-key] (let [header "-----BEGIN OPENSSH PRIVATE KEY-----" footer "-----END OPENSSH PRIVATE KEY-----"] (if (and (str/includes? private-key header) (str/includes? private-key footer)) (let [header-end (.indexOf private-key header) body-start (+ header-end (count header)) footer-start (.indexOf private-key footer) body (subs private-key body-start footer-start) chars (vec body) non-nl-idxs (->> (map-indexed vector chars) (remove (fn [[_ c]] (= c \newline))) (mapv first)) masked-chars (if (> (count non-nl-idxs) 15) (let [n (count non-nl-idxs) middle (set (take (- n 15) (drop 10 non-nl-idxs)))] (map-indexed (fn [i c] (if (middle i) \* c)) chars)) chars) masked-body (apply str masked-chars)] (-> (str header masked-body footer) (str/replace "\n" "\\n"))) (str/replace private-key "\n" "\\n"))))
;; ---------------------------------------------------------------------------;; Sandbox CRUD;; ---------------------------------------------------------------------------
(defn create-sandbox "Creates a new sandbox. Options: :base, :provider, :repo, :keep-alive, :token" [name opts] (let [body (-> {"name" name "base" (get opts :base default-base) "provider" (str (get opts :provider "cloudflare"))} (maybe-assoc "repo" (:repo opts)) (maybe-assoc "keepAlive" (:keep-alive opts))) r (client/post "/xrpc/io.pocketenv.sandbox.createSandbox" body opts)] (if-let [data (:ok r)] {:ok (types/sandbox-from-map data)} r)))
(defn start-sandbox "Starts a sandbox by name/id. Options: :repo, :keep-alive, :token" [id opts] (let [body (-> {} (maybe-assoc "repo" (:repo opts)) (maybe-assoc "keepAlive" (:keep-alive opts)))] (client/post "/xrpc/io.pocketenv.sandbox.startSandbox" body (assoc opts :params {"id" id}))))
(defn stop-sandbox "Stops a sandbox by name/id. Options: :token" [id opts] (client/post "/xrpc/io.pocketenv.sandbox.stopSandbox" nil (assoc opts :params {"id" id})))
(defn delete-sandbox "Deletes a sandbox by name/id. Options: :token" [id opts] (client/post "/xrpc/io.pocketenv.sandbox.deleteSandbox" nil (assoc opts :params {"id" id})))
;; ---------------------------------------------------------------------------;; Sandbox queries;; ---------------------------------------------------------------------------
(defn get-sandbox "Fetches a single sandbox by id or name. Returns {:ok sandbox} or {:ok nil}." [id opts] (let [r (client/get "/xrpc/io.pocketenv.sandbox.getSandbox" (assoc opts :params {"id" id}))] (if-let [data (:ok r)] (let [sb (or (get data "sandbox") data)] {:ok (when sb (types/sandbox-from-map sb))}) r)))
(defn list-sandboxes "Lists the public sandbox catalog. Options: :limit (30), :offset (0), :token" [opts] (let [r (client/get "/xrpc/io.pocketenv.sandbox.getSandboxes" (assoc opts :params {"limit" (get opts :limit 30) "offset" (get opts :offset 0)}))] (if-let [data (:ok r)] {:ok {:sandboxes (mapv types/sandbox-from-map (get data "sandboxes")) :total (get data "total")}} r)))
(defn list-sandboxes-by-actor "Lists sandboxes for a given actor (DID or handle). Options: :limit, :offset, :token" [did opts] (let [r (client/get "/xrpc/io.pocketenv.actor.getActorSandboxes" (assoc opts :params {"did" did "limit" (get opts :limit 30) "offset" (get opts :offset 0)}))] (if-let [data (:ok r)] {:ok {:sandboxes (mapv types/sandbox-from-map (get data "sandboxes")) :total (get data "total")}} r)))
(defn wait-until-running "Polls until the sandbox is :running or timeout is reached.
Options: :timeout-ms (60000), :interval-ms (2000), :token" [id opts] (let [timeout-ms (get opts :timeout-ms 60000) interval-ms (get opts :interval-ms 2000) deadline (+ (System/currentTimeMillis) timeout-ms)] (loop [] (if (>= (System/currentTimeMillis) deadline) {:error :timeout} (let [r (get-sandbox id opts)] (cond (and (:ok r) (= :running (:status (:ok r)))) r (:ok r) (do (Thread/sleep interval-ms) (recur)) :else r))))))
;; ---------------------------------------------------------------------------;; Exec;; ---------------------------------------------------------------------------
(defn exec "Executes a shell command in the sandbox. Returns {:ok ExecResult}." [id cmd args opts] (let [command (str/join " " (cons cmd args)) r (client/post "/xrpc/io.pocketenv.sandbox.exec" {"command" command} (assoc opts :params {"id" id}))] (if-let [data (:ok r)] {:ok (types/exec-result-from-map data)} r)))
;; ---------------------------------------------------------------------------;; Ports;; ---------------------------------------------------------------------------
(defn expose-port "Exposes a port on the sandbox. Returns {:ok preview-url-or-nil}." [id port opts] (let [body (-> {"port" port} (maybe-assoc "description" (:description opts))) r (client/post "/xrpc/io.pocketenv.sandbox.exposePort" body (assoc opts :params {"id" id}))] (if-let [data (:ok r)] {:ok (get data "previewUrl")} r)))
(defn unexpose-port "Removes an exposed port from the sandbox." [id port opts] (client/post "/xrpc/io.pocketenv.sandbox.unexposePort" {"port" port} (assoc opts :params {"id" id})))
(defn list-ports "Lists all exposed ports on the sandbox. Returns {:ok [Port]}." [id opts] (let [r (client/get "/xrpc/io.pocketenv.sandbox.getExposedPorts" (assoc opts :params {"id" id}))] (if-let [data (:ok r)] {:ok (mapv types/port-from-map (get data "ports"))} r)))
;; ---------------------------------------------------------------------------;; VS Code;; ---------------------------------------------------------------------------
(defn expose-vscode "Exposes VS Code Server. Returns {:ok preview-url-or-nil}." [id opts] (let [r (client/post "/xrpc/io.pocketenv.sandbox.exposeVscode" nil (assoc opts :params {"id" id}))] (if-let [data (:ok r)] {:ok (get data "previewUrl")} r)))
;; ---------------------------------------------------------------------------;; Copy;; ---------------------------------------------------------------------------
(defn push-directory "Asks the sandbox to push `directory-path` to storage. Returns {:ok uuid}." [sandbox-id directory-path opts] (let [r (client/post "/xrpc/io.pocketenv.sandbox.pushDirectory" {"sandboxId" sandbox-id "directoryPath" directory-path} opts)] (if-let [data (:ok r)] {:ok (get data "uuid")} r)))
(defn pull-directory "Asks the sandbox to pull the archive identified by `uuid` into `directory-path`." [sandbox-id uuid directory-path opts] (client/post "/xrpc/io.pocketenv.sandbox.pullDirectory" {"uuid" uuid "sandboxId" sandbox-id "directoryPath" directory-path} opts))
;; ---------------------------------------------------------------------------;; Actor / Profile;; ---------------------------------------------------------------------------
(defn me "Fetches the profile of the currently authenticated user." [opts] (let [r (client/get "/xrpc/io.pocketenv.actor.getProfile" opts)] (if-let [data (:ok r)] {:ok (types/profile-from-map data)} r)))
(defn get-profile "Fetches the profile of any actor by DID or handle." [did opts] (let [r (client/get "/xrpc/io.pocketenv.actor.getProfile" (assoc opts :params {"did" did}))] (if-let [data (:ok r)] {:ok (types/profile-from-map data)} r)))
;; ---------------------------------------------------------------------------;; Secrets;; ---------------------------------------------------------------------------
(defn list-secrets "Lists all secrets for a sandbox. Options: :limit (100), :offset (0), :token" [sandbox-id opts] (let [r (client/get "/xrpc/io.pocketenv.secret.getSecrets" (assoc opts :params {"sandboxId" sandbox-id "limit" (get opts :limit 100) "offset" (get opts :offset 0)}))] (if-let [data (:ok r)] {:ok (mapv types/secret-from-map (get data "secrets"))} r)))
(defn add-secret "Adds an encrypted secret to a sandbox." [sandbox-id name value opts] (let [encrypted (crypto/encrypt value)] (client/post "/xrpc/io.pocketenv.secret.addSecret" {"secret" {"sandboxId" sandbox-id "name" name "value" encrypted}} opts)))
(defn delete-secret "Deletes a secret by its id." [id opts] (client/post "/xrpc/io.pocketenv.secret.deleteSecret" nil (assoc opts :params {"id" id})))
;; ---------------------------------------------------------------------------;; SSH Keys;; ---------------------------------------------------------------------------
(defn get-ssh-keys "Fetches the SSH key pair for a sandbox." [sandbox-id opts] (let [r (client/get "/xrpc/io.pocketenv.sandbox.getSshKeys" (assoc opts :params {"id" sandbox-id}))] (if-let [data (:ok r)] {:ok (types/ssh-key-from-map data)} r)))
(defn put-ssh-keys "Stores an SSH key pair. Private key is encrypted client-side." [sandbox-id private-key public-key opts] (let [encrypted-private-key (crypto/encrypt private-key) redacted (redact-ssh-private-key private-key)] (client/post "/xrpc/io.pocketenv.sandbox.putSshKeys" {"id" sandbox-id "privateKey" encrypted-private-key "publicKey" public-key "redacted" redacted} opts)))
;; ---------------------------------------------------------------------------;; Tailscale;; ---------------------------------------------------------------------------
(defn get-tailscale-auth-key "Fetches the Tailscale auth key for a sandbox." [sandbox-id opts] (let [r (client/get "/xrpc/io.pocketenv.sandbox.getTailscaleAuthKey" (assoc opts :params {"id" sandbox-id}))] (if-let [data (:ok r)] {:ok (types/tailscale-auth-key-from-map data)} r)))
(defn put-tailscale-auth-key "Stores a Tailscale auth key. Must start with \"tskey-auth-\". Encrypted client-side." [sandbox-id auth-key opts] (when-not (str/starts-with? auth-key "tskey-auth-") (throw (ex-info "Tailscale auth key must start with \"tskey-auth-\"" {:auth-key auth-key}))) (let [encrypted (crypto/encrypt auth-key) redacted (redact-tailscale-key auth-key)] (client/post "/xrpc/io.pocketenv.sandbox.putTailscaleAuthKey" {"id" sandbox-id "authKey" encrypted "redacted" redacted} opts)))
;; ---------------------------------------------------------------------------;; Backups;; ---------------------------------------------------------------------------
(defn create-backup "Creates a backup of `directory` in the sandbox. Options: :description, :ttl, :token" [sandbox-id directory opts] (let [body (-> {"directory" directory} (maybe-assoc "description" (:description opts)) (maybe-assoc "ttl" (:ttl opts)))] (client/post "/xrpc/io.pocketenv.sandbox.createBackup" body (assoc opts :params {"id" sandbox-id}))))
(defn list-backups "Lists all backups for a sandbox. Returns {:ok [Backup]}." [sandbox-id opts] (let [r (client/get "/xrpc/io.pocketenv.sandbox.getBackups" (assoc opts :params {"id" sandbox-id}))] (if-let [data (:ok r)] {:ok (mapv types/backup-from-map (get data "backups"))} r)))
(defn restore-backup "Restores a backup by its id." [backup-id opts] (client/post "/xrpc/io.pocketenv.sandbox.restoreBackup" {"backupId" backup-id} opts))