<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Zebulun Arendsee</title>
    <description>The latest articles on DEV Community by Zebulun Arendsee (@arendsee).</description>
    <link>https://dev.to/arendsee</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3792319%2Fa2943535-8935-4a05-aee1-6814d1e89fa9.png</url>
      <title>DEV Community: Zebulun Arendsee</title>
      <link>https://dev.to/arendsee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arendsee"/>
    <language>en</language>
    <item>
      <title>CLI tools have always had a problem</title>
      <dc:creator>Zebulun Arendsee</dc:creator>
      <pubDate>Fri, 18 Sep 2026 12:38:19 +0000</pubDate>
      <link>https://dev.to/arendsee/cli-tools-have-always-had-a-problem-2hga</link>
      <guid>https://dev.to/arendsee/cli-tools-have-always-had-a-problem-2hga</guid>
      <description>&lt;p&gt;As one who has lived in the shell for twenty years, I've been happy (though not surprised) to see how naturally the CLI fit into the AI world.&lt;/p&gt;

&lt;p&gt;However, there is a systemic flaw in the CLI ecosystem: each tool reimplements the same concerns: parsing, I/O formatting, compression, streaming, exit codes, introspection, and more. The builders, whether human or AI, navigate this design space independently, leading to an ecosystem where each interface is unique. Since all tools vary independently, the only path to consistency is for all creators to agree on a wide range of conventions and write their code accordingly.&lt;/p&gt;

&lt;p&gt;Now this flaw isn't a problem when we use CLI tools interactively, but it bites the moment we start using CLIs at scale and directly use their results. But it bites the moment we start using them in more complex cases. The problem is especially acute now as AIs begin to require large libraries of these CLI tools.&lt;/p&gt;

&lt;p&gt;I describe specific instances of this problem below:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tool self-descriptions are inconsistent&lt;/strong&gt;. Suppose you want to loop over the tools in an environment and assemble a manifest of what each one does. CLI tools are supposed to be self-describing, so you should just be able to loop over all tools and call them with &lt;code&gt;--help&lt;/code&gt;. But this fails. Some tools have no &lt;code&gt;--help&lt;/code&gt; at all, some take seconds to produce it (looking at you, Python), and formatting is idiosyncratic. &lt;code&gt;--help&lt;/code&gt; is prose meant for humans, not a machine-readable contract derived from the tool itself. If you want to make a system of tools machine-accessible, you must maintain wrappers around the tools and update them as the tools change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sharing complex data requires agreement on format&lt;/strong&gt;. The UNIX philosophy that everything is a file makes interoperability easy: any tool can take anything as input. But if the input can be anything, only the most general operations are well formed. We can count bytes, grep for patterns, or concatenate streams; for anything more, the tool must know what the bytes mean. Passing structured data is trivial within a native program, but in a system of CLI tools this freedom is lost. A few special cases, like mp3 and png, get dedicated formats but structured data in general cannot be shared. Serializing to JSON is lossy (integer widths vanish) while serializing with Protobuf or Parquet requires agreeing on a heavy framework. Either way, tools depend on shared conventions and implicit knowledge of the upstream producers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Duplication of concerns multiplies dependencies and prevents innovation&lt;/strong&gt;. Much of what a CLI tool does is orthogonal to its core job: compression, parallelism, serialization, argument handling. None of this is hard -- the algorithms are well established. But each algorithm is re-wrapped in each language and each tool must independently decide which to import, which settings to use, and what flags to expose. In cases like compression, where downstream tools must understand the chosen format, the best option is often not the most efficient algorithm but the one most likely to be widely supported. These independent choices multiply dependencies, can break compatibility, resists evolution, and cannot be set for the whole system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Feature addition or subtraction&lt;/strong&gt;. Every maintainer must balance between feature addition and weight reduction. New features may add dependencies, add distracting complexity, slow compile time, and increase maintenance costs. The user wants two powers the monolith denies: addition and subtraction. A compile-time plugin system could offer addition, but that's heavy. In twenty years of writing CLI tools, I've never written a CLI plugin system. Subtraction, the removal of unwanted or problematic features, has no clear solution short of rewriting the program. So the creator chooses a set of features and the user must accept it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commits to one face&lt;/strong&gt;. A CLI tool is made of internal functions and an interface that exposes them. The functions are stable, testable and inherently compatible with many interfaces: a human at the shell types flags, a program passes values directly, a network client sends JSON, a model picks a tool and fills in arguments. The callers change but the functions do not. But when all we have is a CLI, every other interface must be laid over it. Network clients, library bindings, and MCPs all must generate system calls, follow human-oriented flag conventions, and pass stringified types. The connectivity is backwards -- the CLI should be just one interface among equals.&lt;/p&gt;

&lt;p&gt;The purpose of my post here is to describe the problem, not the solution. We need to accept that the problem exists before we can solve it. That said, I do have a solution.&lt;/p&gt;

&lt;p&gt;The solution is NOT a new best practices document (or AI skill). The solution is also NOT to wrap every CLI tool in yet another layer. What I propose is that we strip the CLIs down to their cores and distribute the functions. Then we generate the CLIs deterministally from the functions given their types. And no, I'm suggesting AI here, that would only change how conventions are defined. We need something we can trust. A compiler.&lt;/p&gt;

&lt;p&gt;For the last decade, I have been building the Morloc compiler for this purpose. Morloc generates rich CLIs (and other interfaces) from functions sourced from many languages and unified under a common type system. The CLI is projected from the type. You can find links to more info on &lt;a href="https://github.com/morloc-project/morloc" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; or a post with examples &lt;a href="https://dev.to/arendsee/making-clis-mcps-and-apis-from-one-source-with-morloc-1c87"&gt;here&lt;/a&gt;. If you disagree with the problem, let me know in the comments.&lt;/p&gt;

&lt;p&gt;Image: mis-matching gears by MidJourney&lt;/p&gt;

</description>
      <category>cli</category>
    </item>
    <item>
      <title>Making CLIs, MCPs and APIs from one source with Morloc</title>
      <dc:creator>Zebulun Arendsee</dc:creator>
      <pubDate>Wed, 12 Aug 2026 01:58:06 +0000</pubDate>
      <link>https://dev.to/arendsee/making-clis-mcps-and-apis-from-one-source-with-morloc-1c87</link>
      <guid>https://dev.to/arendsee/making-clis-mcps-and-apis-from-one-source-with-morloc-1c87</guid>
      <description>&lt;p&gt;A Command Line Interface takes a core set of functions and exposes them as a shell utility. An HTTP server can take the same functions and expose them over a network while an MCP can expose them to AI agents. One functional core, many views. In each case, the interface is boilerplate where the best design is the most boring design. Creativity in an API or CLI is like creativity in spelling: iritating. So why not automatically derive the views from the functions? The &lt;a href="https://github.com/morloc-project/morloc" rel="noopener noreferrer"&gt;Morloc&lt;/a&gt; compiler does exactly this.&lt;/p&gt;

&lt;p&gt;As a strongly-typed polyglot compiler, Morloc is uniquely suited for this problem. It knows all function types, fully manages serialization, and propagates docstring hints through to code generation. And this can be done across languages, ensuring consistency.&lt;/p&gt;

&lt;p&gt;Here is a Morloc program that renders the Mandelbrot set with the escape-time computed in Rust and the image rendered in Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- fractals.loc&lt;/span&gt;
&lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="err"&gt;fractals&lt;/span&gt; &lt;span class="err"&gt;(mandel,&lt;/span&gt; &lt;span class="err"&gt;julia)&lt;/span&gt;

&lt;span class="err"&gt;import&lt;/span&gt; &lt;span class="err"&gt;tensor-rust&lt;/span&gt;
&lt;span class="err"&gt;import&lt;/span&gt; &lt;span class="err"&gt;tensor-py&lt;/span&gt;

&lt;span class="err"&gt;source&lt;/span&gt; &lt;span class="nn"&gt;Rust&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="s"&gt;"mandel.rs"&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"julia_grid"&lt;/span&gt; &lt;span class="n"&gt;as&lt;/span&gt; &lt;span class="n"&gt;julia&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"mandel_grid"&lt;/span&gt; &lt;span class="n"&gt;as&lt;/span&gt; &lt;span class="n"&gt;mandel&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;--' Render the Mandelbrot set over a viewport.&lt;/span&gt;
&lt;span class="c1"&gt;--' @render -a/--ascii=asAscii @default&lt;/span&gt;
&lt;span class="c1"&gt;--' @render -p/--ppm=asPpm&lt;/span&gt;
&lt;span class="c1"&gt;--' @render -g/--png=asPng&lt;/span&gt;
&lt;span class="n"&gt;mandel&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Matrix&lt;/span&gt; &lt;span class="kt"&gt;I64&lt;/span&gt;

&lt;span class="c1"&gt;--' Render a Julia set&lt;/span&gt;
&lt;span class="c1"&gt;--' @render -a/--ascii=asAscii @default&lt;/span&gt;
&lt;span class="c1"&gt;--' @render -p/--ppm=asPpm&lt;/span&gt;
&lt;span class="c1"&gt;--' @render -g/--png=asPng&lt;/span&gt;
&lt;span class="n"&gt;julia&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt;
  &lt;span class="c1"&gt;--' Real part of the Julia constant&lt;/span&gt;
  &lt;span class="c1"&gt;--' @arg -r/--real&lt;/span&gt;
  &lt;span class="c1"&gt;--' @default -0.8&lt;/span&gt;
  &lt;span class="kt"&gt;F64&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;--' Imaginary part of the Julia constant&lt;/span&gt;
  &lt;span class="c1"&gt;--' @arg -i/--imaginary&lt;/span&gt;
  &lt;span class="c1"&gt;--' @default 0.156&lt;/span&gt;
  &lt;span class="kt"&gt;F64&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="kt"&gt;Matrix&lt;/span&gt; &lt;span class="kt"&gt;I64&lt;/span&gt;

&lt;span class="c1"&gt;-- continued in next Morloc code block&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rust tensor definitions are imported from the &lt;code&gt;tensor-rust&lt;/code&gt; Morloc module. The &lt;code&gt;mandel&lt;/code&gt; function is sourced from Rust and mapped to a Morloc type. The &lt;code&gt;View&lt;/code&gt; type is a record that is defined in the following Morloc lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="c1"&gt;--' Viewport controls.&lt;/span&gt;
&lt;span class="c1"&gt;--' @unroll&lt;/span&gt;
&lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="kr"&gt;where&lt;/span&gt;
  &lt;span class="c1"&gt;--' Real part of the view center&lt;/span&gt;
  &lt;span class="c1"&gt;--' @arg --center-x&lt;/span&gt;
  &lt;span class="c1"&gt;--' @metavar X&lt;/span&gt;
  &lt;span class="c1"&gt;--' @default -0.5&lt;/span&gt;
  &lt;span class="n"&gt;centerX&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;F64&lt;/span&gt;

  &lt;span class="c1"&gt;--' Imaginary part of the view center&lt;/span&gt;
  &lt;span class="c1"&gt;--' @arg --center-y&lt;/span&gt;
  &lt;span class="c1"&gt;--' @metavar Y&lt;/span&gt;
  &lt;span class="c1"&gt;--' @default 0.0&lt;/span&gt;
  &lt;span class="n"&gt;centerY&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;F64&lt;/span&gt;

  &lt;span class="c1"&gt;--' Magnification (higher = deeper zoom)&lt;/span&gt;
  &lt;span class="c1"&gt;--' @arg --zoom&lt;/span&gt;
  &lt;span class="c1"&gt;--' @metavar Z&lt;/span&gt;
  &lt;span class="c1"&gt;--' @default 1.0&lt;/span&gt;
  &lt;span class="n"&gt;zoom&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;F64&lt;/span&gt;

  &lt;span class="c1"&gt;--' Output width in cells&lt;/span&gt;
  &lt;span class="c1"&gt;--' @arg --width&lt;/span&gt;
  &lt;span class="c1"&gt;--' @metavar COLS&lt;/span&gt;
  &lt;span class="c1"&gt;--' @default 80&lt;/span&gt;
  &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;I64&lt;/span&gt;

  &lt;span class="c1"&gt;--' Output height in cells&lt;/span&gt;
  &lt;span class="c1"&gt;--' @arg --height&lt;/span&gt;
  &lt;span class="c1"&gt;--' @metavar ROWS&lt;/span&gt;
  &lt;span class="c1"&gt;--' @default 40&lt;/span&gt;
  &lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;I64&lt;/span&gt;

  &lt;span class="c1"&gt;--' Escape-iteration ceiling&lt;/span&gt;
  &lt;span class="c1"&gt;--' @arg --max-iter&lt;/span&gt;
  &lt;span class="c1"&gt;--' @metavar N&lt;/span&gt;
  &lt;span class="c1"&gt;--' @default 200&lt;/span&gt;
  &lt;span class="n"&gt;maxIter&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;I64&lt;/span&gt;

&lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="kt"&gt;Rust&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"View"&lt;/span&gt;

&lt;span class="c1"&gt;-- continued in final Morloc code block&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Morloc type definition provides docstring hints to the record that is passed to the downstream code generators. The record will be unrolled in the interfaces as a collection of optional parameters.&lt;/p&gt;

&lt;p&gt;The raw wrapped functions deal with the fundamental data -- numeric matrices in this case. The &lt;code&gt;@render&lt;/code&gt; fields associate renders that take the function output and transform it into a binary or textual output. These can be written in any supported language. Writing them in Rust would have better performance since there would be no data marshalling costs, but I've opted for Python here for prototyping convenience.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="c1"&gt;--' @mime image/png&lt;/span&gt;
&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="kt"&gt;PNG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Vector&lt;/span&gt; &lt;span class="kt"&gt;U8&lt;/span&gt;

&lt;span class="c1"&gt;--' @mime image/x-portable-pixmap&lt;/span&gt;
&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="kt"&gt;PPM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Vector&lt;/span&gt; &lt;span class="kt"&gt;U8&lt;/span&gt;

&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="kt"&gt;Py&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="s"&gt;"render.py"&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"as_ascii"&lt;/span&gt; &lt;span class="n"&gt;as&lt;/span&gt; &lt;span class="n"&gt;asAscii&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"as_ppm"&lt;/span&gt;   &lt;span class="n"&gt;as&lt;/span&gt; &lt;span class="n"&gt;asPpm&lt;/span&gt;
  &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"as_png"&lt;/span&gt;   &lt;span class="n"&gt;as&lt;/span&gt; &lt;span class="n"&gt;asPng&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;--' Shade the grid into terminal ASCII art.&lt;/span&gt;
&lt;span class="n"&gt;asAscii&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Matrix&lt;/span&gt; &lt;span class="kt"&gt;I64&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Str&lt;/span&gt;

&lt;span class="c1"&gt;--' Encode the grid as a binary PPM (P6) image.&lt;/span&gt;
&lt;span class="n"&gt;asPpm&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Matrix&lt;/span&gt; &lt;span class="kt"&gt;I64&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;PPM&lt;/span&gt;

&lt;span class="c1"&gt;--' Encode the grid as a PNG image with a smooth colormap (Pillow).&lt;/span&gt;
&lt;span class="n"&gt;asPng&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;Matrix&lt;/span&gt; &lt;span class="kt"&gt;I64&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;PNG&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Rust and Python sourced code is entirely idiomatic. The data inputs and outputs are natural data structures. There are no Morloc dependencies.&lt;/p&gt;

&lt;p&gt;The Rust code is just 57 lines, summarized below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[allow(non_snake_case)]&lt;/span&gt;
&lt;span class="nd"&gt;#[derive(Clone)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;centerX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;centerY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;zoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;maxIter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;i64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// helper functions&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;mandel_grid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;View&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Array2&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;i64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;julia_grid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c_re&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_im&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;f64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;View&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Array2&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;i64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Morloc View record maps the Rust View struct. Likewise, the sourced Python code for the image renderers is just normal Python code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# render.py -- ordinary numpy; nothing from morloc appears here
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;as_ascii&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;   &lt;span class="c1"&gt;# numpy array in, string out
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;as_ppm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;   &lt;span class="p"&gt;...&lt;/span&gt;   &lt;span class="c1"&gt;# numpy array in, PPM image bytes out
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now compile and run this program and run it with the default ASCII renderer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;morloc make &lt;span class="nt"&gt;-o&lt;/span&gt; fractals fractals.loc 
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./fractals mandel &lt;span class="nt"&gt;--zoom&lt;/span&gt; 1 &lt;span class="nt"&gt;--width&lt;/span&gt; 56 &lt;span class="nt"&gt;--height&lt;/span&gt; 26
&lt;span class="gp"&gt;,,,,,,,;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;iiiiiiiiiiiirrrrrrssXXA@@@32Xssrrriiii&lt;span class="p"&gt;;;;;;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,,,;&lt;/span&gt;iiiiiiiiiiiirrrrrssXXXA2@@@@AXXXssriiiii&lt;span class="p"&gt;;;;;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,,;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;iiiiiiiiiiirrrrssX25AA22H@@52AAXX5sriiii&lt;span class="p"&gt;;;;;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,,;&lt;/span&gt;iiiiiiiiiiirrrsssXXhGh5G@@@@@@@2A2MAriiiii&lt;span class="p"&gt;;;;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,,;&lt;/span&gt;iiiiiiiiirrsssssXXA2@@@@@@@@@@@@h@5Xsriiii&lt;span class="p"&gt;;;;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,;&lt;/span&gt;iiiiiiiirrssssssXXX35@@@@@@@@@@@@@9AXsriiiii&lt;span class="p"&gt;;;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,;&lt;/span&gt;iiiiiirrsXXXXXXXXXA#@@@@@@@@@@@@@@@HAsrriiii&lt;span class="p"&gt;;;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,iiiirrrrsXAAAAMAXAAMh@@@@@@@@@@@@@@@M5Xrriiiii;&lt;/span&gt;&lt;span class="p"&gt;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,iirrrrrssXX2S2535AA#&lt;/span&gt;@@@@@@@@@@@@@@@@@AXrriiiii&lt;span class="p"&gt;;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,irrrrrsssXX2#&lt;/span&gt;@@@@32h@@@@@@@@@@@@@@@@@5Xrriiiii&lt;span class="p"&gt;;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,rrrrrsssAAAH@@@@@@h@@@@@@@@@@@@@@@@@@Asrriiiii;&lt;/span&gt;&lt;span class="p"&gt;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,irrrrXXXXA55@@@@@@@@@@@@@@@@@@@@@@@@@@Xsrrriiiii;&lt;/span&gt;&lt;span class="p"&gt;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,rsXXAAAAM5@@@@@@@@@@@@@@@@@@@@@@@@@@@AXsrrriiiii;&lt;/span&gt;&lt;span class="p"&gt;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,rsXXAAAAM5@@@@@@@@@@@@@@@@@@@@@@@@@@@AXsrrriiiii;&lt;/span&gt;&lt;span class="p"&gt;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,irrrrXXXXA55@@@@@@@@@@@@@@@@@@@@@@@@@@Xsrrriiiii;&lt;/span&gt;&lt;span class="p"&gt;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,rrrrrsssAAAH@@@@@@h@@@@@@@@@@@@@@@@@@Asrriiiii;&lt;/span&gt;&lt;span class="p"&gt;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,irrrrrsssXX2#&lt;/span&gt;@@@@32h@@@@@@@@@@@@@@@@@5Xrriiiii&lt;span class="p"&gt;;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,iirrrrrssXX2S2535AA#&lt;/span&gt;@@@@@@@@@@@@@@@@@AXrriiiii&lt;span class="p"&gt;;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,iiiirrrrsXAAAAMAXAAMh@@@@@@@@@@@@@@@M5Xrriiiii;&lt;/span&gt;&lt;span class="p"&gt;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,;&lt;/span&gt;iiiiiirrsXXXXXXXXXA#@@@@@@@@@@@@@@@HAsrriiii&lt;span class="p"&gt;;;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,;&lt;/span&gt;iiiiiiiirrssssssXXX35@@@@@@@@@@@@@9AXsriiiii&lt;span class="p"&gt;;;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,,;&lt;/span&gt;iiiiiiiiirrsssssXXA2@@@@@@@@@@@@h@5Xsriiii&lt;span class="p"&gt;;;;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,,;&lt;/span&gt;iiiiiiiiiiirrrsssXXhGh5G@@@@@@@2A2MAriiiii&lt;span class="p"&gt;;;;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,,;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;iiiiiiiiiiirrrrssX25AA22H@@52AAXX5sriiii&lt;span class="p"&gt;;;;;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,,,;&lt;/span&gt;iiiiiiiiiiiirrrrrssXXXA2@@@@AXXXssriiiii&lt;span class="p"&gt;;;;;;;;;&lt;/span&gt;
&lt;span class="gp"&gt;,,,,,,,;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;iiiiiiiiiiiirrrrrrssXXA@@@32Xssrrriiii&lt;span class="p"&gt;;;;;;;;;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or we can apply the PNG renderer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;./spin mandel --center-x -0.743 --center-y 0.131 \
              --zoom 250 --max-iter 400 \
&lt;/span&gt;&lt;span class="gp"&gt;              --width 1000 --height 420 --png &amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mandel.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8mkqyg3gv7zluxzl7hhb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8mkqyg3gv7zluxzl7hhb.png" alt="A mandelbrot visualization" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating help statements
&lt;/h2&gt;

&lt;p&gt;Given the function types and the associated docstring hints, Morloc can generate rich usage statements. We can view the top-level help where one subcommand for each exported term is displayed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;morloc make &lt;span class="nt"&gt;-o&lt;/span&gt; fractals fractals.loc
&lt;span class="nv"&gt;$ &lt;/span&gt;./fractals &lt;span class="nt"&gt;-h&lt;/span&gt;
Usage: ./fractals &amp;lt;nexus_options&amp;gt; &amp;lt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &amp;lt;command_options&amp;gt;

Commands:
  mandel  Render the Mandelbrot &lt;span class="nb"&gt;set
  &lt;/span&gt;julia   Render a Julia &lt;span class="nb"&gt;set&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we can view the usage info for the individual subcommand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./fractals julia &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;span class="go"&gt;Render a Julia set

&lt;/span&gt;&lt;span class="gp"&gt;Usage: ./fractals &amp;lt;nexus_options&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;julia &amp;lt;command_options&amp;gt;
&lt;span class="go"&gt;
General Options:
  -h, --help   Print help (see more with '--help')
  -a, --ascii  Shade the grid into terminal ASCII art.
  -p, --ppm    Encode the grid as a binary PPM (P6) image.
  -g, --png    Encode the grid as a PNG image with a smooth colormap (Pillow).

Optional arguments:
&lt;/span&gt;&lt;span class="gp"&gt;  -r, --real &amp;lt;F64&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;Real part of the Julia constant
&lt;span class="go"&gt;                         type: F64 [default: -0.8]
&lt;/span&gt;&lt;span class="gp"&gt;  -i, --imaginary &amp;lt;F64&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;Imaginary part of the Julia constant
&lt;span class="go"&gt;                         type: F64 [default: 0.156]
&lt;/span&gt;&lt;span class="gp"&gt;      --center-x &amp;lt;X&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;Real part of the view center
&lt;span class="go"&gt;                         type: F64 [default: -0.5]
&lt;/span&gt;&lt;span class="gp"&gt;      --center-y &amp;lt;Y&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;Imaginary part of the view center
&lt;span class="go"&gt;                         type: F64 [default: 0.0]
&lt;/span&gt;&lt;span class="gp"&gt;      --zoom &amp;lt;Z&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;Magnification &lt;span class="o"&gt;(&lt;/span&gt;higher &lt;span class="o"&gt;=&lt;/span&gt; deeper zoom&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;                         type: F64 [default: 1.0]
&lt;/span&gt;&lt;span class="gp"&gt;      --width &amp;lt;COLS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;Output width &lt;span class="k"&gt;in &lt;/span&gt;cells
&lt;span class="go"&gt;                         type: I64 [default: 80]
&lt;/span&gt;&lt;span class="gp"&gt;      --height &amp;lt;ROWS&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;Output height &lt;span class="k"&gt;in &lt;/span&gt;cells
&lt;span class="go"&gt;                         type: I64 [default: 40]
&lt;/span&gt;&lt;span class="gp"&gt;      --max-iter &amp;lt;N&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;Escape-iteration ceiling
&lt;span class="go"&gt;                         type: I64 [default: 200]

Return:
  default:    Matrix _ _ I64
  -a/--ascii: Str
  -p/--ppm:   image/x-portable-pixmap
  -g/--png:   image/png
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note from the &lt;code&gt;Usage&lt;/code&gt; line that CLI invocations separate the &lt;code&gt;nexus_options&lt;/code&gt; from the &lt;code&gt;command_options&lt;/code&gt;. A Morloc CLI tool separates the namespace flags that are shared between all Morloc CLIs from the namespace of flags that are unique to a particular program. This allows the whole ecosystem of tools to gain new features without conflicting with the argument space of the commands.&lt;/p&gt;

&lt;p&gt;Two of these "nexus" options are &lt;code&gt;--json-help&lt;/code&gt; and &lt;code&gt;--mcp-tools&lt;/code&gt; which, respectively, print the full machine-readable interface in JSON for the CLI and for the MCP server (which we will cover soon).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;./fractals &lt;span class="nt"&gt;--mcp-tools&lt;/span&gt; | jq &lt;span class="s1"&gt;'.tools[].name'&lt;/span&gt;
&lt;span class="s2"&gt;"mandel"&lt;/span&gt;
&lt;span class="s2"&gt;"julia"&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;./fractals &lt;span class="nt"&gt;--mcp-tools&lt;/span&gt; | jq &lt;span class="s1"&gt;'.tools[]|select(.name=="mandel").inputSchema.properties|keys'&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"_render"&lt;/span&gt;, &lt;span class="s2"&gt;"centerX"&lt;/span&gt;,&lt;span class="s2"&gt;"centerY"&lt;/span&gt;,&lt;span class="s2"&gt;"height"&lt;/span&gt;,&lt;span class="s2"&gt;"maxIter"&lt;/span&gt;,&lt;span class="s2"&gt;"render"&lt;/span&gt;,&lt;span class="s2"&gt;"width"&lt;/span&gt;,&lt;span class="s2"&gt;"zoom"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this way, all Morloc CLI tools offer machine-verified self-description.&lt;/p&gt;

&lt;p&gt;These fractal CLIs focus on the rendered images. For the more common CLIs where we want to directly access the output data, we have several built in options for formatting. For tools where no default renderer is set, the default output is the JSON representation of the functional return type (here a &lt;code&gt;Matrix I64&lt;/code&gt; type, a matrix of 64-bit integers). To over-ride the default, we explicitly ask for JSON formatting with the &lt;code&gt;-f json&lt;/code&gt; option on the left of the subcommand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;./fractals &lt;span class="nt"&gt;-f&lt;/span&gt; json mandel &lt;span class="nt"&gt;--width&lt;/span&gt; 8 &lt;span class="nt"&gt;--height&lt;/span&gt; 4
&lt;span class="o"&gt;[[&lt;/span&gt;4,8],[1,2,3,4,13,4,2,2,1,4,10,11,200,200,3,2,1,4,10,11,200,200,3,2,1,2,3,4,13,4,2,2]]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using JSON here is lossy and non-self-describing. We can alternatively use the Morloc binary format (VoidStar).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;./fractals &lt;span class="nt"&gt;-f&lt;/span&gt; packet &lt;span class="nt"&gt;-z&lt;/span&gt; 3 mandel &lt;span class="nt"&gt;--width&lt;/span&gt; 800 &lt;span class="nt"&gt;--height&lt;/span&gt; 400 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; mandel.dat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we write data into compressed Morloc binary. This can be natively read by other Morloc CLI tools and is the natural medium for chaining Morloc tools together and storing cached data. In the future, I will write a dedicated post that presents Morloc VoidStar format and the efficient compression, streaming and parallelism that it enables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Composing a new command
&lt;/h2&gt;

&lt;p&gt;Morloc modules are not terminal -- they can be imported into other Morloc programs and the functions can be re-exported or composed into new functions.&lt;/p&gt;

&lt;p&gt;Here is a second Morloc program that imports the original &lt;code&gt;fractals&lt;/code&gt; module, re-exports &lt;code&gt;mandel&lt;/code&gt; and &lt;code&gt;julia&lt;/code&gt; and wraps a sweep over imaginary components of the Julia set to create an MP4 movie:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight haskell"&gt;&lt;code&gt;&lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="err"&gt;spin&lt;/span&gt; &lt;span class="err"&gt;(mandel,&lt;/span&gt; &lt;span class="err"&gt;julia,&lt;/span&gt; &lt;span class="err"&gt;spin)&lt;/span&gt;

&lt;span class="err"&gt;import&lt;/span&gt; &lt;span class="err"&gt;root-rust&lt;/span&gt; &lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Functor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;.fractals&lt;/span&gt;

&lt;span class="c1"&gt;--' @mime video/mp4&lt;/span&gt;
&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="kt"&gt;MP4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Vector&lt;/span&gt; &lt;span class="kt"&gt;U8&lt;/span&gt;

&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="kt"&gt;Py&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="s"&gt;"render.py"&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;"as_mp4"&lt;/span&gt; &lt;span class="n"&gt;as&lt;/span&gt; &lt;span class="n"&gt;asMp4&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;--' Encode a sequence of iteration grids as an MP4.&lt;/span&gt;
&lt;span class="n"&gt;asMp4&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Matrix&lt;/span&gt; &lt;span class="kt"&gt;I64&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;MP4&lt;/span&gt;

&lt;span class="c1"&gt;--' Make movie over Julia sweep&lt;/span&gt;
&lt;span class="c1"&gt;--'&lt;/span&gt;
&lt;span class="c1"&gt;--' @render -m/--mp4=asMp4 @default&lt;/span&gt;
&lt;span class="n"&gt;spin&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt;
  &lt;span class="c1"&gt;--' Values for constant cRe&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;F64&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;--' Imaginary part of the Julia constant&lt;/span&gt;
  &lt;span class="kt"&gt;F64&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Matrix&lt;/span&gt; &lt;span class="kt"&gt;I64&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;spin&lt;/span&gt; &lt;span class="n"&gt;cs&lt;/span&gt; &lt;span class="n"&gt;cIm&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;\&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;julia&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;cIm&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;cs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This post emphasizes Morloc as an interface generator, but it is also fully featured polyglot programming language with typeclasses, generics, higher-order functions, recursion and other abstractions. In the code above, the &lt;code&gt;julia&lt;/code&gt; function is mapped over a given list of inputs and the resulting list of matrices is converted by a custom renderer to an MP4.&lt;/p&gt;

&lt;p&gt;Inputs, such as the list of real constant components above, can be passed in many ways. For list-shaped data, Morloc first parses them a JSON strings (e.g., &lt;code&gt;[-0.9,-0.8]&lt;/code&gt; and then as files formatted as Morloc binary packets, MessagePack or JSON. The inputs formats that are supported depend on the Morloc data type and on special docstring directives. The full power of the input system would merit its own post (or you could read the docs).&lt;/p&gt;

&lt;p&gt;In the example below, I provide a JSON file for the real components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;morloc make spin.loc
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./main spin julia-values.json 0.156 &lt;span class="nt"&gt;--center-x&lt;/span&gt; 0 &lt;span class="nt"&gt;--width&lt;/span&gt; 800 &lt;span class="nt"&gt;--height&lt;/span&gt; 400 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; spin.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7aavd6an6t15ev9rz9pb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7aavd6an6t15ev9rz9pb.gif" alt="A GIF video showing a changing Julia set fractal visualization" width="760" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The same core as an MCP server
&lt;/h2&gt;

&lt;p&gt;Built with one more flag, the &lt;strong&gt;same&lt;/strong&gt; &lt;code&gt;spin.loc&lt;/code&gt; can create an MCP&lt;br&gt;
server. Every exported function becomes a tool and the &lt;code&gt;View&lt;/code&gt; record's fields become tool properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;morloc make &lt;span class="nt"&gt;-o&lt;/span&gt; spin &lt;span class="nt"&gt;--mcp-out&lt;/span&gt; spinm spin.loc
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;claude mcp add spin &lt;span class="nv"&gt;$PWD&lt;/span&gt;/spinm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can directly use these tools in Claude, here is the full help menu for the &lt;code&gt;spin&lt;/code&gt; movie generator in Claude:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnjg7nsv213mpuifrq1hd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnjg7nsv213mpuifrq1hd.png" alt="A screenshot from Claude Code of the help menu for the spin command" width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The same core as a network server
&lt;/h2&gt;

&lt;p&gt;Feeding fractal videos into your LLM is a great way to stay warm on a&lt;br&gt;
winters night, but probably a more useful backend would be a conventional HTTP server. This can be built with the &lt;code&gt;--daemon-out &amp;lt;exe&amp;gt;&lt;/code&gt; flag. The generated executable can serve the module over HTTP, TCP, and Unix sockets. Over HTTP a renderer whose result carries a &lt;code&gt;@mime&lt;/code&gt; is sent as raw bytes with a real &lt;code&gt;Content-Type&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;morloc make &lt;span class="nt"&gt;--daemon-out&lt;/span&gt; spind spin.loc
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./spind &lt;span class="nt"&gt;--http-port&lt;/span&gt; 8080 &amp;amp;
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; localhost:8080/discover | jq &lt;span class="s1"&gt;'.result.commands[].name'&lt;/span&gt;
&lt;span class="go"&gt;"mandel"  "julia"  "spin"

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'localhost:8080/call/mandel?render=raw'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;    -d '[{"centerX":-0.5,"centerY":0.0,"zoom":1.0,"width":6,"height":3,"maxIter":200}]'
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 79
Connection: close
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type

{"status":"ok","result":[[3,6],[1,3,3,21,3,2,1,200,200,200,4,2,1,3,3,21,3,2]]}

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'localhost:8080/call/mandel?render=png'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;       -d '[{"centerX":-0.743,"centerY":0.131,"zoom":250.0,"width":600,"height":400,"maxIter":400}]' \
       -o mandel.png
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;spin&lt;/code&gt; animation is media-typed too, so it comes back over HTTP as a normal MP3 file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST localhost:8080/call/mlcp_spin_mp4 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;    -d '[[-0.8,-0.7,-0.6,-0.5],0.156,{"width":600,"height":400,"zoom":1,"centerX":0,"centerY":0,"maxIter":200}]' \
    -o spin.mp4
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file spin.mp4
&lt;span class="go"&gt;spin.mp4: ISO Media, MP4 Base Media v1 [ISO 14496-12:2003]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So write your pure code and then generate all the interfaces with consistency guaranteed by construction. This post just covers one aspect of Morloc. I'll write several follow-up posts focusing on different aspects of the Morloc compiler, language, and ecosystem.&lt;/p&gt;

&lt;p&gt;This post is based on Morloc v0.97.0, see the &lt;a href="https://morloc-project.github.io/" rel="noopener noreferrer"&gt;docs&lt;/a&gt; for the latest.&lt;/p&gt;

</description>
      <category>cli</category>
      <category>mcp</category>
      <category>compilers</category>
      <category>morloc</category>
    </item>
  </channel>
</rss>
