Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/commands.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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)"),
Expand Down
26 changes: 26 additions & 0 deletions src/tools.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions test/tools.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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.
Expand Down
Loading