Skip to content

feat(cli): author a CLI in code, by pointer, or as a script - #712

Merged
zechengz merged 11 commits into
mainfrom
feat/cli-invocation
Aug 6, 2026
Merged

zechengz merged 11 commits into
mainfrom
feat/cli-invocation

Conversation

@zechengz

@zechengz zechengz commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Item 2 of #705, authoring your own CLI. Three steps on one branch.

Summary

  • One invocation record. handle_cli builds a frozen CLIInvocation per
    line (argv, stdin, env, config, paths, texts, flags) and every
    handler tier renders it, so fn(config, paths, *texts, **flags) becomes
    fn(inv) in both languages and the stdin-kwarg clobber is gone structurally.
  • cli: points at code, the way resource: does: a builtin or registered
    name, module:ATTR, ./file.py:ATTR, or the mirage.clis entry-point group
    (python; TypeScript keeps its named registry, matching its resources).
  • A CLI can be a script. CLISpec.script (yaml script:) embeds an ordinary
    .py/.js/.mjs program at load and a workspace runtime runs it:
    runtime_for_language takes the first entry speaking the script's language, or
    runtime: pins one. The program gets what a native binary would get, argv
    verbatim after the head word, piped stdin, and the install config as
    MIRAGE_CONFIG json; its exit code becomes the line's $?. That makes a
    script CLI a zero-SDK plugin: one yaml entry plus a program that imports
    nothing from mirage.
  • Both tiers sit behind one front door (help, unknown verbs and declared-flag
    validation all run before either handler) and convert their outcome through
    one run_output/runOutput mapping, now also used by python3 and node.
  • monty gained a stdin global; it dropped RunArgs.stdin before this.

Test plan

  • pytest green; core, node and server tsc and vitest green; pre-commit clean;
    spec drift clean; spec parity 93/93.
  • New script-arm unit suites in both languages (language selection, runtime:
    pin, unknown pin, language mismatch, help and usage refusals without
    executing, MIRAGE_CONFIG, stdin, crash, exit code, limit).
  • New e2e script CLIs: python on monty plus runtime: local and a yaml install
    end to end (quickjs gated on MIRAGE_QUICKJS_HOME), typescript on monty and
    quickjs, both covering $?, history recording and function shadowing.

Arguments and stdin are spelled per runtime (argv global on monty, sys.argv
on pyodide/wasi/local, scriptArgs on quickjs), which is the contract the
python3 and node commands already have; the CLI docs now state it in a
table. Snapshot capture for script installs and the worked examples follow in
the next PR.

- 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

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@zechengz

zechengz commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread python/mirage/workspace/executor/command/cli.py Outdated
Comment thread python/mirage/config.py
Comment thread python/mirage/config.py
…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.
@zechengz

zechengz commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread python/mirage/workspace/snapshot/state.py
Comment thread python/mirage/workspace/executor/command/cli.py
Comment thread python/mirage/workspace/snapshot/state.py
@zechengz

zechengz commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread python/mirage/workspace/cli/registry.py
@zechengz
zechengz merged commit 9c2053b into main Aug 6, 2026
46 checks passed
@zechengz
zechengz deleted the feat/cli-invocation branch August 6, 2026 01:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant