From f17e48d9bcfe9125c72a8e6e122a0318843f51f2 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 7 Sep 2026 15:09:33 +0000 Subject: [PATCH] Add noodle to the tool registry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Noodle is a terminal REST client: every request is a readable YAML file that lives beside the code it exercises, driven from the TUI, the CLI or a script. It sits next to mcpjam in the registry for the obvious reason — mcpjam tells you whether an MCP server answers, noodle is how you ask an HTTP one anything at all. It ships a per-platform static binary on its own GitHub releases rather than npm, so the install is the vendor script. Through `bash`, not `sh`: the script declares a bash shebang and uses `local`, unlike the c0upons installer beside it, which avoids bashisms on purpose. No `upgrade` key. Noodle ships no updater of its own, and the installer always fetches the latest release, checks it against the published SHA256SUMS and renames it over the existing binary atomically, so re-running the install IS the upgrade and toolUpgradeSpec falls back to it. The binary lands in ~/.local/bin and nothing appends to PATH, hence binDirs — the same gap turso, gradient, kimi and yt-dlp have. Verified against the live installer rather than the spec alone: install, `moshcode tools` status, top-level passthrough and the upgrade re-run all exercised end to end on noodle 0.8.5. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01PqnstZRhi764pihZounU3n --- README.md | 24 ++++++++++++++++++++++++ src/commands.mjs | 1 + src/tools.mjs | 26 ++++++++++++++++++++++++++ test/tools.test.mjs | 20 ++++++++++++++++++++ 4 files changed, 71 insertions(+) diff --git a/README.md b/README.md index a128e55b..5a68106b 100644 --- a/README.md +++ b/README.md @@ -869,6 +869,30 @@ engines, `mcpjam` tells you whether that server is healthy first — health checks, OAuth conformance, tool-surface diffing, and structured triage from the terminal or CI. Re-running `moshcode install mcpjam` is also its upgrade path. +### Noodle — a REST client that lives in the repo + +```sh +moshcode install noodle # static binary → ~/.local/bin + +moshcode noodle collection create my-api +moshcode noodle request create users/get --url https://api.example.com/users/42 +moshcode noodle request run users/get --collection ./my-api +moshcode noodle --collection ./my-api # the TUI +``` + +[Noodle](https://noodlerest.dev/) is MCPJam one protocol over: MCPJam tells you +whether an MCP server answers, Noodle is how you ask an HTTP one anything at +all. Every request is a readable YAML file — method, url, path params, headers +— so a collection is reviewed in a diff and kept beside the code it exercises, +rather than living in a desktop app's private workspace. The same files drive +the TUI, the CLI and a script, which is what makes it a roster tool: `request +run` exits with a status and prints the exchange, so it pipes. + +It ships a per-platform static binary on its own releases and the install script +picks the right one, checks it against the published `SHA256SUMS`, and renames +it over any existing copy. So there is no updater to reach for: re-running +`moshcode install noodle` is the upgrade path. + ### ElevenLabs — Eleven Agents, voices, and speech ```sh diff --git a/src/commands.mjs b/src/commands.mjs index 026762f6..e495145a 100644 --- a/src/commands.mjs +++ b/src/commands.mjs @@ -853,6 +853,7 @@ const COMMANDS = [ cliVerb("coral", "drive the Coral CLI (SQL over APIs, databases, and internal systems)"), cliVerb("alpaca", "drive the native Alpaca trading CLI"), cliVerb("mcpjam", "drive the MCPJam CLI (test, debug, and validate MCP servers)"), + cliVerb("noodle", "drive the Noodle CLI (terminal REST client: collections, requests, runs)"), cliVerb("spinifex", "drive the Spinifex CLI (spx — AWS-compatible cloud on your own hardware)"), cliVerb("alchemy", "drive the Alchemy CLI (onchain data, apps, wallets, x402)"), cliVerb("elevenlabs", "drive the ElevenLabs CLI (Eleven Agents, voices, TTS, dubbing)"), diff --git a/src/tools.mjs b/src/tools.mjs index 28ed70fe..fec1e212 100644 --- a/src/tools.mjs +++ b/src/tools.mjs @@ -304,6 +304,32 @@ export const TOOLS = { // idempotent, so it doubles as the upgrade path — no `upgrade` key needed. install: { cmd: "npm", args: ["install", "-g", "@mcpjam/cli"] }, }, + noodle: { + desc: "Noodle — terminal REST client: readable request files, run them from the TUI, CLI or a script", + bin: "noodle", + // The sibling of mcpjam one protocol over: mcpjam tells you whether an MCP + // server answers, noodle is how you ask an HTTP one anything at all. Every + // request is a YAML file that lives beside the code it exercises, so a + // collection is reviewable in a diff and runnable from a pipeline rather + // than trapped in a desktop app's workspace. + // + // Its own install script, not npm: noodle ships a per-platform static + // binary on its GitHub releases and the script picks the right one. The + // shebang is bash and the script uses `local`, so it goes through bash + // rather than `sh` — the opposite of the c0upons entry above, which avoids + // bashisms on purpose. + install: { cmd: "bash", args: ["-c", "curl -fsSL https://noodlerest.dev/install.sh | bash"] }, + // No `upgrade` key on purpose: noodle ships no updater of its own, and the + // installer always fetches the latest release, checks it against the + // published SHA256SUMS, and renames it over the existing binary atomically. + // Re-running the install IS the upgrade, the same as mcpjam and railway, + // and toolUpgradeSpec falls back to install on its own. + // + // It lands in ~/.local/bin and appends nothing to PATH, so the shell that + // ran the install cannot see it — the same gap turso, gradient, kimi and + // yt-dlp have. + binDirs: [path.join(homedir(), ".local", "bin")], + }, spinifex: { desc: "Spinifex — AWS-compatible cloud on your own hardware (EC2, EBS, S3, VPC, IAM)", // The product is Spinifex; the binary it installs is `spx`. Same split as diff --git a/test/tools.test.mjs b/test/tools.test.mjs index 8f70728a..2332d4ae 100644 --- a/test/tools.test.mjs +++ b/test/tools.test.mjs @@ -523,6 +523,7 @@ for (const [name, shell, script] of [ ["c0mpute", "sh", "curl -fsSL https://c0mpute.com/install.sh | sh"], ["c0upons", "sh", "curl -fsSL https://c0upons.com/install.sh | sh"], ["coral", "bash", "curl -fsSL https://withcoral.com/install.sh | bash"], + ["noodle", "bash", "curl -fsSL https://noodlerest.dev/install.sh | bash"], ["spinifex", "bash", "curl -fsSL https://install.mulgadc.com | INSTALL_SPINIFEX_SKIP_NEWGRP=1 bash"], [ "cli-tools", @@ -606,6 +607,25 @@ test("the media toolchain installs through the right mechanism for each tool", ( } }); +test("noodle installs through its own script and re-runs it to upgrade", () => { + assert.deepEqual(resolveTool("noodle"), ["noodle", TOOLS.noodle]); + assert.equal(TOOLS.noodle.bin, "noodle"); + assert.match(toolList(), /noodle/); + // bash, not sh: noodle's installer declares a bash shebang and uses `local`, + // unlike the c0upons script next to it in the registry. + assert.deepEqual(TOOLS.noodle.install, { + cmd: "bash", + args: ["-c", "curl -fsSL https://noodlerest.dev/install.sh | bash"], + }); + // No updater of its own, and the installer replaces the binary in place from + // the latest release, so the upgrade is the install. + assert.equal(toolUpgradeSpec(TOOLS.noodle), TOOLS.noodle.install); + // A user-local binary in ~/.local/bin, so it never escalates, and the shell + // that ran the install needs the directory named to find it afterwards. + assert.equal(needsRootHere(TOOLS.noodle, "linux"), false); + assert.deepEqual(TOOLS.noodle.binDirs, [path.join(homedir(), ".local", "bin")]); +}); + test("the package-manager tools ask for root everywhere but macOS", () => { // Homebrew refuses to run as root, so a mac must not be prompted for a // password by a step that never escalates.