Extension CLI Entry xune¶
Besides attaching tools, commands, and hooks to an agent, an extension can declare a command line of its own and have xune run it directly.
This fits operational work that has nothing to do with the agent but everything to do with extension data: reports, data backfills, budget settings.
xune # print usage and list extensions that declare a CLI
xune <extension> [args…] # run that extension's CLI
xune <extension> -h # that extension's own --help
xune stat show --days 7
xune stat budget set --day 500 --user alice
Arguments and exit codes¶
xune has a single positional argument — the extension name. Every other argument is handed verbatim to the extension's own parser.
| Case | Output | Exit code |
|---|---|---|
| no arguments | usage + list of extensions with a CLI | 2 |
-h / --help |
same as above | 0 |
| extension not loaded | unknown extension '<name>' — loaded: … |
2 |
| extension declares no CLI | extension '<name>' declares no CLI |
2 |
| normal execution | decided by the extension | the handler's int return value is the exit code, otherwise 0 |
How it finds the extension¶
xune reuses the core extension loader: it scans $XUN_HOME/extensions/ and imports the extension modules,
then reads the parser and handler declared by @extension_attr(cli=(parser, handler)):
flowchart LR
A["xune <name> args"] --> B["Scan and import<br/>$XUN_HOME/extensions"]
B --> C{"Does this extension declare cli?"}
C -- no --> X["Print a hint and exit 2"]
C -- yes --> D["parser.parse_args(args)"]
D --> E["handler(namespace)"]
E --> F["Return value is the exit code"]
Two points are worth noting:
- Import only, no setup.
xunenever runssetup_extension(ctx), so nothing is attached to any agent; resources that need initialization (creating a database, creating directories) must be handled inside the CLI handler itself. - Same data directory rules. Because the core process is the one importing the extension,
extension_data_dir()resolves to the same path an agent sees, so no extraPYTHONPATHor module search path is required.
How to declare it¶
cli is a two-tuple (argparse.ArgumentParser, handler), and the handler receives the parsed Namespace:
import argparse
from xun import ExtensionContext, extension_attr
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
parser.add_argument("--days", type=int, default=7)
return parser
def run(args) -> int:
print(f"last {args.days} days")
return 0
@extension_attr(api_min_version="1.4.0", data_version="1", cli=(build_parser(), run))
def setup_extension(ctx: ExtensionContext) -> None:
...
The parser's prog is rewritten to xune <name>, so --help shows exactly the command users type.
The built-in stat extension uses this mechanism to provide its report and budget commands (xune stat show|budget …);
see Extension Attributes.