Build an app for the Omniscient Master Suite
An app is a folder with a manifest that says what it can do and a Python file that does it. Everything else comes for free: the manifest and run_action drive the app's own window, its command line, the MCP tools AI assistants call, and the workflow editor's nodes, so a human at the keyboard, a script and an AI all run exactly the same code.
Folder layout
my-app/
manifest.json what the app is, what it can do, what it may touch
app.py the App subclass (plus --action CLI, optional GUI)
icon.svg tile icon (no scripts)
core.py your own code (optional)manifest.json
{
"id": "image-cropper",
"name": "Image Cropper",
"version": "1.0.0",
"description": "Batch-crop and resize images with previews.",
"category": "images",
"icon": "icon.svg",
"entry": {"gui": "app.py", "headless": "app.py"},
"actions": [
{"name": "crop_folder",
"label": "Crop a folder",
"description": "Crop every image in a folder to a target size.",
"inputs": {"folder": {"type": "path"},
"size": {"type": "string", "default": "1024x1024"},
"mode": {"type": "enum", "values": ["fit", "fill"], "default": "fit"}},
"outputs": {"files": {"type": "path[]"}},
"destructive": false}
],
"permissions": {"filesystem": "ask", "network": false, "processes": false},
"requires": {"python": ">=3.10", "packages": ["Pillow"], "models": []},
"ui": {"theme": "omniscient", "override": false},
"ai": {"mcp": true, "example_prompts": ["crop everything in folder X to 1024x1024"]}
}Rules the loader enforces (an app that breaks them is not listed):
idandnameare required.id: lowercase letters, digits and hyphens, 2-64 characters, starting with a letter.versionis semver (1.0or1.0.0).categoryis one of:images,media,social,web,ai,files,data,system,demos,other. Defaultother.- Every action needs a unique
name.labelis optional (defaults to the name with spaces).inputs/outputsmap a field name to{"type": ...}where the type is one of:bool,choice,enum,float,int,json,path,path[],profile,secret,string,text. - An input is required unless it has a
defaultor"optional": true.enum/choiceinputs list their allowedvalues. destructive: truemarks an action that changes or deletes things; those are the ones that honour--dry-run.permissionsvalues aretrue,false,"ask","yes"or"no"(filesystemmay also be a list of folders). The site shows the ones that are on.requiresdocuments the Python version, pip packages, local models and external binaries (for exampleffmpeg) the app needs.ai.mcp(default true) declares that the app is meant to be driven by AI assistants;ai.example_promptsare shown on the app page as things to ask.ui.override: truemarks an app whose window deliberately departs from the standard suite look (every other app inherits it fromomni_kit).
app.py
from omni_kit.sdk import ActionContext, App
class Cropper(App):
def setup(self) -> None:
# one handler per action declared in the manifest
self.register("crop_folder", self.crop_folder)
def crop_folder(self, ctx: ActionContext) -> dict:
folder = ctx.params["folder"] # already validated, coerced, defaults filled
ctx.log(f"cropping {folder}") # shows up in the envelope's "logs"
if ctx.dry_run: # only True for destructive actions
return {"files": []} # report the plan, change nothing
return {"files": [...]} # anything JSON-serialisable
if __name__ == "__main__":
raise SystemExit(Cropper().main())The handler contract (run_action):
- Registering an action that is not in the manifest is an error at start-up.
- Before your handler runs,
paramsare checked against the manifest: missing required inputs and unknown inputs are rejected, defaults are filled in, and values are coerced (int,float,boolaccepts1/true/yes/on,enummust be one of itsvalues,path[]accepts a single string). - The handler receives an
ActionContext:ctx.params,ctx.log(msg),ctx.dry_run,ctx.secret(key)(from the encrypted vault, if unlocked). The app also offersself.settings(per-app settings) andself.data_dir(per-app data folder). - Whatever the handler returns becomes the
result. An exception becomes an error envelope; it never takes the host down.
One envelope for every surface
run_action never raises. It always returns one JSON object:
{"ok": true, "action": "crop_folder", "dry_run": false, "result": {"files": []}, "logs": []}
{"ok": false, "action": "crop_folder", "error": "folder not found: D:/x", "logs": []}An unknown action also returns available (the action names); an unexpected exception also carries trace.
The command line
App.main() gives every app the same CLI:
python app.py --list # actions, required inputs, [destructive]
python app.py --manifest # the manifest as JSON
python app.py --action crop_folder --params "{\"folder\": \"D:/renders\"}" --json
python app.py --action crop_folder --params "..." --dry-run # destructive actions only
python app.py # no --action: open the app's window--json prints the envelope; without it, logs and the result are printed in plain text. Exit codes: 0 ok, 1 the action failed, 2 bad usage (invalid --params JSON, or no window available).
Checklist
manifest.jsonloads (omni_kit.manifest.validate_file(path)returns(True, "OK ...")).python app.py --listshows every action, and each one is registered.- Each action works from the CLI with
--jsonbefore you build any GUI on top. - Destructive actions support
--dry-runand say what they would do. - Originals are never overwritten unless the action is marked destructive.