Skip to content

Releases: moshcoder/moshcode

v0.66.0 — Alchemy joins the roster, and a tool can name its own pit words

Choose a tag to compare

@ralyodio ralyodio released this 19 Aug 21:03

Three changes, and two of them are the same idea arriving in stages: a tool that ships a set of commands can now tell the pit what to call them, and installing it is when that happens.

Alchemy joins the roster

moshcode install alchemy
moshcode alchemy auth

One binary covering four things: onchain data across EVM and Solana — balances, NFTs, transfers, prices, blocks, logs, traces, simulations, raw RPC — Alchemy apps, networks, allowlists and webhooks, an agent-ready wallet that sends, swaps, calls contracts, approves and bridges cross-chain, and x402 payments in USDC under a spend cap.

The package's own bin is named alchemy, so the pit word and the executable are the same thing. No product/binary split to explain, the way /secrets fronts logicsrc and /spinifex fronts spx.

Two fields are deliberately absent. There is no upgrade, because npm install -g is idempotent and re-running the install is the upgrade — the same shape mcpjam has. And there is no installHelp, because that line exists to name a missing installer the way alpaca names Go and gradient names python3; npm is not missing, moshcode runs on it. The package does declare node >=22, which npm warns about rather than refuses. Hearing that from the CLI on first run is better than hearing it from an install that already succeeded.

alchemy auth opens a browser, links your account and saves the selected app's API key. Nothing works before that. Scripts and agents want --json --no-interactive, which is what makes it read like the rest of the roster in a pipeline.

Where CoinPay is the payments product moshcode ships alongside, this is the read side of the same world — the chain itself rather than one wallet's ledger.

A tool proposes its pit aliases; the pit disposes

Several tools in the roster are a set of commands rather than one binary, and have short words in mind for them at the prompt. Until now the only way to get those words was to let the tool write ~/.moshcode/aliases.json itself — a file it does not own, and the same objection that keeps railway setup agent out of /install.

So a tool declares an aliases spec, moshcode runs it, reads the JSON and merges it. Declared rather than probed: an aliases --json guessed at every installed CLI would eventually hit one where those words mean something else entirely.

A name you bound yourself always wins, and is named in the report rather than silently kept. Both are plausible readings of the word, and the pit's own aliases carry flags a generic suggestion knows nothing about. Names that collide with a pit command, an engine or a tool are refused for the same reason /alias set refuses them: built-ins resolve first, so such an alias would be dead on arrival.

…and adopting them is part of installing it

The first cut of the above made it its own verb, /alias install <tool>. That was the wrong call, and this release corrects it in the same breath as shipping it.

Seven commands behind one dispatcher are not reachable from the pit until the words that reach them exist. Wiring them up is part of installing the tool, not a second thing to remember afterwards. So /install <tool>, /tools install <tool> and moshcode install <tool> now adopt whatever the tool proposes — and /upgrade does the same, because an upgrade is exactly where a tool gains commands, and a roster adopted once at install time goes stale the first time it ships something new. That is precisely how cli-tools' own generate-names never reached anybody.

It is quiet by construction, which is what makes it safe to hang off an install. A tool that offers nothing — every engine, most tools, alchemy included — prints nothing. One that cannot be asked stays silent rather than following a successful install with an error about a nicety.

/alias install <tool> remains, for anything installed before this existed.

/tools install <name> now routes to the installer rather than answering unknown tool "install". It is the obvious thing to type after /tools has just printed the roster.

Two test fixes this surfaced

Both real. test/tools.test.mjs used a fake shell as a spy that a tool's own shebang could overwrite, and its runner never isolated $HOME — so a successful install inside the suite could rewrite the aliases of whoever ran it.

v0.65.0 — cli-tools joins the roster

Choose a tag to compare

@ralyodio ralyodio released this 19 Aug 13:34
649b1a8

profullstack/cli-tools went public today, so moshcode can install it like anything else:

moshcode install cli-tools

That is one step for seven commands — blog-post, domainfree, domainjson, gh-prs, gh-prs-merge, gh-prs-fix-all and tcfeed — plus the cli-tools dispatcher that fronts them. Afterwards /cli-tools list, /cli-tools update and the rest pass straight through from the pit.

Why this entry is not shaped like its neighbours

Every other tool in the roster is one product with one binary. UGig installs ugig; Spinifex installs spx. This one installs a set, and three fields had to change because of it.

bin probes the dispatcher, not the commands. The obvious thing would be to check for gh-prs and call it a day. That answer is wrong on exactly the machines where it matters: anyone who has been using these tools already has gh-prs and tcfeed on PATH from a hand-rolled checkout, so probing either would report the set installed when six of the seven are missing. cli-tools is the one name that only exists if the whole thing landed.

binDirs covers ~/.local/bin. The installer symlinks there and appends nothing to PATH, so the shell that ran the install cannot see the result and would report the tool missing the moment it finished installing it. Same gap turso, gradient and kimi already needed covering.

upgrade is the tool's own updater, not a re-run of the installer. Re-running an installer is the right upgrade path for a self-contained binary, and the wrong one here. cli-tools update pulls and relinks the checkout that actually owns the commands — which for a contributor is somewhere under ~/src, not the path a fresh install would have picked — and it refuses to move a dirty or diverged tree rather than quietly discarding work. Pointing upgrade at the installer would have cloned a second copy beside the first and left the original still on PATH.

What you get on the other side

The commands are real executables rather than shell functions, so they work from an interactive shell, from zsh -c, from a systemd unit and from a CI step alike, with nothing having been sourced first.

cli-tools aliases --install will merge a thin set of pit aliases — /blog, /free, /merge, /prs, /whois — into ~/.moshcode/aliases.json. It merges rather than replaces: a name you bound yourself is kept and the collision is reported, never silently repointed. None of them shares a name with a command, because a shell function beats PATH and an alias named after the file it wraps would shadow it.

v0.64.0 — the red row that lied

Choose a tag to compare

@ralyodio ralyodio released this 17 Aug 08:40
5d9a66a

moshcode mcp install has been registering servers with Claude Code since the day the command shipped. It just spent that whole time telling people it hadn't.

What it looked like

Run an install twice — which is the normal way to use it, because you re-run it whenever you add an engine or a new machine:

· registering rssamplifier → https://rssamplifier.com/mcp across MCP engines…
   claude    ✗ failed (code 1)
   gemini    not installed — /install gemini
   codex     ✓ added
   opencode  ✓ added
   privacycode ✓ added

One red row, in a column of green ones. Read from a box that already had the server, that summary says exactly one thing: moshcode cannot register with Claude Code. So you go and type claude mcp add --transport http rssamplifier https://rssamplifier.com/mcp by hand — registering a server that was already registered, to fix a bug that did not exist.

Why it said that

Claude Code exits 1 when the server is already there, with MCP server rssamplifier already exists in user config. So does Gemini. OpenCode, for the identical no-op, prints a cheerful green box and exits 0.

The fan-out only had the exit code to go on, and an exit code cannot tell "I refused" from "there was nothing to do". Every engine that reports a duplicate honestly got painted as a failure, and the whole command exited 1 along with it — so any script gating on moshcode mcp install broke the second time it ran.

runCmd inherited the child's stdio, which is right for installers whose output nobody parses but left no way to read the one line that settles it. It now has an opt-in capture that tees — the terminal sees exactly what it saw before, and the fan-out gets the text. An engine that says the server is already there is reported as already registered, in grey, alongside the other rows where nothing needed doing. A failure for any other reason is still a failure, in red, and still exits 1.

   claude    already registered
   gemini    not installed — /install gemini
   qwen      ✓ added
   codex     ✓ added
   opencode  ✓ added
   privacycode ✓ added

qwen joins the fan-out

Chasing the above meant checking every agent CLI on the box against its own --help rather than against what moshcode believed about it, and one engine had been misfiled. Qwen Code is a Gemini CLI fork and kept the entire mcp add surface — same -s/-t/-e/-H, same "URL or command" positional — while moshcode listed it under no MCP support. It now shares gemini's argv builder outright, so the two can only drift on purpose.

The rest of the roster was checked and left alone, which is worth stating so nobody re-checks it: kimi still has no mcp subcommand at all, openagents exposes an MCP server rather than registering one, deepseek-code exits silently even for --help, and aider genuinely has none.

The flag that did nothing, loudly

One more thing surfaced while testing, and it is the sharpest of the three. mcp install <url> --dry-run wrote to every engine's config.

--dry-run is not an mcp flag. Typed after a remote URL it was never matched as a flag, so it fell through into args — and args is what every remote builder discards, because a URL server takes no command arguments. The flag vanished with no error and the install proceeded at full speed. Someone asking for a preview got five config files written instead.

Anything left over after a remote URL is now an error, raised before a single engine runs. Arguments after -- are still the user's own command line and are still not second-guessed.

Note

If you hand-added an MCP server to Claude Code because moshcode said it failed, nothing is broken — you registered what was already there. claude mcp get <name> will confirm it, and re-running moshcode mcp install now says so plainly.

v0.63.0 — the bridge was there all along

Choose a tag to compare

@ralyodio ralyodio released this 17 Aug 06:00
6270c33

moshcode dns status has been telling people their bridge was down while it was up, and then handing them the one command that would actually take it down.

What it looked like

A machine running the bridge as a systemd unit, resolving Moshpit names perfectly, forwarding everything else:

platform   linux
bridge     not running
routing    configured (/etc/systemd/resolved.conf.d/moshpit.conf)

! routing is in place but the bridge is not running — Moshpit names will fail.
  fix with: sudo moshcode dns enable     undo with: sudo moshcode dns disable

Every line of that alarm is false, and the remedy is worse than the complaint. dns enable starts a bridge bound to 127.0.0.1:5354, while the one already working holds 0.0.0.0:5354. The kernel delivers to the more specific socket, so the healthy bridge stops receiving anything at all — running, supervised, and deaf. Catch-all routing means that is not a Moshpit outage, it is the machine's whole resolver. portHolder has carried a comment about this failure for a while ("that took DNS down twice in one day"); what was missing is that status was the thing walking people into it.

Why it said that

status asked its own pidfile and nothing else. That file describes a bridge this tool started, in this privilege context — and nothing else qualifies:

  • a bridge run as a systemd unit
  • a bridge started by hand with dns start
  • a bridge started by dns enable itself, which escalates to root and therefore wrote its pidfile under root's HOME instead of the invoking user's runtime dir

The third is the sharp one. dns enable is the supported way to start a bridge, and it recorded that bridge somewhere every later unprivileged dns status and dns disable would neither look at nor be allowed to read. The tool lost track of its own daemon the moment it succeeded.

What changed

The port gets asked. bridgePresence puts both questions, because they fail differently — a resolver that stopped answering Moshpit names loses a namespace, and one that stopped forwarding takes the box off the internet:

platform   linux
bridge     answering on 127.0.0.1:5354 (pid 1330, bun) — started by something other than `dns enable`
routing    configured (/etc/systemd/resolved.conf.d/moshpit.conf)
registry   reachable — 17848 TLDs claimed

A bridge nothing here started is reported as a bridge, with its pid, so it can be found. The alarm now fires on nothing answers, not our pidfile is empty. And a new warning covers the state that was invisible from the Moshpit side entirely: a bridge that answers names but has stopped forwarding, which is the one that costs you clearnet.

The advice lost its sudo too. The CLI escalates the single step that needs root; teaching sudo moshcode as a habit is how sudo moshcode update ends up reinstalling the whole tool into /root.

Two supporting fixes came with it. pidfilePath honours SUDO_UID, so an escalated enable records the bridge where the unprivileged runs that ask about it will actually look — and only when that runtime dir exists, since deriving /run/user/<uid> on a machine without one swaps an unreadable path for a missing one. And status now uses the injected registry client like every other caller, which had quietly made it the one subcommand that could not be tested without a network.

Note

bridge not running was never a reason on its own to run dns enable. If you hit this and enabled on top of a working bridge, check for two listeners on 5354 — ss -lnup | grep 5354 — and stop the one you did not mean to start.

v0.62.0 — same code, fresh release

Choose a tag to compare

@ralyodio ralyodio released this 17 Aug 05:13
c0a1751

No code changes. v0.62.0 is identical to v0.61.0 — the only difference is the
version in package.json. It was cut so there is a fresh release and a fresh npm
publish to update from.

If you are upgrading to chase a DNS problem, this is worth knowing up front: it
does not contain a fix for one. moshcode dns enable, the bridge, and the
resolver logic are all unchanged from v0.61.0.

Two things worth recording from the session that produced this release, neither of
which required a code change:

  • The bridge parks unpointed names correctly. A name that is claimed but has
    no target — scrambled.eggs is the one the docs use — resolves to the parking
    address (69.46.46.103), not NXDOMAIN. So curl http://scrambled.eggs failing
    with "Could not resolve host" is not the bridge; it means the system resolver
    never asked the bridge, which points at the routing step, not the daemon.
  • dns start takes a few seconds to bind. It fetches the full ending list
    (17,848 of them) before it opens the socket, so a query fired immediately after
    launch gets connection-refused from a process that is working fine. dns enable
    already accounts for this by probing until the bridge answers.

v0.61.0 — Spinifex brings the cloud back in-house

Choose a tag to compare

@ralyodio ralyodio released this 17 Aug 04:40
c6a811f

Spinifex brings the cloud back in-house

moshcode install spinifex now installs Spinifex — EC2, EBS, S3, VPC and IAM, API-compatible with AWS, running on hardware you own.

moshcode install spinifex          # curl -fsSL https://install.mulgadc.com | bash
moshcode spinifex version          # straight through to the native `spx` CLI
moshcode spinifex ec2 describe-instances

It is the other end of the infra list. railway, doctl, supabase and the rest drive someone else's cloud; this one turns a bare-metal box, an edge node or an on-prem rack into one, so the same aws calls and Terraform providers point at machines you control.

The product is Spinifex and the binary is spx, so the registry carries both: moshcode tools lists spinifex, and moshcode spinifex … is exact passthrough to spx — the same split moshcode secrets already has with logicsrc. Arguments, stdin, stdout, stderr and the native exit code go straight through. spinifex(args…) works in .mosh scripts, and the name is in shell completions, /help, /tools and moshcode upgrade alongside the rest of the registry.

It is a host platform, not a binary drop

Spinifex installs system services, so its installer is the most invasive one in the registry, and this release is deliberate about two things.

It always needs root — it writes /usr/local/bin/spx, systemd units and scoped sudoers.d rules, and pulls QEMU/KVM, OVN/Open vSwitch and the AWS CLI through apt. Unlike tailscale, no platform gets a pass: Ubuntu 26.04 and Debian 13 are the supported hosts and there is no macOS build to delegate to. MoshCode asks for the password before the work starts rather than letting the installer stop for it partway through, which is what keeps an unattended moshcode update from parking on a prompt nobody is watching.

MoshCode also passes INSTALL_SPINIFEX_SKIP_NEWGRP=1. On a terminal the vendor script finishes by execing newgrp spinifex to activate the new group, which would replace the install with an interactive subshell — you would land in a shell instead of back in the pit, and inside moshcode update the rest of the plan would wait behind it. Log in again, or run newgrp spinifex yourself, to pick the group up.

One prerequisite MoshCode does not touch: the host's WAN interface must already be enslaved to a bridge named br-wan. Check it with ip -br link show br-wan before you install. The installer does not create it, bridging a live uplink can drop the box off the network, and that is not a decision an install command should make for you. Post-install — setup-ovn.sh --management, spx admin init, systemctl start spinifex.target — stays with Spinifex's own docs.

Re-running the installer is Spinifex's documented update path: it detects the existing install, replaces the binary, applies pending config migrations and restarts the services. So moshcode upgrade spinifex is the same command that installed it, which is exactly how coral and the other script-installed tools already behave.

Full changelog: v0.60.0...v0.61.0

v0.60.0 — the herd stops reading paint

Choose a tag to compare

@ralyodio ralyodio released this 17 Aug 03:30
c1560e1

The herd stops reading paint

Session state came from a regex against a screen capture. That is a guess, and
it was wrong in the two places it mattered most: a prompt left no evidence it
had ever been submitted, and the roster stopped at the edge of this machine.
PRD 0011 replaces the guess with the engine's own word.

moshcode herd hooks install claude   # state from the engine, not from its screen
moshcode herd tasks api              # every prompt, and how long each waited on you
moshcode herd serve                  # the herd over A2A v0.3.0, behind your login
moshcode herd remote add research https://…/production
moshcode herd eval datasets/refactor.jsonl --engines claude,codex

hooks install writes Claude Code's own lifecycle hooks into your settings
file — merged, never clobbered — so a session reports authority: hook instead
of a pattern match, and blocked gains sub-kinds (permission, question, menu)
that ride in --json and in notifications. Every prompt now mints a task with
its transitions and its output, in a 0600 ledger capped at 500 tasks per
session, which is what herd tasks, herd task, herd log and herd stats
read back. Blocked time is reported as what it actually is: waiting on a human.
wait --any/--all retires the polling loop every fan-out script had written for
itself. herd remote add puts a deployed agent on the roster and prompt,
read, wait and kill work on it unchanged. herd doctor will now tell you
what is wrong with rules.json rather than ignoring the file.

/usage, because that is the word everyone types

moshcode cost shipped in v0.59.0 and answers exactly what a coding agent's own
/usage answers. So typing the word you already know got you unknown command
and nothing else. It is now the same command:

moshcode usage --all --since 7d   # identical to moshcode cost

/usage in the pit, moshcode usage on the CLI, /help usage, and shell
completion all resolve to cost — one entry in the command table, so there is
no second copy to drift.

qwen counts too

qwen was listed as an engine that keeps no readable record. That has stopped
being true, so a qwen session — launched by moshcode agents qwen or by hand —
was reported nowhere at all. It writes one record per request to
~/.qwen/usage/token-usage-YYYY-MM.jsonl, and the directory it started in to
~/.qwen/projects/<slug>/chats/<session>.runtime.json, and both are now read.

Two pieces of its arithmetic are easy to get backwards, and neither is guessed
here. cachedTokens is part of inputTokens, so the fresh input is the
difference — the same trap Codex's cumulative counts set. And thoughtsTokens
is already inside outputTokens on the OpenAI-compatible path, where
completion_tokens contains reasoning_tokens; adding it would bill the
thinking twice. Only the native path, which reports Gemini's
candidatesTokenCount, has to add it back.

No Alibaba rates ship, for the same reason no OpenAI or Google rates do: the
model ids these CLIs record do not appear on a public price list, so anything
written down would be invented. qwen runs report their tokens with no dollar
figure, and the report tells you where to put your own rates.

Still unreported: gemini, kimi, deepseek and openagents. They log nothing a
reader can use, and they say so rather than showing $0.

Also

Moshpit's bulk publish streams its request instead of buffering the whole batch
in memory, and its body limit is now sized for the batch the API documents
rather than something smaller. The PWA is served from nginx and systemd on the
box that hosts it.

v0.59.0 — what the herd is costing you

Choose a tag to compare

@ralyodio ralyodio released this 16 Aug 12:13
8c8cbfb

The other question you have with six agents running

moshcode ps answers "which one is blocked". It has never answered "what is
this costing me", and with a herd running overnight that is the question you
wake up to.

  session  engine  model          in    out   cache  cost    age
  api      claude  claude-opus-5  1.2k  27k   10.5M  $9.91~  42m
  audit    codex   gpt-5.6-sol    400   200   600    —       12m

  total  $9.91~  1.6k in · 27k out · 10.5M cached
  ~ estimated from published rates; unmarked figures are the engine's own.
⚠ no rate for gpt-5.6-sol — tokens counted, cost omitted.

Nothing is instrumented, wrapped or proxied to produce that. Every engine
already writes down what it used, because it needs the numbers itself — Claude
Code keeps a per-message usage block in ~/.claude/projects/**/*.jsonl, Codex
emits cumulative token_count events into ~/.codex/sessions/…, opencode and
privacycode store a cost they computed themselves in SQLite, and aider prints
a running session total into .aider.chat.history.md beside the code. This
reads what is on disk and lines it up against the herd.

moshcode cost                    # per session, last 24h
moshcode cost api                # one session, with its engine runs
moshcode cost --all --since 7d   # every engine session on the box, herd or not
moshcode cost --watch            # re-read every 10s — the slope is the point
moshcode cost --json             # for a script

Also moshcode herd cost, /cost in the pit, and cost() in moshscript.
--all needs no herd at all, so a foreground moshcode agents claude is
covered the same way.

A ~ is an estimate and an unmarked figure is not

opencode and aider price their own messages, and that price is reported
untouched — they know which model actually served the request. Claude Code and
Codex record tokens and nothing else, which is the honest state of things on a
subscription where the marginal request costs nothing extra, so those get
multiplied by a rate card and marked. The mark is the whole point: one of those
numbers is a measurement and the other is "what this would have cost on the
API". --json carries it as costSource.

A model nobody has priced shows its tokens and no cost, rather than a
convincing-looking $0. Only Anthropic rates ship, because a Codex or Kimi
model id (gpt-5.6-sol is what a rollout actually records) does not appear on
any public price list and inventing one would be worse than a blank. Your own
rates go in ~/.moshcode/pricing.json and win over the shipped table:

{ "gpt-5.6-sol": { "input": 1.25, "output": 10 } }

Cache tokens get a column of their own for the same reason. On a long agent
session they are most of the traffic and a tenth of the price — fold them into
in and a $3 session reads like a $60 one, which is the opposite of the point.

Attribution is engine + directory + "started before this run did". That is right
for the ordinary case of one agent per directory and can be wrong when two
sessions of the same engine share one, so --json carries the raw run list
rather than implying an answer, and a run that matches no session is its own
line instead of being absorbed into a neighbour's total. gemini, kimi, qwen,
deepseek and openagents keep no readable usage log, so they report no cost —
which is not the same as zero cost, and the report says so.

A name can publish, not just point

target proxies a name at a web server its owner already runs. That is the
right primitive and the wrong first step: most people who claim a name have
something to publish and nowhere to publish it from, so the commonest thing in
the registry was a name that never became a site.

A name can now carry a feed — paste an RSS or Atom URL next to any name you
hold and /n/<name> renders it, as a blog with dates and excerpts or as a
podcast with cover art, running times and a player. The layout is inferred from
whether the entries carry audio enclosures, and can be overridden when the guess
is wrong.

The other half is somewhere to put writing that does not exist anywhere yet:

POST /api/moshpit/sites/blue.eggs/content
{"kind":"link","title":"Worth reading","url":"https://example.com/post"}

Eight kinds — section and page for navigation, then text, link, image, gallery,
video and embed — which is the set a link aggregator has because it is the set
people actually post. It upserts on the slug, because the caller is a webhook
and anything firing over HTTP retries: a publish endpoint that makes a second
copy on every redelivery is one you cannot point automation at.

And GET /api/moshpit/log finally exposes the allocation record. "The directory
can be mirrored and served by anyone" was true of the schema and false of the
product, because nothing could read the log; now a second copy of the registry
is something a stranger can build.

moshscript catches up with the CLI

The vocabulary had drifted to 43 verbs against 54 commands, with no way for a
script to authenticate, reach the pit's aliases, or read a tool's output as
anything but an exit code. Account verbs (login, whoami, requireLogin,
logout) close that, with identity() in src/auth.mjs as the one place that
decides what "logged in" means so the CLI and a script can never disagree.

The paddle is on the game's clock

Pong and breakout tick at 60Hz and move the ball on that clock; the paddle — the
one object you are actually steering — was still being moved by the keypress
itself, one jump per key. A terminal has no key-up, only auto-repeat, so that is
two problems and neither of them is "the paddle is slow". Both paddles now run
on the same clock as everything else on the board.

Housekeeping

The ThreatCrush pack moves to 1.7.0, replacing a local scoping fix, and its PR
comment is scoped to the files a PR actually changes.

v0.58.0 — a silent hand-off says so

Choose a tag to compare

@ralyodio ralyodio released this 14 Aug 13:29
a816fdf

A hand-off that runs nothing no longer looks like a clean session

/agents deepseek printed this, and it read as success:

· opening deepseek autonomously — hand-off to its CLI, exit it to come back…
· deepseek exited (code 0). back in the pit.

The engine had not run a single line. "exited (code 0)" is what the pit says
when somebody opens an agent and quits it, so the failure was invisible and the
search for the bug started in the wrong program.

It is a real failure mode rather than a hypothetical. @serjm/deepseek-code
0.5.0 decides whether it is the entrypoint by comparing
resolve(process.argv[1]) against import.meta.url, and npm installs every
global bin as a symlink — so the comparison never matches, the program falls off
the end having done nothing, and exits 0. Anyone who installed it the documented
way got a CLI that silently does nothing. (A fix is open upstream.)

The child owns the terminal while it runs, so its output is not the pit's to
inspect. The clock is enough: an exit of 0 in under a second and a half did not
host a session anybody used, and the pit now says so —

· deepseek exited (code 0). back in the pit.
⚠ deepseek exited instantly without running — that usually means a broken
  install, not a clean session. check it directly with `deepseek-code
  --version`, and reinstall with /install deepseek if that prints nothing.

Only for silent success. A non-zero exit already says something went wrong, and
the engine has usually printed why; adding "that was fast" on top would be
noise. Workflow-tool hand-offs get the same treatment, because they fail the
same way.

v0.57.0 — smallweb stops being a catalogue

Choose a tag to compare

@ralyodio ralyodio released this 14 Aug 11:58
b1216fc

smallweb stops being a catalogue

/rss and /news find were carrying Kagi's Small Web as its catalogue: a 5MB
OPML of 33,000 personal blogs, plus a 1.1MB plain-text fork of the same list.
Both were marked search-only, which kept them out of a subscription but not out
of a search — and find loads every published list on every search, so each
search parsed 33,000 outlines. That is what took the reader down.

Kagi publishes those same blogs already merged. kagi.com/smallweb/feed is one
Atom document, newest first, several thousand recent posts with titles and
summaries, and that is what smallweb is now: one feed wearing a list's name.
/news add smallweb subscribes to it without fetching anything, /news rm smallweb takes it back out, and it reads on its own with
/news --feed kagi-small-web.

A catalogue can no longer be subscribed to by any route

Search-only guarded the list name, so add smallweb was refused while
add https://kagi.com/smallweb/opml quietly wrote 32,969 outlines into
news.opml — after which every /rss tried to fetch all of them. A URL is not
a smaller promise than a name, so the limit now sits on the number of feeds an
add subscribes to, whatever the source: named list, URL, or local file. It is
set well above every published list and well below a catalogue.

If you already have one in your subscriptions, ~/.moshcode/news.opml is the
file to look at — an import by URL lands untagged, so /news rm <list> cannot
find it.

Aggregated feeds no longer crowd out everyone else

Two guards that apply to every feed, not just this one. A feed read stops at 2MB
and parses what arrived, rather than failing outright at the document cap — a
10MB firehose used to be refused entirely when its newest few hundred entries
were exactly what was wanted. And no single feed may put more than 200 items
into a merged listing, because otherwise subscribing to an aggregator is the
same as unsubscribing from everything else: its same-day entries sort above
every other publisher's headlines.