#!/usr/bin/env bb
;; Vers-Duckhole: Traversable connection through duck conversation spacetime
;; 🦆🕳️ Like a wormhole, but for ducks
;; Usage:
;; ./vers-duckhole.bb --serve # Open the duckhole locally
;; ./vers-duckhole.bb --deploy <vm-id> # Deploy duckhole to vers VM
;; ./vers-duckhole.bb --ngrok # Tunnel through the duckhole
;; ./vers-duckhole.bb --query <sql> # Query across the duckhole
(require '[babashka.process :refer [shell process]]
'[clojure.string :as str]
'[cheshire.core :as json]
'[babashka.fs :as fs]
'[org.httpkit.server :as http])
;;; ============================================================
;;; Configuration
;;; ============================================================
(def PORT (or (some-> (System/getenv "PORT") parse-long) 8080))
(def DUCK_PATHS
{:bootstrap (str (System/getProperty "user.home") "/.effective-topos/bootstrap.duckdb")
:claude-db (str (System/getProperty "user.home") "/.claude/interactions.duckdb")
:claude (str (System/getProperty "user.home") "/.claude/history.jsonl")
:codex (str (System/getProperty "user.home") "/.codex/history.jsonl")
:duck (str (System/getProperty "user.home") "/.duck/history.jsonl")
:postduck (str (System/getProperty "user.home") "/.postduck/history.jsonl")})
;;; ============================================================
;;; DuckDB Integration
;;; ============================================================
(defn duck-query [db sql]
(let [result (shell {:out :string :err :string :continue true}
"duckdb" db "-json" "-c" sql)]
(if (= 0 (:exit result))
{:ok true :data (try (json/parse-string (:out result) true)
(catch Exception _ (:out result)))}
{:ok false :error (:err result)})))
(defn get-conversation-history [source limit]
(let [path (get DUCK_PATHS (keyword source))]
(if (and path (fs/exists? path))
(duck-query ":memory:"
(str "SELECT * FROM read_ndjson('" path "', ignore_errors=true) LIMIT " limit))
{:ok false :error (str "Source not found: " source)})))
(defn get-vm-inventory []
(duck-query (:bootstrap DUCK_PATHS)
"SELECT vm_id, image_name, codex_version, created_at FROM vm_inventory ORDER BY created_at DESC"))
(defn get-unified-interactions [limit]
(if (fs/exists? (:claude-db DUCK_PATHS))
(duck-query (:claude-db DUCK_PATHS)
(str "SELECT source, role, content, timestamp FROM unified_interactions ORDER BY timestamp DESC LIMIT " limit))
{:ok false :error "interactions.duckdb not found"}))
;;; ============================================================
;;; Vers CLI Integration
;;; ============================================================
(defn vers-execute [vm-id cmd]
(let [result (shell {:out :string :err :string :continue true}
"vers" "execute" vm-id cmd)]
(if (= 0 (:exit result))
{:ok true :output (:out result)}
{:ok false :error (:err result)})))
(defn vers-list-aliases []
(let [result (shell {:out :string :err :string :continue true}
"vers" "alias")]
(if (= 0 (:exit result))
{:ok true :aliases (str/split-lines (:out result))}
{:ok false :error (:err result)})))
(defn deploy-to-vm [vm-id]
(println (str "🚀 Deploying duck-web-viewer to VM " vm-id "..."))
;; Install dependencies
(println "📦 Installing dependencies...")
(vers-execute vm-id "pacman -Syu --noconfirm nodejs npm curl python python-pip 2>/dev/null || apt-get update && apt-get install -y nodejs npm curl python3 python3-pip")
(vers-execute vm-id "curl -LsSf https://astral.sh/uv/install.sh | sh")
(vers-execute vm-id "npm install -g nbb ai-agent-skills")
(vers-execute vm-id "~/.local/bin/uv pip install --system duckdb textual rich")
;; Copy viewer script
(println "📄 Copying duck-web-viewer...")
(let [script (slurp "duck-web-viewer.cljs")]
(vers-execute vm-id (str "cat > /app/duck-web-viewer.cljs << 'EOF'\n" script "\nEOF")))
;; Start server
(println "🌐 Starting server...")
(vers-execute vm-id "nohup nbb /app/duck-web-viewer.cljs > /var/log/duck-viewer.log 2>&1 &")
(println (str "✅ Deployed! Access via: ssh -L 8080:localhost:8080 " vm-id ".vm.vers.sh")))
;;; ============================================================
;;; HTTP Server (local)
;;; ============================================================
(defn html-wrap [title content]
(str "<!DOCTYPE html>
<html>
<head>
<title>" title "</title>
<style>
body { font-family: monospace; background: #0a0a0f; color: #e0e0e0; padding: 2rem; }
h1 { color: #26d826; }
h2 { color: #4ecdc4; }
pre { background: #1a1a2e; padding: 1rem; border-radius: 4px; overflow-x: auto; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #333; padding: 0.5rem; text-align: left; }
th { background: #1a1a2e; color: #26d826; }
a { color: #4ecdc4; }
.nav a { margin-right: 1rem; padding: 0.5rem 1rem; background: #1a1a2e; border-radius: 4px; text-decoration: none; }
.entry { background: #16213e; margin: 0.5rem 0; padding: 1rem; border-radius: 4px; border-left: 3px solid #26d826; }
</style>
</head>
<body>
<div class='nav'>
<a href='/'>Home</a>
<a href='/conversations?source=claude'>Claude</a>
<a href='/conversations?source=codex'>Codex</a>
<a href='/vms'>VMs</a>
<a href='/health'>Health</a>
</div>
" content "
</body></html>"))
(defn handler [{:keys [uri query-string]}]
(let [params (when query-string
(->> (str/split query-string #"&")
(map #(str/split % #"="))
(into {})))]
(cond
(= uri "/")
{:status 200
:headers {"Content-Type" "text/html"}
:body (html-wrap "Duck Viewer"
"<h1>🦆 Duck Conversation Viewer</h1>
<p>Vers CLI pathway to duck conversation history</p>
<h2>Endpoints</h2>
<ul>
<li><a href='/conversations?source=claude&limit=20'>Claude History</a></li>
<li><a href='/conversations?source=codex&limit=20'>Codex History</a></li>
<li><a href='/conversations?source=duck&limit=20'>Duck History</a></li>
<li><a href='/vms'>VM Inventory</a></li>
<li><a href='/unified?limit=20'>Unified Interactions</a></li>
</ul>")}
(= uri "/health")
{:status 200
:headers {"Content-Type" "application/json"}
:body (json/generate-string
{:status "ok"
:duckdb (fs/exists? (:bootstrap DUCK_PATHS))
:claude (fs/exists? (:claude DUCK_PATHS))
:codex (fs/exists? (:codex DUCK_PATHS))
:timestamp (str (java.time.Instant/now))})}
(= uri "/conversations")
(let [source (or (get params "source") "claude")
limit (or (get params "limit") "20")
result (get-conversation-history source limit)]
{:status 200
:headers {"Content-Type" "text/html"}
:body (html-wrap (str source " History")
(if (:ok result)
(str "<h1>" source " History</h1>"
"<p>" (count (:data result)) " entries</p>"
(str/join "\n"
(map (fn [e]
(str "<div class='entry'><pre>"
(subs (pr-str e) 0 (min 500 (count (pr-str e))))
"...</pre></div>"))
(:data result))))
(str "<pre>Error: " (:error result) "</pre>")))})
(= uri "/vms")
(let [result (get-vm-inventory)]
{:status 200
:headers {"Content-Type" "text/html"}
:body (html-wrap "VMs"
(if (:ok result)
(str "<h1>VM Inventory</h1>"
"<table><tr><th>VM ID</th><th>Image</th><th>Codex</th></tr>"
(str/join "\n"
(map (fn [vm]
(str "<tr><td>" (:vm_id vm) "</td>"
"<td>" (:image_name vm) "</td>"
"<td>" (:codex_version vm) "</td></tr>"))
(:data result)))
"</table>")
(str "<pre>Error: " (:error result) "</pre>")))})
(= uri "/api/query")
(let [sql (get params "sql")
db (or (get params "db") ":memory:")
result (duck-query db sql)]
{:status 200
:headers {"Content-Type" "application/json"}
:body (json/generate-string result)})
:else
{:status 404 :body "Not Found"})))
(defn start-server []
(println (str "🦆🕳️ Opening duckhole at http://localhost:" PORT))
(http/run-server handler {:port PORT})
(println "✅ Server running. Press Ctrl+C to stop.")
@(promise))
;;; ============================================================
;;; Main
;;; ============================================================
(defn -main [& args]
(let [cmd (first args)]
(case cmd
"--serve" (start-server)
"--deploy" (if-let [vm-id (second args)]
(deploy-to-vm vm-id)
(println "Usage: --deploy <vm-id>"))
"--ngrok" (do
(println "🔗 Starting with ngrok...")
(future (shell "ngrok" "http" (str PORT)))
(Thread/sleep 2000)
(start-server))
"--query" (let [sql (str/join " " (rest args))
result (duck-query ":memory:" sql)]
(println (json/generate-string result {:pretty true})))
"--vms" (let [result (get-vm-inventory)]
(doseq [vm (:data result)]
(println (str (:vm_id vm) " | " (:image_name vm) " | " (:codex_version vm)))))
(do
(println "🦆🕳️ vers-duckhole - like a wormhole, but for ducks")
(println)
(println "Usage: vers-duckhole.bb [command]")
(println)
(println "Commands:")
(println " --serve Open the duckhole locally")
(println " --deploy <vm-id> Deploy duckhole to vers VM")
(println " --ngrok Tunnel through the duckhole")
(println " --query <sql> Query across the duckhole")
(println " --vms List VMs visible through duckhole")))))
(apply -main *command-line-args*)