Appearance
App
App is the center of a Klix application. It is where you register commands, middleware, completers, and event handlers, and it is the object that starts the runtime.
See also:
Why App Exists
Without a central object, a CLI framework tends to spread behavior across globals and ad hoc registries. Klix keeps those concerns in one place:
- runtime configuration
- command registry
- lifecycle hooks
- router access
- persistence wiring
- input/output startup
That makes apps easier to reason about and easier to scaffold with klix init.
Basic Usage
python
import klix
from dataclasses import dataclass
@dataclass
class MyState(klix.SessionState):
counter: int = 0
app = klix.App(
name="mytool",
version="1.0.0",
description="A sample tool",
state_schema=MyState,
persist_session=True,
)What App Owns Internally
The current implementation stores:
_commands_middlewares_event_bus_router_custom_completers_state_manager_help_generator
You normally interact with those through decorators or public methods:
app.command(...)app.on(...)app.middlewareapp.completer(...)app.generate_help()app.run()
Registration APIs
Command registration
python
@app.command("/deploy", help="Deploy the current branch")
def deploy(session: klix.Session):
session.ui.print("Deploying...")This decorator builds a Command object under the hood. See Commands.
Event registration
python
@app.on("start")
def on_start(session: klix.Session):
session.ui.print("Ready.", color="accent")See Events.
Middleware registration
python
@app.middleware
async def log(ctx: klix.MiddlewareContext, next: klix.NextFn):
print("before")
await next(ctx)
print("after")See Middleware.
Completer registration
python
@app.completer("/deploy")
def deploy_completer(text: str, session: klix.Session) -> list[str]:
return ["prod", "staging", "dev"]See Autocomplete.
What run() Actually Does
At a high level, run():
- parses global CLI args through
CLIArgsParser - creates a session and terminal metadata
- creates the completer
- picks a renderer via compatibility detection
- attaches
session.uiandsession.input_engine - emits
start - loops on prompt input
- parses slash commands
- builds and executes middleware chain
- emits
exitand saves persistent state
That flow matters because it defines which extension points are active when.
For example:
- middleware only runs for parsed commands
inputevents happen before routing- the
commandevent is emitted during dispatch
Realistic Example
python
import time
import klix
from dataclasses import dataclass
from pydantic import BaseModel
@dataclass
class DeployState(klix.SessionState):
authenticated: bool = False
class DeployArgs(BaseModel):
env: str
force: bool = False
app = klix.App(name="deployctl", state_schema=DeployState)
@app.on("start")
def on_start(session: klix.Session):
session.ui.layout.header.set("Deploy Control", color="accent")
session.ui.layout.status.set("Ready", "Use /help", color="muted")
session.ui.layout.redraw_ui()
@app.middleware
async def timing(ctx: klix.MiddlewareContext, next: klix.NextFn):
start = time.time()
await next(ctx)
ctx.session.ui.print(f"{time.time() - start:.2f}s", color="muted", dim=True)
@app.command("/deploy", args_schema=DeployArgs, help="Deploy an environment")
def deploy(args: DeployArgs, session: klix.Session):
session.ui.output.panel(
f"Deploying to {args.env} (force={args.force})",
title="Deployment",
border_color="accent",
)
if __name__ == "__main__":
app.run()Pitfalls
Confusing App registration time with runtime
Decorators register behavior when the module is imported. They do not run during command dispatch.
Expecting non-command text to dispatch automatically
The default App.run() loop assumes command-style input. If you want free-form chat text, build a custom loop around the same primitives. See Gemini-Style CLI.
Treating private attributes as stable API
The implementation exposes enough internals to support advanced examples, but _commands, _router, and similar members should be treated as implementation details unless the public surface is insufficient for your use case.