Skip to main content

Module cli

Module cli 

Source
Expand description

mes — the mesofact dev toolchain.

The whole CLI lives here in the library rather than in a bin target, mirroring mesofact::cli, so that the two bin targets — mes and the transitional mesofact-dev alias — are each three lines over one definition. (They cannot simply share one main.rs: cargo warns when a file backs two targets, and it compiles the whole CLI twice.)

§Why this is a superset of mesofact, not an alias of it

mes carries the prod verbs (serve, publish) alongside the dev ones (dev, build), so a consumer learns one CLI. That does not weaken the W225 §2 dev/prod boundary, because that boundary is about which crates land in a binary, not about which verbs a binary spells. It only ever runs one direction: this crate already depends on the mesofact facade, so mes serve is an in-process call into mesofact::cli — free. The reverse (making the shipped mesofact binary a shim over mes) would drag the watcher, the dev S3 surface and rolldown into the prod closure, and is exactly what §2 forbids. mesofact stays a standalone binary.

Why not spawn the mesofact binary for the prod verbs instead of linking it? (Asked more than once; the answer is not “in-process is tidier”.) It would save nothing. mes dev — the 95% command — is built directly on the facade’s engine: mesofact::Server::from_workload, mesofact::ssr::spawn, mesofact::SsrSpawnOptions, mesofact::ProxyMap. The facade is linked into this binary for the DEV loop whether or not serve is in-process, so process-calling removes zero bytes and adds a runtime dependency on finding mesofact on PATH — cargo install mesofact-dev would yield a mes whose serve fails until you separately install mesofact. It would also hand us exit-code and signal forwarding for no gain. The link-graph direction is what carries the security property here; the process boundary carries none of it.

One verb is absent: proxy — Mode-2 deployment plumbing (worker pool, manifest reload on SIGHUP). It has no dev-loop meaning; mesofact proxy remains its home. Deliberate, and still true.

check used to be a second one, for a reason that did not hold (R451, reported by @Ashguard:dragon 2026-09-01): the note read “running TypeScript needs node or bun on PATH”, and it does not — typescript@7 ships bin/tsc as a three-line Node shim over a per-platform native Go binary (node_modules/@typescript/typescript-<platform>-<arch>/lib/tsc) that mesofact-build’s check.rs resolves and spawns directly.

mes check is implemented as of R832-T4, and the reason it had to be is sharper than “cheap”: a scaffolded project’s typecheck script has to name a binary the user can actually reach, and the R759-F2 trampoline vends exactly three names — mesofact, mes, mesofact-dev. Every other name, mesofact-build included, exits 70. So the verb had to appear on one of those three before mesofact new could put it in a package.json.

§Bare invocation

mes with no subcommand is mes dev . — the 95% command, so it is what you get for typing the least. This is also what keeps the legacy mesofact-dev <DIR> --port N … form working verbatim: it parses as the bare form, so the ~30 spawn sites in the parent camp need no flag day. The one divergence is a workload directory literally named after a subcommand (mesofact-dev serve); spell it ./serve if that ever comes up.

@yah:relay(R822, “Retire the mesofact-dev BINARY; mes is the only dev command (the package name stays)”) @yah:at(2026-09-02T06:04:50Z) @yah:status(open) @yah:assignee(agent:bundle-anthropic-ashguard) @yah:next(“OPERATOR DECISION 2026-09-01, and it is the inverse of the split first proposed: descriptive name where you READ it, short name where you TYPE it. The PACKAGE stays mesofact-dev permanently – not as a legacy spelling of mes, but as the meta crate, the entrypoint to the dev-tier tools mes needs that are NOT mesofact (the watcher, the local S3 surface standing in for R2, serve_app). It depends on the mesofact facade rather than being it, and that direction IS the W225 section 2 prod/dev boundary. The BINARY mesofact-dev goes: nobody wants to type it, ever.”) @yah:next(“SCOPE, measured not guessed: 30 Rust string-literal sites resolve "mesofact-dev" as an executable name (grep ‘"mesofact-dev"’ app crates –include=*.rs, excluding @yah: annotation prose, which is the other ~170 hits and is history rather than reference). The load-bearing ones: crates/yah/plugin/src/source.rs:112 keys a builtin plugin manifest on it via include_str!; crates/yah/bundled/src/lib.rs:171 registers it as a bundled binary; app/yah/desktop/src/mesofact_versions.rs:80,94 resolve it through yah_bundled::find and slot.bin(); app/yah/cli/src/plugin_host.rs:414 names it as SourceRef::Bundled.”) @yah:next(“THE SHARP EDGE, and the reason this is a migration rather than a find-and-replace: ALREADY-INSTALLED store slots have a file literally named mesofact-dev on disk. app/yah/desktop/src/mesofact_versions.rs does slot.bin("mesofact-dev"), so renaming the bin breaks resolution against versions a user already installed. Needs a compat window that accepts either filename, or a store migration – decide which before touching the 30 sites.”) @yah:next(“ALSO IN SCOPE, all naming the bin rather than the package: .yah/qed/release-build.toml builds package=mesofact-dev bin=mesofact-dev (-> bin=mes); scripts/check-mesofact-store.sh asserts a slot holds mesofact AND mesofact-dev and that shims exist for mesofact/mes/mesofact-dev; scripts/check-install-sh-compat.sh compares the mesofact-dev shim; oss/mesofact/scripts/check-mesofact-new.sh copies target/debug/mesofact-dev into the test slot and runs mesofact-dev ..”) @yah:next(“AND THE LEGACY-FORM LOGIC ITSELF: cli.rs’s bare-invocation design exists specifically so mesofact-dev <DIR> --port N parses as mes dev <DIR>, which is what let the parent camp’s spawn sites keep working without a flag day. Once the bin is gone that rationale is spent – the bare form is still right for mes, but the module doc’s justification for it needs rewriting rather than deleting.”) @yah:next(“NOT BLOCKING THE CRATES.IO PUBLISH. Because the package name is unchanged, mesofact-dev@0.8.29 can be published before any of this lands; removing a bin target later is an ordinary deprecation (cargo install at 0.8.29 gets both binaries, at a later version gets only mes). This ticket was originally framed as racing that publish – it is not.”) @yah:next(“END STATE DECIDED BY OPERATOR 2026-09-01, and it supersedes the narrower ‘drop the second [[bin]]’ framing this ticket opened with: mesofact-dev has NO BIN TARGETS AT ALL. It becomes a pure library – the meta crate, the entrypoint to the dev-tier tools that are not mesofact. A new thin package mes owns the binary: one src/main.rs, three lines over mesofact_dev::cli::run(), depending on mesofact-dev.”) @yah:next(“WHY THIS SHAPE RATHER THAN JUST DELETING THE mesofact-dev BIN: cargo install takes a PACKAGE name, not a bin name. With the binary living in the mesofact-dev package, cargo install mes fails with ‘could not find mes in registry’ and users must know to type cargo install mesofact-dev – unguessable from the command. Splitting gives library named for what it IS and binary named for what you TYPE, and removes the bin-collision footgun of two packages both emitting ~/.cargo/bin/mes.”) @yah:next(“SAFE TO DO: verified 2026-09-01 that NO crate anywhere takes mesofact-dev as a library dependency today – the only line in the tree is the library-tier scaffold template (crates/mesofact/src/cli/new/template-lib/Cargo.toml:31), which wants the library and is unaffected. So nothing loses a binary it was consuming, and the new mes package is the first real lib consumer.”) @yah:gotcha(“SUPERSEDED NOTE, removed rather than left to mislead: an earlier gotcha here said the end state was ‘package mesofact-dev while the only [[bin]] is mes’. It is not. The end state is mesofact-dev with ZERO bin targets and a separate mes package owning the binary. The cargo package-vs-bin decoupling still matters, but it is now the reason the LIBRARY can keep a descriptive name while the COMMAND gets a short one across a package boundary, not within one.”) @yah:gotcha(“FEATURE FORWARDING IS THE FIDDLY PART, and this crate already documents the same trap one level down. mesofact-dev’s surface is default = [ssr, build]; ssr = [mesofact/ssr, dep:mesofact-publisher, publish]; publish = [mesofact/publish]; build = [mesofact/build]. The new mes package must re-expose these (ssr = [mesofact-dev/ssr], etc.) or cargo install mes --no-default-features silently loses the lean static/SPA path that exists so consumers can skip the V8 toolchain. mes itself has no cfgs – cli.rs’s #[cfg(feature = …)] gates evaluate in mesofact-dev – so forwarding is all that is required, but omitting it is invisible until someone tries the lean build.”)

Functions§

run