#!/usr/bin/env nu const HEADER = " ----- MEAT ---------------------------------------- " const FOOTER = " --------------------------------------------------- " const HELP_TEXT = " YUM - CONSUME DELICIOUS MEATS COOK - ONLY PREPARE MEATS POKE - TASTE SUSPICIOUS MEATS GUT - CLEAN MEAT STORES FRESH - HUNT FRESH MEATS LOOK - LOOK FOR FRESHER MEATS HUNT - HUNT FOR MEATS IN NIXPKGS RITUAL - PERFORM RITUAL THEN CONSUME TRADE - SEND MEATS FAR AWAY ..-A - ..ALL MEATS" def meat-print [text: string] { print $"\n \t($text)\n" } def with-frame [body: closure] { print -n $HEADER do $body print -n $FOOTER } def hn []: nothing -> string { ^hostname | str trim } def system-attr []: nothing -> string { $"nixosConfigurations.(hn).config.system.build.toplevel" } def nix-conf-attr []: nothing -> string { $"nixosConfigurations.(hn).config.environment.etc.\"nix/nix.conf\"" } def nix-build-nom [out: string, source: string, attr: string, extras: list = []] { if ($env.MONITOR? | is-empty) { ^nix-build --out-link $out $source -A $attr ...$extras } else { ^nix-build --log-format internal-json --out-link $out $source -A $attr ...$extras out+err>| ^$env.MONITOR --json } } def differ-step [build: string] { if ($env.DIFFER? | is-empty) { meat-print " (no DIFFER set; skipping diff)" } else { ^$env.DIFFER /nix/var/nix/profiles/system $build } } def activate [build_path: string] { ^sudo $nu.current-exe -c $"nix-env --set -p /nix/var/nix/profiles/system ($build_path); ($build_path)/bin/switch-to-configuration switch" } def do-build [extras: list = []] { let tmpdir = ^mktemp -d -t "meat-build.XXXXXX" | str trim let build = $"($tmpdir)/build" nix-build-nom $build $"($env.MEATS)/entry.nix" (system-attr) $extras differ-step $build activate $build try { rm $build } } def cmd-help [] { print -n $"($HEADER)($HELP_TEXT)($FOOTER)\n" } def cmd-yum [...args: string] { with-frame { meat-print "CONSUMING DELICIOUS MEATS.." try { do-build $args } catch { print "FAILED TO CONSUME MEATS." } } } def cmd-cook [...args: string] { with-frame { meat-print "PREPARING DELICIOUS MEATS.." try { ^nix-build --no-out-link $"($env.MEATS)/entry.nix" -A (system-attr) ...$args } } } def cmd-poke [...args: string] { with-frame { meat-print "PREPARING SUSPICIOUS MEATS.." try { ^nix-build --no-out-link --show-trace $"($env.MEATS)/entry.nix" -A (system-attr) ...$args } } } def cmd-gut [...args: string] { with-frame { meat-print "CLEANING MEAT STORES.." try { ^nh clean all ...$args } } } def cmd-trade [] { with-frame { meat-print "TRADING FOREIGN MEATS.." meat-print "tbd" } } def pins-path [] { $"($env.MEATS)/pins/pins.toml" } def lock-path [] { $"($env.MEATS)/pins/pins.lock.json" } def load-pins [] { let p = pins-path if not ($p | path exists) { error make { msg: $"no pins file at ($p)" } } open --raw $p | from toml } def load-lock [] { let p = lock-path if ($p | path exists) { open --raw $p | from json } else { {} } } # Sort keys alphabetically and write atomically. def write-lock [lock: record] { let p = lock-path let sorted = $lock | columns | sort | reduce -f {} { |k, acc| $acc | insert $k ($lock | get $k) } let tmp = $"($p).tmp" $sorted | to json --indent 2 | save -f $tmp ^mv $tmp $p } # scheme:rest → expansion via {path} template, or pass-through. def expand-shorturl [url: string, shorturls: record] { let parts = $url | split row ":" -n 2 if (($parts | length) < 2) { return $url } let scheme = $parts | get 0 if not ($scheme in ($shorturls | columns)) { return $url } ($shorturls | get $scheme) | str replace --regex '\{path\}' ($parts | get 1) } # Decompose to {git_url, ref?} for cheap ls-remote queries (used by `look`). def parse-git-target [url: string] { if ($url | str starts-with "github:") { let body = $url | str substring 7.. let path_query = $body | split row "?" -n 2 let segs = ($path_query | get 0) | split row "/" let owner = $segs | get 0 let repo = $segs | get 1 let ref = if (($segs | length) > 2) { $segs | skip 2 | str join "/" } else { null } return { git_url: $"https://github.com/($owner)/($repo).git", ref: $ref } } if ($url | str starts-with "git+") { let stripped = $url | str substring 4.. let parts = $stripped | split row "?" -n 2 let base = $parts | get 0 let ref = if (($parts | length) > 1) { ($parts | get 1) | split row "&" | each { |kv| let kvp = $kv | split row "=" -n 2 { k: ($kvp | get 0), v: ($kvp | get 1?) } } | where k == "ref" | get -o 0 | get -o v } else { null } return { git_url: $base, ref: $ref } } error make { msg: $"unsupported url scheme for ls-remote: ($url)" } } # Cheap "is upstream ahead?" check via git ls-remote. Returns the rev string. def ls-remote-head [url: string] { let tgt = parse-git-target $url let target_ref = if ($tgt.ref? | is-not-empty) { $"refs/heads/($tgt.ref)" } else { "HEAD" } let r = ^git ls-remote $tgt.git_url $target_ref | complete if $r.exit_code != 0 { error make { msg: $"ls-remote failed: ($r.stderr)" } } let first = $r.stdout | str trim | lines | get -o 0 if ($first | is-empty) { error make { msg: "no refs returned" } } $first | split row "\t" | get 0 } # Real prefetch — caches in /nix/store and returns the full locked attrset. # Output JSON: { hash: SRI, locked: {...}, original: {...}, storePath: PATH } # --refresh bypasses the ref→rev cache (otherwise stale within tarball-ttl). def prefetch-pin [url: string] { let r = ^nix flake prefetch --refresh --json $url | complete if $r.exit_code != 0 { error make { msg: $"prefetch failed for ($url): ($r.stderr)" } } let j = $r.stdout | from json $j.locked | insert narHash $j.hash } def cmd-fresh [...names: string] { with-frame { meat-print "HUNTING FRESH MEATS.." let pins = load-pins let shorturls = $pins.shorturls? | default {} let inputs = $pins.inputs mut lock = load-lock let targets = if ($names | is-empty) { $inputs | columns } else { $names } for name in $targets { if not ($name in ($inputs | columns)) { meat-print $"NO MEAT CALLED ($name | str upcase).." continue } meat-print $"PROCESSING ($name | str upcase).." try { let pin = $inputs | get $name let expanded = expand-shorturl $pin.url $shorturls let entry = prefetch-pin $expanded let old_rev = ($lock | get -o $name) | default {} | get -o rev let new_rev = $entry.rev if $old_rev == $new_rev { meat-print $" ($name | str upcase) STILL FRESH" continue } $lock = $lock | upsert $name $entry write-lock $lock let from = if ($old_rev | is-empty) { "NEW" } else { $old_rev | str substring 0..8 } meat-print $" ($name | str upcase): ($from) -> ($new_rev | str substring 0..8)" } catch { |e| meat-print $" NO FIND ($name | str upcase): ($e.msg)" } } } print "" } def cmd-look [] { with-frame { meat-print "LOOK FOR NEW MEATS.." let pins = load-pins let shorturls = $pins.shorturls? | default {} let inputs = $pins.inputs let lock = load-lock let stale = $inputs | transpose name pin | each { |row| try { let expanded = expand-shorturl $row.pin.url $shorturls let new_rev = ls-remote-head $expanded let old_rev = ($lock | get -o $row.name) | default {} | get -o rev if $old_rev != $new_rev { let from = if ($old_rev | is-empty) { "NEW" } else { $old_rev | str substring 0..8 } meat-print $" ($row.name | str upcase): ($from) -> ($new_rev | str substring 0..8)" $row.name } else { null } } catch { meat-print $" NO FIND ($row.name | str upcase).." null } } | compact if ($stale | is-empty) { meat-print "NO MEAT FRESHER" } } } def truncate-desc [s: string, n: int]: nothing -> string { if (($s | str length) > $n) { ($s | str substring 0..$n) + ".." } else { $s } } def cmd-hunt [...query: string] { with-frame { if ($query | is-empty) { meat-print "WHAT MEAT YOU SEEK?" return } let q = $query | str join " " meat-print $"HUNTING FOR ($q | str upcase).." let body = { from: 0, size: 20, sort: [{ _score: "desc", package_attr_name: "desc", package_pversion: "desc" }], collapse: { field: "package_attr_name" }, query: { bool: { must: [ { term: { type: "package" } }, { multi_match: { type: "cross_fields", query: $q, fields: ["package_attr_name^9", "package_pname^6", "package_description^1.3", "package_longDescription^1"] } } ] } } } try { let resp = ( http post --user "aWVSALXpZv" --password "X8gPHnzL52wFEekuxsfQ9cSh" --content-type "application/json" "https://nixos-search-7-1733963800.us-east-1.bonsaisearch.net/nixos-*-unstable-*/_search" $body ) let hits = $resp.hits?.hits? | default [] if ($hits | is-empty) { meat-print "NO MEATS FOUND!" } else { for hit in $hits { let src = $hit._source let name = $src.package_attr_name? | default "?" let ver = $src.package_pversion? | default "" let desc = $src.package_description? | default "" let ver_str = if ($ver | is-empty) { "" } else { $" \(($ver)\)" } print $" \tnixpkgs#($name)($ver_str)" if not ($desc | is-empty) { print $" \t (truncate-desc $desc 60)" } } } } catch { |e| meat-print $"HUNT FAILED: ($e.msg)" } } } def cmd-ritual [] { with-frame { meat-print "PREPARING RITUAL GROUND.." let tmpdir = ^mktemp -d -t "meat-chew.XXXXXX" | str trim let meats = $env.MEATS let nix_conf = $"($tmpdir)/nix.conf" let build = $"($tmpdir)/build" try { nix-build-nom $nix_conf $"($meats)/entry.nix" (nix-conf-attr) meat-print "CONSUMING MEATS.." with-env { NIX_USER_CONF_FILES: $nix_conf } { nix-build-nom $build $"($meats)/entry.nix" (system-attr) } ^$env.DIFFER /nix/var/nix/profiles/system $build activate $build try { rm $nix_conf } try { rm $build } } catch { print "FAILED TO CONSUME MEATS." } } } def cmd-shelter [new_closure: string] { activate $new_closure } def main [...args: string] { if ($env.MEATS? | is-empty) { meat-print "NO PATH TO RUNESTONE FOUND!" cmd-help return } let sub = $args | get 0? | default "" | str downcase let rest = $args | skip 1 match $sub { "yum" => { cmd-yum ...$rest } "cook" => { cmd-cook ...$rest } "poke" => { cmd-poke ...$rest } "gut" => { cmd-gut ...$rest } "trade" => { cmd-trade } "look" => { cmd-look } "fresh" => { cmd-fresh ...$rest } "hunt" => { cmd-hunt ...$rest } "ritual" => { cmd-ritual } "shelter" => { cmd-shelter $rest } _ => { cmd-help } } }