feat(cli): author a CLI in code, by pointer, or as a script - #712
Conversation
- one frozen CLIInvocation per line; leaf handlers take fn(inv) - cli: resolves a name, module:ATTR, ./file.py:ATTR, or mirage.clis - CLISpec.script + runtime, from yaml script:/runtime: - script arm: runtime_for_language selects the interpreter, argv passes verbatim, config arrives as MIRAGE_CONFIG json, monty gains stdin
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
The snapshot test defines its own CLI leaf, which still took config as its first positional and so read the invocation record as the config.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 976c910a9e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…napshots - ScriptSource carries a module bit stamped from the extension at load, so a .mjs CLI reaches quickjs in ES-module mode - a path-form cli: ./tool.py:ATTR rebases on the config dir like script: - snapshots persist a script install by value (source, language, module, runtime pin) and load rebuilds the spec; a named install still travels by name - the tar manifest carries the clis key, which it omitted, so no install survived a disk snapshot - one cli_config_dump serves capture and copy, which crashed on a script install's mapping config
A grammarless script root now has its whole line forwarded and answers --help itself; declaring options opts back into mirage's parsing. A leaf that declares --help is handed the flag instead of being intercepted. The install config env is MIRAGE_CLI_CONFIG, since MIRAGE_CONFIG already names the workspace config file.
argv slot 0 carries the installed head word (RunArgs.prog), so a script
CLI's own messages name it and two installs are distinguishable; monty,
pyodide and quickjs fill it, while wasi and local keep CPython's -c.
New integ/runtime/script_cli{,_js}.json drive the tier on both hosts and
through the yaml -> daemon -> CLI path, which caught the daemon create
route dropping the config's clis and guards options entirely.
An allowlist of StateKey entries silently dropped whatever it forgot from every tar snapshot while in-memory snapshots stayed green, which is how installed CLIs went missing. The manifest is now the state with only the four blob-bearing keys rewritten, plus a regression test that an unknown key survives the round trip.
eval_language duplicated Runtime.language, so a runtime could claim python at one door and js at the other; the disagreement only surfaced as an unexplained 127 or a policy evaluated on the wrong engine. The type is now a literal union, which made the two places that build a ScriptSource from an untyped string visible: the config loader is annotated, and the snapshot loader validates instead of trusting the file.
eslint's no-unnecessary-type-assertion fixer rewrote each `as AnyDict` on an indexed access into a non-null assertion, which no-non-null-assertion then refused, so the hook both modified the file and failed. Optional chaining satisfies both.
The engine asks for the mapping through one os call and mirage declined it, so a program reading os.environ ran on the python host and raised 'not supported in this environment' here. Returning the run env arrives in the guest as a dict, matching python's nine reads exactly; it is a copy, like OSAccess(environ=dict(environ)). No package upgrade needed, 0.0.19-beta.4 already emits the call. integ/runtime pins both spellings across hosts.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c28c1197e0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c28c1197e0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Item 2 of #705, authoring your own CLI. Three steps on one branch.
Summary
handle_clibuilds a frozenCLIInvocationperline (
argv,stdin,env,config,paths,texts,flags) and everyhandler tier renders it, so
fn(config, paths, *texts, **flags)becomesfn(inv)in both languages and the stdin-kwarg clobber is gone structurally.cli:points at code, the wayresource:does: a builtin or registeredname,
module:ATTR,./file.py:ATTR, or themirage.clisentry-point group(python; TypeScript keeps its named registry, matching its resources).
CLISpec.script(yamlscript:) embeds an ordinary.py/.js/.mjsprogram at load and a workspace runtime runs it:runtime_for_languagetakes the first entry speaking the script's language, orruntime:pins one. The program gets what a native binary would get, argvverbatim after the head word, piped stdin, and the install config as
MIRAGE_CONFIGjson; its exit code becomes the line's$?. That makes ascript CLI a zero-SDK plugin: one yaml entry plus a program that imports
nothing from mirage.
validation all run before either handler) and convert their outcome through
one
run_output/runOutputmapping, now also used bypython3andnode.stdinglobal; it droppedRunArgs.stdinbefore this.Test plan
spec drift clean; spec parity 93/93.
runtime:pin, unknown pin, language mismatch, help and usage refusals without
executing,
MIRAGE_CONFIG, stdin, crash, exit code, limit).runtime: localand a yaml installend to end (quickjs gated on
MIRAGE_QUICKJS_HOME), typescript on monty andquickjs, both covering
$?, history recording and function shadowing.Arguments and stdin are spelled per runtime (
argvglobal on monty,sys.argvon pyodide/wasi/local,
scriptArgson quickjs), which is the contract thepython3andnodecommands already have; the CLI docs now state it in atable. Snapshot capture for script installs and the worked examples follow in
the next PR.