Skip to content

Instantly share code, notes, and snippets.

View cablehead's full-sized avatar

Andy Gayton cablehead

View GitHub Profile
@cablehead
cablehead / canvas-nu-mcp-history.md
Created May 23, 2026 15:11
nu-mcp: value reuse between agent turns via $history ring buffer

nu-mcp: value reuse between agent turns

The usual MCP failure mode is a tool that returns a large value: the agent either floods its context window or has to re-run the command with | head to claw it back. Both options cost turns and lose information.

nu-mcp sidesteps this by storing every evaluation's full result in a ring buffer the agent can refer to by index on subsequent calls.

@cablehead
cablehead / canvas-nu-vs-python.md
Created May 23, 2026 15:10
python -c json piping vs nushell

python -c vs. nushell for JSON

Two ways to pull stargazers_count off the nushell repo's GitHub API response. Both verified against https://api.github.com/repos/nushell/nushell.

One field

bash + python -c:

(
interleave
{ generate {|_ = 0|
let ev = (try { input listen --types [key] --timeout 200ms } catch { null })
{out: (if $ev != null { {kind: key, event: $ev} }), next: 0}
}
}
{ generate {|_ = 0| sleep 1sec; {out: {kind: tick, event: (date now)}, next: 0} } }
)
| where $it != null
with-dotenv {
echo '{"role":"user","content":"Run this in the nu tool exactly: use greeter; greeter hi"}'
| yoke --provider openrouter --model openai/gpt-oss-20b:free --tools nu -I ~/yoke-test/modules
}
{"type":"tools","tools":[{"name":"nu","description":"Execute a Nushell script. Output is auto-converted to nuon -- do NOT add '| to nuon' or '| to json'. Pass structured data via 'input' (JSON) to avoid quoting issues -- it becomes $in in the pipeline.\n\nUse help liberally to learn how nushell works. Do NOT guess command names or flags -- discover them with help. If you encounter an error, you MUST use help related to your task before trying again.\n\nExamples:\n {command: \"help where\"}\n {command: \"help --find convert\"}\n {command: \"$in | where price > 20\", input: [{name: \"Widget A\", price: 25.50}, {name: \"Gadget\", price: 15}]}\n {command: \"seq 1 10 | each { |n| $n * $n }\"}"}]}
{"type":"agent_start"}
{"type":"turn_start"}
{"role":"user","content":[{"type":"text","text":"Run this in

In the for_collect case, my understanding is the main goal of FilePath is to prevent clobbering files with save. If we've collected, it's now safe to overwrite our original file, so FilePath is cleared. The code has always been doing this, but the previous version tied that decision to content_type — it only cleared FilePath when content_type was also None. That coupling didn't make sense, so when I added the custom metadata field, I moved the collect-time cleanup into its own for_collect method on PipelineMetadata and made it always clear FilePath regardless of other fields.

Connecting your app to Stellar for live CSS updates

Stellar is a local-first CSS generator with an interactive web editor. stellar serve serves your generated CSS and exposes a /live-refresh SSE endpoint that pushes stylesheet updates via Datastar.

stellar serve  # http://localhost:7331
@cablehead
cablehead / xs-naming-rfc.md
Last active January 19, 2026 17:16
xs stream naming conventions RFC

xs Naming Conventions RFC

Proposing consistent naming for stream position concepts in xs.

The Problem

Current naming mixes metaphors:

  • .head returns the newest frame (like Git HEAD)
  • --tail skips to the end (like Unix tail)
  • But resume_from: "head" means start from the beginning

Exploring schema validation for nushell/http-nu

We want to validate records against a schema (like JSON Schema). Nushell already has type syntax for command signatures:

def foo []: record<
  name: string,
  age: int,
  email?: string,
  tags: list<string>,
use xs.nu *
use http-nu/router *
use http-nu/datastar *
use http-nu/html *
def escape []: string -> string {
$in | str replace -a '&' '&amp;' | str replace -a '<' '&lt;' | str replace -a '>' '&gt;'
}
use /root/.config/nushell/scripts/xs.nu *
def decode-basic-auth [header?: string] {
if ($header | is-empty) or (not ($header | str starts-with 'Basic ')) {
return null
}
let encoded_part = $header | str replace 'Basic ' ''
# handle base64 decode errors