zshrs 0.10.9

The first compiled Unix shell — bytecode VM, worker pool, AOP intercept, SQLite caching
Documentation
# daemon-shell.nu — nushell client for zshrs-daemon's HTTP service.
#
# Source from your config (`config nu` then add at bottom, or via
# $env.config.nu_files):
#
#     source path/to/daemon-shell.nu
#     daemon ping
#     daemon record alias gst "git status"
#     daemon defs query --kind alias --shell-id nu
#
# Nushell idiom: subcommands under `daemon …` (multi-word custom commands)
# rather than hyphen-prefixed names — feels native, completes naturally.
#
# Endpoint and auth come from env (in your config.nu):
#
#     $env.DAEMON_URL = "http://127.0.0.1:7733"
#     $env.DAEMON_TOKEN = "<bearer-token>"      # optional
#     $env.DAEMON_SHELL_ID = "nu"               # see docs/SHELL_IDS.md
#
# All commands return parsed records / tables — `http get` and
# `http post` decode JSON automatically. No `jq` needed.

# ---- Config defaults ------------------------------------------------------

if not ('DAEMON_URL'      in $env) { $env.DAEMON_URL      = "http://127.0.0.1:7733" }
if not ('DAEMON_TOKEN'    in $env) { $env.DAEMON_TOKEN    = "" }
if not ('DAEMON_SHELL_ID' in $env) { $env.DAEMON_SHELL_ID = "nu" }

# ---- Internal helpers -----------------------------------------------------

# Build the auth header record. nushell's `http` accepts `--headers`
# as a flat list `[name value name value]`.
def _daemon_headers []: nothing -> list<string> {
    if ($env.DAEMON_TOKEN | is-empty) {
        []
    } else {
        ["Authorization" $"Bearer ($env.DAEMON_TOKEN)"]
    }
}

# Internal: POST /op/<name> with JSON body record. Single-line — nu's
# `\` line-continuation only works after operators, not in the middle
# of a flag list.
def _daemon_post [op: string, body: record = {}]: nothing -> any {
    http post --content-type application/json --headers (_daemon_headers) $"($env.DAEMON_URL)/op/($op)" $body
}

# Internal: GET an endpoint (/health, /ops, /metrics).
def _daemon_get [endpoint: string]: nothing -> any {
    http get --headers (_daemon_headers) $"($env.DAEMON_URL)($endpoint)"
}

# ---- Public commands ------------------------------------------------------

# `daemon health` — liveness + version probe.
def "daemon health" []: nothing -> any { _daemon_get "/health" }

# `daemon ops` — list every op the daemon accepts.
def "daemon ops" []: nothing -> any { _daemon_get "/ops" }

# `daemon info` — full daemon snapshot.
def "daemon info" []: nothing -> any { _daemon_post info {} }

# `daemon ping [echo]` — round-trip latency.
def "daemon ping" [...echo: string]: nothing -> any {
    let body = if ($echo | is-empty) { {} } else { { echo: ($echo | str join " ") } }
    _daemon_post ping $body
}

# `daemon call OP [JSON-RECORD]` — generic op caller for ops without
# a dedicated wrapper.
def "daemon call" [op: string, body: record = {}]: nothing -> any {
    _daemon_post $op $body
}

# ---- Federated recorder (definitions.*) ----------------------------------

def _daemon_emit [
    kind: string,
    name: string,
    value: string = "",
    --file: string,
    --line: int,
    --fn-chain: string,
]: nothing -> any {
    mut body = {
        shell_id: $env.DAEMON_SHELL_ID
        kind: $kind
        name: $name
    }
    # Optional flags default to null when not supplied — `is-not-empty`
    # treats null and "" the same, but `?` postfix only works on cell
    # paths, not regular vars. Compare against null directly.
    if ($value | is-not-empty) { $body = ($body | upsert value $value) }
    if ($file != null and $file != "") { $body = ($body | upsert file $file) }
    if ($line != null) { $body = ($body | upsert line $line) }
    if ($fn_chain != null and $fn_chain != "") { $body = ($body | upsert fn_chain $fn_chain) }
    _daemon_post definitions_emit $body
}

def "daemon record alias"     [name: string, body: string]: nothing -> any { _daemon_emit alias $name $body }
def "daemon record galias"    [name: string, body: string]: nothing -> any { _daemon_emit galias $name $body }
def "daemon record salias"    [name: string, body: string]: nothing -> any { _daemon_emit salias $name $body }
def "daemon record function"  [name: string, body: string]: nothing -> any { _daemon_emit function $name $body }
def "daemon record export"    [name: string, value: string]: nothing -> any { _daemon_emit env $name $value }
def "daemon record param"     [name: string, value: string]: nothing -> any { _daemon_emit params $name $value }
def "daemon record bindkey"   [seq: string, widget: string]: nothing -> any { _daemon_emit bindkey $seq $widget }
def "daemon record compdef"   [cmd: string, completer: string]: nothing -> any { _daemon_emit compdef $cmd $completer }
def "daemon record zstyle"    [pattern: string, style: string]: nothing -> any { _daemon_emit zstyle $pattern $style }
def "daemon record zmodload"  [module: string]: nothing -> any { _daemon_emit zmodload $module }
def "daemon record setopt"    [opt: string]: nothing -> any { _daemon_emit setopt $opt "on" }
def "daemon record unsetopt"  [opt: string]: nothing -> any { _daemon_emit setopt $opt "off" }
def "daemon record source"    [path: string]: nothing -> any { _daemon_emit source $path }
def "daemon record path"      [dir: string]: nothing -> any { _daemon_emit path $dir }
def "daemon record fpath"     [dir: string]: nothing -> any { _daemon_emit fpath $dir }
def "daemon record zle"       [widget: string, body: string = ""]: nothing -> any { _daemon_emit zle $widget $body }
def "daemon record trap"      [signal: string, handler: string]: nothing -> any { _daemon_emit trap $signal $handler }
def "daemon record named-dir" [name: string, path: string]: nothing -> any { _daemon_emit named_dir $name $path }
def "daemon record completion" [cmd: string, path: string = ""]: nothing -> any { _daemon_emit completion $cmd $path }

# ---- Federated catalog query / diff --------------------------------------

# `daemon defs query [--kind K] [--name N] [--prefix P] [--shell-id S] [--limit N]`
def "daemon defs query" [
    --kind: string,
    --name: string,
    --prefix: string,
    --shell-id: string,
    --limit: int,
]: nothing -> any {
    mut body = {}
    if ($kind     != null and $kind     != "") { $body = ($body | upsert kind $kind) }
    if ($name     != null and $name     != "") { $body = ($body | upsert name $name) }
    if ($prefix   != null and $prefix   != "") { $body = ($body | upsert prefix $prefix) }
    if ($shell_id != null and $shell_id != "") { $body = ($body | upsert shell_id $shell_id) }
    if ($limit    != null)                     { $body = ($body | upsert limit $limit) }
    _daemon_post definitions_query $body
}

def "daemon defs kinds" []: nothing -> any { _daemon_post definitions_kinds {} }

# `daemon defs diff SHELL_A SHELL_B [KIND]`
def "daemon defs diff" [shell_a: string, shell_b: string, kind?: string]: nothing -> any {
    mut body = { shell_a: $shell_a, shell_b: $shell_b }
    if ($kind != null and $kind != "") { $body = ($body | upsert kind $kind) }
    _daemon_post definitions_diff $body
}

# ---- Pubsub --------------------------------------------------------------
#
# Streaming endpoints (/stream/*) deliver SSE; nushell's `http get` doesn't
# do streaming today, so use `curl -N` for `daemon watch` / `daemon events`
# from a regular $env.DAEMON_URL setup. `daemon publish` is a one-shot
# POST and works fine.

# `daemon publish TOPIC DATA-RECORD`
def "daemon publish" [topic: string, data: any]: nothing -> any {
    _daemon_post publish { topic: $topic, data: $data }
}