Skip to content

Releases: moshcoder/moshcode

v0.24.0 — names under a name

Choose a tag to compare

@ralyodio ralyodio released this 05 Aug 09:13
a63f7c1

Third-level Moshpit names

  • www.chovy.hacker now resolves. It is asked of the registry as written, and a name the registry does not hold falls back to the owner's published *.chovy.hacker wildcard.
  • A sub-name that misses both is NXDOMAIN rather than parked. Parking exists to say a name is for sale, and a name under someone else's name is not — parking it would advertise their subdomains to anyone who guessed.
  • * is a label only whole and only leftmost: f*.chovy.hacker and foo.*.hacker are not names the registry can be asked about.
  • Four labels remain a shape the registry cannot hold, and forwarding is unchanged — the bridge still gates on the ending, so www.google.com is still your normal nameserver's to answer.
  • moshcode dns resolve now has something to print for a sub-name that exists nowhere, instead of failing with a type error over the top of the answer.

This syncs the vendored DNS bridge with @moshcoder/moshpit-dns 0.5.0. The drift test that guards the copy had been pinned two releases behind, so it was green while the two had diverged; it now compares against the current package.

Also

  • --proxy refuses a host name instead of quietly answering NODATA for every live name.
  • Agent status can be printed as JSON.

Everything outside those paths is unchanged.

v0.23.0 — every live name behind the proxy

Choose a tag to compare

@ralyodio ralyodio released this 05 Aug 02:45
650adc8

A certificate a stock client will accept

  • moshcode dns start --proxy answers every live Moshpit name with the local pinned-TLS proxy instead of the name's origin.
  • The proxy reads the SNI, checks the origin's key against the registry pin, and re-signs with a root this machine generated — the only way a stock browser can be told the result, since no CA will ever sign for a Moshpit name.
  • Previously the proxy could sit on loopback with nothing routed to it, so every name still arrived as a self-signed certificate no matter what was installed.
  • --proxy probes 127.0.0.1 and ::1 by default; --proxy <host> probes only the host you name.
  • It refuses to start when nothing is listening on 443, rather than pointing every name on the machine at a closed port and turning a certificate problem into a total outage.
  • Reachability is rechecked rather than remembered: a proxy that dies after the resolver started is the same outage as one that was never running.
  • A proxy that speaks only one address family answers NODATA for the other, not a fabricated address that would refuse the connection.
  • Parked names still reach the parking page. They have no origin and no published pin, so handing one to the proxy would turn "this name is for sale" into a TLS error.

Also

  • New caddy-proxy example template: Caddyfile, README, and a systemd unit for putting a Moshpit name in front of a local service.

Requires moshpit-proxy listening on 443. Everything outside --proxy is unchanged.

v0.22.1 — parallel pits

Choose a tag to compare

@ralyodio ralyodio released this 04 Aug 20:06
029a64f

Parallel moshcode tabs

  • Type /new at the mosh prompt to open an independent moshcode tab and switch to it.
  • Run /agents kimi (or any provider) in multiple tabs and switch between sessions without exiting either CLI.
  • Outside tmux, moshcode creates an isolated two-tab workspace with a bottom tab bar and predictable Ctrl-b n, Ctrl-b p, and Ctrl-b 1/2 controls.
  • Inside tmux, /new adds a window to the current session and respects its configured window keys.
  • Each tab remains a separate process on a real PTY, preserving native provider input, output, full-screen UI, signals, and shortcuts.
  • Failed private-workspace attachments now clean up their tmux server instead of leaving background pits behind.

Requires tmux for /new; all existing non-tab CLI behavior is unchanged.

v0.22.0 — Alpaca in the pit

Choose a tag to compare

@ralyodio ralyodio released this 04 Aug 18:32
e6217bf

Alpaca trading workflows

  • Install the official Alpaca CLI with moshcode install alpaca; upgrade and uninstall use the same workflow-tool lifecycle.
  • Use moshcode trade, TUI /trade, or moshscript trade() for ticker lookup, quotes, snapshot analysis, positions, orders, account details, and watchlists.
  • Preview buy and sell orders safely by default; submission requires an explicit --submit, with quantity and notional order support.
  • Drop to the native client with moshcode alpaca or TUI /alpaca whenever the facade is not enough.
  • Shell completion, generated help, README documentation, missing-Go diagnostics, and real Alpaca CLI compatibility coverage are included.

Authenticate with moshcode trade login before using account endpoints. Alpaca profile selection still controls paper versus live trading.

v0.21.0 — three more engines

Choose a tag to compare

@ralyodio ralyodio released this 04 Aug 13:37
dffd166

Three more coding engines you can install and drive: Kimi Code, Qwen Code, and DeepSeek Code.

moshcode install kimi       # curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash
moshcode install qwen       # npm i -g @qwen-code/qwen-code
moshcode install deepseek   # npm i -g @serjm/deepseek-code

Each one resolves by alias the way every other engine does (qwencode, alibaba, ds, dsc, kimi-code, …), gets its reviewed autonomous-mode flags behind agents, and gets a headless mode behind moshscript's ai():

moshcode agents kimi        # kimi --yolo
moshcode agents qwen        # qwen --approval-mode=yolo
moshcode agents deepseek    # deepseek-code --turbo

Two of those entries route around a bad name:

  • kimi installs the kimi-code script directly. The older code.kimi.com/install.sh wrapper now installs the deprecated Python kimi-cli, and it prompts — Enter, or a 30-second timeout, silently redirects to that same script. A vendor installer that blocks on a human for half a minute is not something moshcode install can drive.
  • deepseek launches deepseek-code, not the dsc alias its package also installs, because dsc is Microsoft's Desired State Configuration binary. On a machine with both, the short name would silently start the wrong program. DeepSeek publishes no first-party agentic CLI; @serjm/deepseek-code is the live community one, while deepseek-tui is now a stub renamed to codewhale and deepseek-cli has not shipped since January 2025.

template list grew a --json form for automation (#284).

Also fixes a DoH config that dropped its default guards when only one of them was overridden (#285).

Minor, not patch: three new engines and a new output format.

MCP and skills fan-out stay opt-in per engine, so the new engines report "no support" there for now.

Full changelog: #284, #285, #286, #287, #288v0.20.0...v0.21.0

v0.20.0 — a name that survives the handshake

Choose a tag to compare

@ralyodio ralyodio released this 04 Aug 04:30
5fde89b

A Moshpit name that resolves now survives the TLS handshake.

The certificate error that outlived the local root

v0.19.0 installed a local CA, and it changed nothing at the browser: the root signs certificates issued by moshpit-proxy, and nothing routes to the proxy. The resolver answers the origin directly, the proxy sits on loopback, and the two were never connected. So https://seo.rank still met the origin's self-signed leaf.

dns trust <name> closes that without the proxy in the path at all. A Moshpit name serves a self-signed leaf whose SAN names only itself, so trusting it vouches for exactly one name — a far smaller grant than a root, and why this needs no name-constraints argument to be defensible.

The pin is what separates it from recklessness. Installing whatever answered the socket trusts whoever reached the port first. Installing it only when the registry already publishes that exact key is registry-backed trust — a stronger claim than domain validation ever made. Every published pin is accepted, not just the newest, because the registry lists the old key beside the new during a rotation precisely so a key can change without a flag day. A registry outage is reported as an outage and never as a rejected certificate: the answer to "the registry is down" is to wait, not to distrust a name.

dns start --trust-all

One command per name does not scale — someone browsing meets a certificate error on every site they have not personally thought about, which is indistinguishable from the namespace being broken. --trust-all trusts a name as it resolves, on the same pin check. A name with no published pin gets nothing, silently and forever, so this is registry-backed trust rather than trust-on-first-use. Only names that actually resolved to one of ours are considered: a forwarded clearnet name is not ours to trust, and NXDOMAIN has no origin to fetch from.

Certificate work never blocks a DNS answer, a name is considered once rather than once per query, and a refusal is final for that name until restart — otherwise every lookup writes a log line and fails.

Two commands that reported success they had not earned

update --timer --install wrote the systemd units, ran systemctl enable --now, then printed "checking on a schedule now" and exited 0 whether or not the enable succeeded. On a host without systemd or without the privileges to enable a unit, the timer never started and the tool still promised auto-updates. It now says the timer is not scheduled and exits 1.

site <name> --proxy <port> validated the port with ^\d+$. --proxy 0 is falsy downstream, so the site silently fell back to a static root; --proxy 99999 wrote a proxy_pass to a port that cannot exist. Both exited 0 and called the site live. The port is now checked against 1-65535, matching every other port the tool takes.

Upgrading

moshcode update

Then, to pick up the trust path on a machine already routing Moshpit:

moshcode dns enable

Run that with the privileges the bridge runs under.

Included: #279, #280, #281, #282

v0.19.1 — a refusal that says what to do about it

Choose a tag to compare

@ralyodio ralyodio released this 03 Aug 17:50
abd5621

v0.19.0 refused an unsafe CA root correctly, and then left you standing there.

Both refusals printed the same line — "moshcode will not put an unconstrained root in your trust store" — with no next step. For one of them that sentence was not even true.

The two cases are nothing alike:

  • stale — the root permits endings other than the ones claimed today, because it was generated when a different set was claimed. Nothing dangerous. Regenerating costs one command.
  • dangerous — the root has no permitted DNS subtree, so it can vouch for any name on the internet.

Collapsing them into one dead end is how a safety gate gets worked around with --no-trust instead of satisfied: with no way to tell a real hole from an out-of-date file, the opt-out becomes the obvious move.

Each refusal now carries its own remedy, and only one of them has a way forward:

  STOP the root does not permit rank
       the root is older than the endings claimed now — regenerating it is enough:
         rm -rf ~/.moshpit/ca && moshpit-proxy   # writes a fresh root
       then re-run `moshcode dns enable`.
  STOP the root permits no DNS subtree, so every name it does not exclude is allowed
       moshcode will not put a root that can vouch for names outside Moshpit
       into your trust store. Names will resolve but not pass TLS until the
       root is regenerated with name constraints. This is not overridable.

The dangerous case is deliberately offered nothing, and a test asserts it never advertises --no-trust as the fix.

The rule itself is unchanged, and stays: one DNS:.com in a permitted subtree and the root reaches the clearnet. What was wrong was not the strictness — it was that being strict was a dead end.

Upgrading

moshcode update

1351 tests, 1348 pass, 0 fail, 3 skipped.

v0.19.0 — a name that resolves, and then works

Choose a tag to compare

@ralyodio ralyodio released this 03 Aug 17:35
6d36fc2

curl https://seo.rank used to return this on a machine where DNS was working perfectly:

curl: (60) SSL certificate OpenSSL verify result: self-signed certificate (18)

dns enable pointed names at the resolver and stopped. A name that resolves and then fails its TLS handshake is not "DNS works" to the person who typed the URL — it is still broken.

No CA is ever going to close this

The CA/Browser Forum Baseline Requirements banned certificates for non-IANA names. Issuance stopped in November 2015, the survivors were revoked by October 2016, and a CA in a browser root store that issued for .rank would be distrusted for doing it. The rule is the penalty.

What changed

moshpit-proxy already checks an origin's key against the pin the registry published for that name and re-signs with a root it generated on this machine. Nothing wired it up. dns enable now finds that root, checks it, installs it into the trust stores, and reports each one separately — NSS (Chrome, Firefox) and the system store (curl, wget, anything on OpenSSL) succeed and fail independently, and saying "installed" while curl still refuses is exactly how someone concludes the whole thing is broken.

It never fails enable. DNS is switched and verified by the time this runs, and failing the command over a trust store would roll back working resolution to fix a certificate. --no-trust opts out.

The refusal is the feature

Installing a local root is only acceptable because of one extension: nameConstraints, marked critical, permitting Moshpit endings and nothing else. With it, the worst a stolen key reaches is a name its holder already controls. Without it, it can vouch for your bank.

So moshcode refuses a root that lacks it rather than warning, and the check parses the extension rather than searching it. That distinction is not academic: Excluded: and Permitted: read identically to a substring search, and RFC 5280 §4.2.1.10 binds constraints only to the name types they mention — so a root carrying excluded;DNS:.hacker permits every other name on the internet. It is refused, along with a root permitting a namespace we do not control, and one permitting a Moshpit ending and .com.

Trust lands in your stores, not root's

dns enable escalates itself, so $HOME is /root while it runs. The CA and the NSS database both live in the operator's home, so both are resolved from SUDO_USER through passwd, and the NSS store is handed back afterwards — left root-owned, the browser silently loses the ability to update it.

Scope, stated plainly

This fixes machines you run it on. Nothing shippable can make a stranger's curl trust a Moshpit name — that is the Baseline Requirements, not a gap in our tooling. TronBrowser remains the answer for someone who has run nothing: it verifies registry pins natively and needs no DNS config, no proxy, and no root.

Upgrading

moshcode update

Not sudo moshcode update — that is refused, as of v0.18.2.

1348 tests, 1345 pass, 0 fail, 3 skipped.

v0.18.2 — refuse the install that succeeds into the wrong home

Choose a tag to compare

@ralyodio ralyodio released this 03 Aug 17:16
0ab56aa

sudo curl … | sh installed moshcode successfully into a home the person who typed it cannot read.

Every path in install.sh derives from $HOME. Under sudo that is /root, mode 0700 — so the payload went to /root/.moshcode and both wrappers to /root/.local/bin. Nothing failed. It printed success. The bill arrived much later, with no hint of the cause:

$ moshcode install secrets
zsh: permission denied: moshcode

This release closes that trap from both ends.

The installer refuses up front

install.sh now refuses the escalated case before the download, and says how to proceed: re-run as yourself, or name the paths explicitly if a system-wide install really is what was wanted.

A bare root shell — containers, CI images — has no SUDO_USER and remains a legitimate way to install, so only the escalated-from-a-real-user case is refused. MOSHCODE_ALLOW_ROOT overrides even that.

The tests drive the real install.sh with id shadowed on PATH, so the root branch is exercised without root. curl and tar are shadowed too, and reaching either fails the test — which is what pins refuses before anything is downloaded rather than merely refuses eventually.

dns escalates itself, so update never has to

The habit was the other half of the trap. dns enable genuinely needs root — it writes /etc/resolver/<tld>, a systemd-resolved drop-in or /etc/dnsmasq.d, and binds :53 — and it advised re-running the whole CLI as sudo moshcode dns enable.

That advice is right for dns and dangerous everywhere else, because moshcode update self-updates by re-running the installer. Escalated, that reinstalls moshcode into root's home and publishes /usr/local/bin/moshcode → /root/.local/bin/moshcode.

dns now re-runs this CLI's own argv under sudo (or doas) with stdio inherited, so the password prompt works and only the privileged command is raised. The DNS state it writes lives in /etc and /var/lib/moshcode, never the operator's home, so raising just that command loses nothing.

When escalation isn't possible — no tty, no sudo on the box, or already escalated and still unprivileged — it falls back to exactly the advice printed before. Nothing that worked stops working.

Also

install.sh help no longer prints through set -eu; its sed range ended one line past the header.

The /prd healthy-cwd test no longer launches a real coding engine and blocks until it finishes authoring a PRD. The suite went from 479s to ~70s.

Upgrading

moshcode update

Not sudo moshcode update — that is now refused, which is the point.

1319 tests, 1316 pass, 0 fail, 3 skipped.

v0.18.1 — a pointed name answers

Choose a tag to compare

@ralyodio ralyodio released this 03 Aug 15:26
da55fd1

seo.rank, chovy.hacker, alt.2600 — every name in the registry pointed at a host — answered an authoritative NOERROR with no records. A client is entitled to treat that as final, and curl does: could not resolve host, no retry.

google.com resolved the whole time. dig said the name existed. Nothing logged an error. Most of the namespace was unreachable and the machine looked healthy.

The address path

RECORD_TYPES covered CNAME, MX and TXT, so a published A or AAAA was never consulted for an address question — alt.2600 publishes an AAAA and still answered nothing. And targetAddress() returns null for a hostname, with nothing picking up after it.

addressAnswer() replaces that with one plan: target address, published A/AAAA, published CNAME, then a CNAME synthesised from a hostname target. The cheap question is still asked first, so a name pointed at a bare IP costs one registry call and no record fetch — the fast path every page load takes.

The ending list

isOurs() gates every answer on the set of claimed endings, and the fetch was wrapped in .catch(() => []). An empty set makes the bridge say "not mine" to every name, and with upstreams configured that means forwarding all of Moshpit to the clearnet — which denies it by definition. dig prompt, resolvectl right, systemctl green, every Moshpit name NXDOMAIN, and no log mentioning the list. It now says when the list did not load.

dns enable no longer starts a second bridge

Preflight would find a bridge already on the port, probe it, report that it "is being used as-is" — and then start another one anyway. Both bound, and the kernel handed the queries to whichever took the more specific address, which was not necessarily the one the note named.

Upgrading

moshcode update && moshcode dns enable

Run it with the privileges the bridge runs under, wherever one is running — including as your own user if you installed one there.