fromargs
fromargs is a self-healing, agent-friendly wrapper around
Cyclopts. It composes one cyclopts.App,
forces every command's return value to JSON, and repairs the argv mistakes an
LLM agent tends to make, without ever guessing at intent it cannot verify.
Install
uv add fromargs
# or
pip install fromargs
fromargs pins cyclopts>=4.25.3,<5; it does not yet track Cyclopts 5.
Quick start
from typing import Annotated
import fromargs
app = fromargs.App("cheese-cave", help="Track wheels of cheese as they ripen.")
@app.command
def age(
name: str,
*,
weeks: Annotated[int, fromargs.Parameter(help="Number of weeks to age.")],
dry_run: bool = False,
) -> dict[str, object]:
"""Age one wheel for more weeks."""
if weeks < 1:
raise fromargs.CliError(f"--weeks must be at least 1, got {weeks}")
return {"name": name, "weeks": weeks, "dry_run": dry_run}
if __name__ == "__main__":
app.main()
$ python cheese_cave.py age brie --weeks 2
{
"name": "brie",
"weeks": 2,
"dry_run": false
}
See examples/cheese_cave.py for a fuller example, with a command group and
a truncated list result.
Output contract
- A handler returns data, not text. A non-
Nonereturn value prints as one JSON document on stdout, then the process exits0. - A
Nonereturn value means exit0with no stdout. - Every error is one JSON line on stderr:
{"error": <message>, "exit_code": <n>}. Raisefromargs.CliError(message)for exit code2, orfromargs.contract_error(exc, context=...)to wrap a caught exception at exit code3. An unhandled Cyclopts parse error also reports at exit code2. - A quote-split repair (below) prints one plain-text
note:line on stderr; it never changes stdout or the exit code.
Global flags
fromargs strips two flags from argv before Cyclopts ever sees them, from
anywhere before the end-of-options marker:
--jsonis a no-op. Agents that append it by habit get plain JSON either way, so the flag costs nothing and fails nothing.--fullturns off result truncation for the current call.
Neither flag reaches a handler, and neither is a real Cyclopts option.
limit
@app.command(limit=n) truncates a sequence result to its first n items,
unless the caller passes --full. Truncation prints a note: line on
stderr and never applies to a mapping or a string. App.default accepts the
same limit keyword.
App.default
@app.default (bare or called, matching @app.command) registers the
handler that runs when argv names no subcommand at that app or group level.
It is rejected at registration if it declares a json or full parameter,
the same rule @app.command enforces. Registering a second default on the
same app or group raises ValueError.
Self-healing
An agent's shell layer sometimes merges two arguments into one quoted token,
for example --weeks "2 --dry-run" instead of --weeks 2 --dry-run. When
Cyclopts rejects an argv, fromargs shell-splits each option's value once
and re-parses. It applies a split only when:
- the split has at least two pieces, and one looks like a flag; and
- the option can take a split value (not a boolean flag, not free-text
strorPath); and - exactly one split candidate among all options parses cleanly.
It prints note: split quoted argument ... into ... on stderr when it
applies a repair. fromargs refuses to guess when a split is ambiguous
(more than one candidate parses), when the option takes free text, or for
any token after the end-of-options marker (-- by default). In every
refusal case, the original parse error is reported unchanged.
Version resolution
fromargs.App(name) reports the version of the calling module, not the
version of fromargs itself. It resolves, in order:
- an explicit
version=argument, if the caller passes one; importlib.metadata.version(...)for the caller's installed distribution;- the caller module's
__version__attribute; "0.0.0", if none of the above resolve.
App.group(name, version=..., **cyclopts_kwargs) forwards every extra
keyword, including version, to the nested cyclopts.App, so a group can
report its own version independently of the root app.
Release files for fromargs 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fromargs-0.1.0.tar.gz | 37.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fromargs-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 52.1 kB
Release files / fromargs-0.1.0.tar.gz
| Download URL | fromargs-0.1.0.tar.gz |
|---|---|
| Size | 37.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1ce6aa90e3f357e552eff570d15f5a185c3655fe8eb5b17e34f4089c3206816b
|
|
BLAKE2b-256 checksum How to use checksums |
9c5ad0861938ba24d3e6b4ba51d3ef86db36d30aa32c6db71b415f66cc9a02f9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / fromargs-0.1.0-py3-none-any.whl
| Download URL | fromargs-0.1.0-py3-none-any.whl |
|---|---|
| Size | 14.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
164ab49373d9d044d16164b4b880934d12b80bef484e2c908d77accfc471dc4f
|
|
BLAKE2b-256 checksum How to use checksums |
d36aea6b15a52c719a7159a0f3e08f7dc861c4170a677258db4e1382e1e681d1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|