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.

Two verbs are 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 (tsc --noEmit) — absent for a reason that no longer holds, corrected here rather than left to misinform (R451, reported by @Ashguard:dragon 2026-09-01). This used to read “running TypeScript needs node or bun on PATH”. 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), and mesofact-build’s check.rs resolves and spawns that binary directly — PATH=/nonexistent mesofact-build check <dir> runs green and reports error TS2322 with exit 1 on a seeded type error. The illustrated failure the old note gave (“dies with bun: command not found”) was never this path’s failure mode either; check.rs has never invoked bun.

    So mes check is now merely unimplemented, and cheap — this crate already links mesofact-build behind the default-on build feature. Adding it belongs with whichever ticket owns the check surface, so that the facade verb and this one land together rather than drifting.

§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