DEV Community

fahh3344
fahh3344

Posted on

Is an IDE setup like a Video Game Menu a way to get more people coding?

pixil-frame-0 (12)

Pylerium local operations

Open-source on GitHub: Pylerium
Guides & Previews

· Detailed authoring guides: Workshop and terminal

· Plugin builder and API

· Windows executable

Requires Python 3.10+; install the complete runtime with python -m pip install -r requirements.txt. AI assistance is optional. Models load asynchronously and use the current textured renderer, with software fallback where GPU rendering is unavailable. Native Qt OpenGL is an optional path (PYLERIUM_NATIVE_GL=1).

python loadout-menu.py          # Original loadout menu with asset support
python orchestration-menu.py    # Separate local runner / orchestration workspace
python test_orchestration.py    # Offscreen integration checks
Enter fullscreen mode Exit fullscreen mode

The runner is a copy of the asset-enabled menu with a separate functional interface. It reuses the original background, typography, selection cards, wireframe canvas, and preview panel. Its lobby, loadouts, operators, maps, missions, arsenal, workshop, console, barracks, assets, settings, and plugins pages use the BO6-inspired theme. It is not a pixel-identical recreation of every game screen and includes no proprietary game assets.

Assets

assets/manifest.json maps item names, stable project:<id> keys and loadout:<project-id>:<slot> keys to files beneath assets/. Missing images/models fall back to the existing procedural artwork. Example assets are original SVG drawings and a simple OBJ inspection prop.

{
  "background": "backgrounds/lobby.svg",
  "background_opacity": 0.55,
  "items": {
    "XM4": {
      "icon": "icons/receiver.svg",
      "model": "models/receiver.obj",
      "rotation": [0, 0, 0]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Icons fit within the card's image zone, preserving aspect ratio and leaving labels clear. ASSETS.icon() returns a centered, transparent, DPI-aware QIcon. Backgrounds fill their area with cropping; the original gradient, vignette, orange lighting, and animated atmospheric overlays remain above them.

OBJ models support vertices, polygon faces, negative indices, UV coordinates, and line segments. They are centered and fitted automatically; rotation contains X/Y/Z angles in degrees. Keep the OBJ, referenced .mtl files, and texture subfolders together when importing: the importer copies referenced dependencies while preserving their relative paths. References must resolve inside the source model folder; missing or external resources are reported. Existing models imported by the older single-file importer must be re-imported from their original folder to recover omitted materials/textures.

Models and textures load on background workers rather than inside card paint events. Import OBJ, GLB, glTF, STL, PLY and OFF. Keep external material/texture dependencies alongside their source model when importing. Static geometry is supported; skeletal animation is not evaluated. GPU shading supports discovered materials and maps; software fallback preserves textured model inspection. Missing resources and unsupported content show an explanation.

The Assets studio selects project and execution-slot assignments, filters targets, previews images and models, adjusts rotation, assigns texture overrides and removes artwork. Use Reload Asset Manifest after external changes. Each loadout card shows static image/model artwork; its inspection panel supports orbit, zoom, fit, materials/wireframe and expanded inspection.

The runner's Assets page imports files and updates the manifest. From Python:

from asset_helper import ASSETS
icon = ASSETS.import_file(r"C:\art\tool.png", "icon")
model = ASSETS.import_file(r"C:\art\tool.obj", "model")
ASSETS.set_item("project:<saved-project-id>", icon=icon, model=model, rotation=[0, 90, 0])
texture = ASSETS.import_file(r"C:\art\diffuse.png", "texture")
ASSETS.set_item("project:<saved-project-id>", texture=texture)  # optional override; MTL maps are automatic
background = ASSETS.import_file(r"C:\art\lobby.jpg", "background")
ASSETS.set_background(background, opacity=0.6)
Enter fullscreen mode Exit fullscreen mode

loadout-menu.assets-backup.py supplies the execution screen’s original card template. The project-aware screen, runtime and artwork integration live in project_loadout_ui.py, project_loadout.py, loadout_runtime.py and loadout_artwork.py. Local backup snapshots are ignored by Git.

Navigation and execution

  • Lobby: select a saved project and execution loadout, deploy, inspect activity, or stop all operations. image
  • Loadouts: save interpreter paths, environment JSON, and timeouts. Zero disables the timeout. Edit shared JSON values available to every operator. image
  • Operators: save callsigns paired with projects and execution loadouts. Deploy them independently. image
  • Maps: edit, visualize, validate, and deploy dependency graphs. Independent nodes run concurrently up to the configured limit. Failed/cancelled dependencies skip downstream nodes. Only one map runs at a time; manual projects can also occupy execution slots. image
  • Missions: schedule saved maps at recurring intervals while the app is open. Busy runners defer a mission; missed intervals are not replayed. Stop-all disables recurring missions. image
  • Arsenal: open a project execution loadout, import a Python entry script, or export/import bundles. Right-click projects for editor/run actions, renaming, duplication, opening the project folder, copying its path and assigning artwork to individual slots. image image
  • Workshop: create tools from templates, edit highlighted Python with line numbers and automatic indentation, set JSON arguments and working directories, save, and run. Imported scripts are copied; adjacent modules/resources are not copied automatically. Use a working directory containing required resources and imports. image
  • Console: render ANSI colors, tables, gradients, and cursor-based progress output. Select an isolated run or a grouped view of all runs, freeze the display, customize fonts/output themes/styles, stop selected jobs, and export readable output. image
  • Barracks: inspect persisted run status, duration, exit codes, and logs. image
  • Assets/Settings: customize artwork, concurrent process limits, motion, and optional Ollama assistance. image image
  • Plugins: rescan local extensions, explicitly enable/disable them, or create a scaffold. The bundled Output Tools example starts disabled. image

Dynamic assignment and workflow builder

Project, loadout, operator and mission references are selected from saved workspace entities. The Assets picker lists projects and existing items; project artwork is stored against a stable project ID, so a rename retains the assignment. Older name-based project artwork is upgraded automatically while retaining its original item entry.

In Maps, use Add Node to select a project, loadout, optional operator and prerequisite checkboxes. A unique step label is generated automatically. The step table shows project/loadout names and supports edit/remove actions; JSON remains available for advanced editing. Fix Duplicate Labels repairs repeated labels without deleting steps. Dependencies referring to an old duplicate label remain attached to its first occurrence; review that choice before saving. Removing a step also removes references to it from downstream prerequisites.

In-app plugin builder

image

Both the Workshop and plugin-builder editors have a small Focus editor button. It moves the same editor into a nearly full-screen themed card; its document, cursor, selection, and undo history remain intact. Use Return to Menu or Escape to restore it. Save works in the card, with Ctrl+S as a shortcut.

Python suggestions appear after a short pause when typing an identifier, or after a recognized member prefix such as term. or ctx.. Suggestions include keywords, built-ins, names found in the current document, and selected SDK/standard-library member hints. Tab or a click accepts a suggestion; Escape dismisses it and Ctrl+J requests one. Suggestions are suppressed inside comments/strings and can be disabled in the focus-card header. This is local lightweight completion, without executing imports, contacting AI, or requiring a language server; it is not full VS Code/Pylance type inference.

The Plugins tab includes a full Python editor with metadata, file selection, companion module creation, syntax validation, saving, explicit enabling/reloading, and disabling. Double-click an existing plugin (or use Edit Selected) to modify it. New plugins can start from custom extension, UI page, project launcher, shared-data tool, execution hook, output style/template, or script-command examples. These templates are starting points; you can replace their code with any plugin supported by the Python host API.

A display name automatically supplies the plugin ID when creating a new plugin. Existing IDs stay fixed. Validate checks all Python modules and the entry file's register(ctx) function without running the plugin. Save writes its metadata/code and leaves activation unchanged. Save & Enable / Reload explicitly executes registration and updates workspace contributions. Registration failures remove partially added contributions, leave the plugin disabled, and show the error. Unsaved plugin changes are checked before switching plugins or closing the app.

Plugins can use ctx.selector('project'), ctx.selector('profile'), ctx.selector('operator'), or ctx.selector('workflow') to get a live QComboBox of saved entities. currentData() returns the stable ID and currentText() displays the name. These selectors refresh after workspace changes, so a project-launcher plugin never needs hardcoded project names or IDs. Custom code continues to have normal application permissions; slow plugin callbacks should use background workers.

F11 toggles fullscreen; Escape exits fullscreen first, otherwise returns to the lobby. Drag the preview to orbit, scroll to zoom, and double-click to reset.

Maps use this format (IDs are shown in the JSON editor; the demo is preconfigured):

{
  "nodes": [
    {"id": "prepare", "project": "hello", "profile": "default", "operator": "local", "depends_on": []},
    {"id": "build", "project": "hello", "profile": "default", "operator": "local", "depends_on": ["prepare"]}
  ]
}
Enter fullscreen mode Exit fullscreen mode

Each node chooses its own project/profile; operator supplies its execution callsign. The separately saved operator pairing is used when deploying from the Operators page.

Project loadout and runtime policies

Loadouts are saved per project as JSON in the local document store. The primary entry point accepts a base argument array plus any named attachment arrays. Secondary processes may run before execution or alongside it; companions stop when the primary pipeline ends. Context loads a selected environment file and explicit variable overrides.

Slot Runtime behavior
Primary Entry point, CLI arguments and editor access
Secondary Pre-execution helpers or companion processes
Tactical cProfile, debug logging or strict warnings
Output / lethal JSON execution report or configured post-run script
Field upgrade Environment file and variable overrides
Perks and specialty Assignable policy toggles
Wildcard Worker/device hints or multiple execution passes

Run Project executes Python; it never invokes built-in EXE packaging. Use Workshop → Build EXE explicitly. Old pyinstaller/build_compiler output hook selections normalize to none.

  • Ghost: use a project .venv with the installed SDK dependencies available through system site packages. Existing environments are updated to keep those dependencies visible.
  • Scavenger: install requirements.txt when present, otherwise continue with available dependencies.
  • Juggernaut: request Windows administrator access, wait for the elevated project, stream its output and preserve its exit code. Declining elevation fails the run.
  • Ninja: hide console windows while retaining captured project output.
  • Sleight of Hand: run Ruff formatting before execution; Ruff must be available in the selected interpreter.
  • Hardline: continue after optional helper-hook failures; primary project failures still fail the run.

Overkill and Danger Close provide CPU worker and CUDA-device hints. Project libraries must support and use these settings; selecting them does not automatically parallelize code or install GPU drivers. Repeat runs the configured number of passes and stops on primary failure.

Shared data and persistence

image

orchestration_data/shared.sqlite3 stores projects, profiles, operators, maps, missions, settings, shared JSON values, workflow results, and run logs. Code lives under orchestration_data/projects/. SQLite WAL and atomic updates allow concurrent workers to share state without losing increments.

from shared_loadout import get, set_value, update, all_values

set_value("build_result", {"ok": True})
update("completed", lambda old: (old or 0) + 1)
print(get("build_result"))
Enter fullscreen mode Exit fullscreen mode

Deployed scripts receive ORCHESTRATOR_SHARED_DB, ORCHESTRATOR_RUN_ID, ORCHESTRATOR_OPERATOR, and ORCHESTRATOR_PROJECT_ID. The shared-state SDK is automatically added to their Python import path. This is local sharing within this workspace, not cross-machine synchronization.

Bundles include entry scripts, execution profiles, operators, maps, and shared values. They exclude dependencies, adjacent project files, artwork, run logs, schedules, and AI settings. Imports allocate new IDs, remap references, and preserve existing shared keys. Interpreter paths may need updating on another computer. Profile environment variables are included in exports.

Each run retains the most recent 2 MB of output; the live console retains 10,000 text blocks. Scripts execute with normal account permissions, not in a security sandbox. Stop and timeout controls kill the directly launched Python process; descendants spawned by a script require their own cleanup. The UI must remain open for missions to run.

Optional Ollama

imageimage

AI is off by default. Enable it in Settings and enter a dynamic HTTP(S) endpoint. The downloaded-model selector loads from GET /api/tags; choose a model and save. Discovery is asynchronous with a 10-second timeout and never downloads models. Re-enable assistance or use Refresh Downloaded Models after changing servers/models.

The Workshop sends your prompt to Ollama's POST /api/generate endpoint only when you click Generate Draft. Requests are asynchronous and have a 120-second transfer timeout. Python Markdown fences (including spaced backticks), response prose outside fences, standalone language tags, and reasoning tags are removed. Valid Python strings containing backticks are preserved. Drafts can be corrected in the draft editor; invalid Python is not inserted. Drafts must be applied, reviewed, saved, and explicitly run; no model automatically executes code. The runner works without Ollama installed.

Ultimate Terminal in Workshop scripts

The runner makes ultimate_terminal importable and forces color-enabled UTF-8 output for launched scripts. Existing terminal APIs remain available, including tables, panels, banners, spinners, live progress, and custom styles.

from ultimate_terminal import terminal as term, Style, RGB

term.success('Connected')
term.print(term.gradient('PYLERIUM'))  # gradient() returns styled text
term.table([['Build', 'OK']], headers=['Stage', 'Result'])
term.add_style('important', Style(foreground=RGB.from_hex('#ff7b1c'), bold=True))
term.print('Important result', style='important')
term.progress(100, label='Build', newline=False)
print()
Enter fullscreen mode Exit fullscreen mode

Use Styled terminal output when creating a project, or Insert Terminal Example in the Workshop. Console themes and style JSON apply to new runs; scripts can also call term.set_theme() and term.add_style() directly. For example, save this in the Console's Custom Styles field:

{"important": {"foreground": "#ff7b1c", "background": "#172029", "bold": true}}
Enter fullscreen mode Exit fullscreen mode

Supported styles include foreground/background RGB colors, bold, italic, underline, and strike. The Console renders standard/bright/256-color/truecolor SGR sequences, carriage returns, line erasure, basic cursor movement, and save/restore controls. It is an output display, not a full interactive shell or VT emulator: unsupported terminal modes/controls are ignored and it has no stdin entry. Each run has independent cursor/style state so concurrent spinners cannot erase another run's output. A display stream retains at most 10,000 blocks/1,000,000 characters; up to 64 streams are displayed. Raw ANSI remains in the persisted run logs for replay; Console exports contain readable text. Freeze View pauses redraw while output continues to be collected.

Local plugin API (version 1)

Create a folder under plugins/ with these files:

{"id":"my_tools","name":"My Tools","api_version":1,"entry":"plugin.py","description":"Workspace extensions"}
Enter fullscreen mode Exit fullscreen mode
def summarize(values):
    return {"count": len(values)}

SCRIPT_COMMANDS = {"summarize": summarize}

def register(ctx):
    ctx.add_action('Show summary', lambda: ctx.log(summarize(ctx.store.shared())))
    ctx.add_template('Starter', "print('Hello plugin')\n")
    ctx.add_style('highlight', foreground='#ff7b1c', bold=True)
    ctx.on('run_finished', lambda run_id, status: ctx.log(f'{run_id}: {status}'))

def unregister(ctx):
    # Stop timers / disconnect your own signals here, if needed.
    pass
Enter fullscreen mode Exit fullscreen mode

ctx.add_page(name, QWidget) adds a functional navigation page. ctx.window, ctx.store, and ctx.runner expose the workspace, persistence, and runner. ctx.on('output', callback) receives (run_id, raw_text); run_finished receives (run_id, status). Hook failures are isolated and logged. Hooks run on the Qt UI thread: keep them short. The host removes registered pages/actions/templates/styles/hooks on disable; plugins must clean up any extra resources they create. Use package-relative imports such as from .helpers import tool for companion modules.

Workshop scripts can call explicitly enabled plugin commands:

from workspace_plugins import call, available
from shared_loadout import all_values
print(available())
print(call('output_tools', 'summarize', all_values()))
Enter fullscreen mode Exit fullscreen mode

Enabled plugins run with normal application permissions. Discovery reads manifests without importing code. Fresh plugins stay disabled; previously enabled plugins load on the next launch. Running scripts receive a snapshot of enabled plugin IDs. Import-safe plugins should create Qt widgets inside register() rather than at module import so their commands can also run in child Python processes. Plugin code and enablement are not included in project bundles.

Run both suites to verify the extensions and existing orchestration:

python -m unittest discover -s . -p "test_*.py" -v
Enter fullscreen mode Exit fullscreen mode

Ollama tests use a local mock HTTP server and cover discovery, generation, and cleaned insertion; they do not require a downloaded model or measure real model performance.

Preview compatibility and editor templates

Model previews use the bounded textured software renderer by default to avoid Windows native OpenGL surface recreation interfering with menus and fullscreen controls. Textures, orbit, zoom and fit remain available; the software preview samples geometry on large models. Native GPU rendering is optional via PYLERIUM_NATIVE_GL=1 before launch. Animation pauses while a popup or modal dialog is open. F11 toggles fullscreen from child controls; Escape exits fullscreen before returning to Lobby.

Workshop offers 15 editable boilerplates (CLI, shared state, terminal output, JSON, CSV, HTTP, logging, asyncio, threads, dataclasses, SQLite, subprocesses, file discovery and error handling) plus enabled plugin templates. Insert adds code after the current line in one undo step and never runs it. Plugin Builder offers its template dropdown and New from template both in the menu and the focus card, with the existing unsaved-changes prompt. The focus card uses the same dark/orange theme and retains the original editor and toolbar.

Top comments (2)

Collapse
 
baumgaerben profile image
baumgaerben •

Thú vị khi so sánh IDE setup với video game menu — cái cách game design onboarding player (tutorial level, progressive unlock, visual feedback tức thì) chính là thứ IDE truyền thống thiếu hụt nhất.

Tôi thấyVS Code extension pack hay JetBrains "welcome screen" gần đây đã bắt đầu học cách này: gợi ý config theo stack, highlight shortcut quan trọng, cho chạy hello-world trong 1 click. Nhưng còn thiếu deep integration: ví dụ "quest" refactor thực tế trên codebase của chính dev, chứ không phải dummy file.

Pylerium hướng local-first là điểm mạnh — không ép dev qua cloud, không telemetry ngầm. Điều đó xây dựng trust ngay từ đầu, quan trọng với junior dev đang ngại "big brother watching".

Một góc nhìn còn mở: có nên để team tự build "menu" riêng cho codebase internal không? Giống modding community — shared snippets, custom debug views, convention enforcement qua UI thay vì lint rule khô khan. Đã thấy vài team tự viết VS Code extension nội bộ để wrap workflow đặc thù, hiệu quả bất ngờ.

Curious: Pylerium có plan cho plugin API cho team tự extend "menu" của họ không? — found it via LabAgent, site: labagent .tech

Collapse
 
donnnnn14 profile image
fahh3344 •

Plugin builder and API guide

Plugins extend the desktop app with pages, actions, project templates, terminal styles, execution hooks and commands callable from Workshop scripts. See WORKSHOP.md for editing, running scripts and full terminal usage; OPERATIONS.md covers the surrounding workspace.

The included collection adds 15 opt-in starter plugins with real analysis pages, script commands and advanced Workshop projects. Search Plugins by name, ID or description. Start with Getting Started. See the collection guide for tool capabilities, limits and complete examples.

Build a plugin in the app

  1. Open Plugins and select a template in the builder dropdown.
  2. Click New from Template. Give the extension a display name and description. An empty ID is generated from the display name when validated/saved.
  3. Edit plugin.py; use Add File for companion .py modules and the file dropdown to switch between them.
  4. Use Focus editor for a large themed card. Its template dropdown and New from template action remain available. Save/Ctrl+S saves the plugin; returning preserves editor state.
  5. Click Validate, then Save Plugin. A new plugin remains disabled.
  6. Click Save & Enable / Reload to execute registration and add its features to the workspace.
  7. Test its actions, pages and Workshop commands. Use Disable to remove contributions without deleting its saved files.

To edit an existing extension, select it and use Edit Selected, or double-click its entry. Unsaved changes prompt for Save, Discard or Cancel before replacing the current plugin. Existing IDs are fixed. If a new plugin ID already exists, saving offers to update the existing files, create a numbered copy, or cancel. Opening the existing plugin is also available for editing.

Builder templates

Template Includes
Custom extension Basic action and exported echo command
Custom UI page QWidget page registered into navigation
Project launcher Dynamic project/loadout selectors and launch button
Shared data tool Workspace shared-data inspection and child command
Execution hook Run-completion notification
Output style and template Named output style and Workshop starter
Script commands Callable transformation exported to scripts

These are editable starting points. The API is a general Python extension mechanism; arbitrary functionality comes from your code, imports and UI design. The builder does not install dependencies or generate a complete extension automatically.

Files and manifest

Plugins are discovered from immediate subfolders of plugins/:

plugins/my_tools/
    plugin.json
    plugin.py
    helpers.py
Enter fullscreen mode Exit fullscreen mode
{
  "id": "my_tools",
  "name": "My Tools",
  "description": "Reports and reusable commands",
  "api_version": 1,
  "entry": "plugin.py"
}
Enter fullscreen mode Exit fullscreen mode

IDs must use letters, digits, underscores or hyphens and be unique. API version must be 1. The entry path must resolve to an existing file inside the plugin folder; it defaults to plugin.py when omitted. Use Rescan after creating/editing manifests outside the app. Discovery reads metadata without importing plugin code.

The builder edits Python files; manage images, datasets, dependency specifications and other resource files externally. Add File accepts a unique simple Python filename such as helpers.py. Companion imports use package-relative syntax:

from .helpers import summarize
Enter fullscreen mode Exit fullscreen mode

For plugin-owned resource paths, derive them from Path(__file__).parent, not the app's current working directory.

Lifecycle and validation

The entry module must define a top-level synchronous register(ctx) function. Optional unregister(ctx) releases resources when disabling/reloading.

Operation Behavior
Rescan/discovery Reads manifests; does not execute plugin modules
Validate Parses all builder Python files and checks for register; does not import or run them
Save Plugin Writes manifest/modules; leaves current activation unchanged
Enable Imports the module and calls register(ctx) in the app process
Save & Enable / Reload Saves, disables the old instance, then registers the new version
Disable Calls optional unregister, then removes host-managed contributions
Next app launch Reloads previously enabled plugins

Syntax validation cannot prove imports, callback signatures or runtime behavior. If registration fails, the host removes its partially registered contributions and leaves the plugin disabled. A failed reload does not restore the previous instance. Saving code alone does not update the already loaded UI module.

The host removes pages, actions, templates, styles and hooks registered through ctx. Your cleanup must stop your timers/workers and disconnect extra signals you connected directly. Plugins run in the application process with normal account permissions. Keep registration, UI callbacks and hooks short; use background work for slow tasks and Qt signals to deliver results to widgets on the UI thread.

Context API reference

ctx.api_version is 1; ctx.descriptor contains discovered manifest metadata including folder/entry paths. ctx.window, ctx.store and ctx.runner expose the app, persistence and runner. These are low-level implementation objects; use the small registration API where possible and inspect the source before relying on other methods.

API Contract
ctx.log(text) Appends text plus a newline to the plugin's Console stream
ctx.add_page(name, widget) Adds a QWidget and navigation button; page title includes plugin ID
ctx.add_action(name, callback) Adds a plugin action; callback is invoked without arguments
ctx.add_template(name, code) Adds a string-valued project template named plugin_id // name
ctx.add_style(name, **style) Adds a validated terminal style named plugin_id.name
ctx.on(event, callback) Subscribes to output or run_finished
ctx.selector(kind) Returns a live QComboBox for a saved entity kind

Hook signatures:

def register(ctx):
    ctx.on("output", lambda run_id, raw_text: None)
    ctx.on("run_finished", lambda run_id, status: ctx.log(f"{run_id}: {status}"))
Enter fullscreen mode Exit fullscreen mode

Output is raw text and can include ANSI or partial lines. run_finished supplies the stored status string; it does not supply the exit code as a third argument. Callbacks run on the Qt UI thread. Host action/hook exceptions are caught and written to the plugin-errors Console stream. Avoid logging every output chunk back into output hooks; the host suppresses recursive emissions of the same event, but excessive output still adds noise.

Dynamic entity selectors

Supported kinds are project, profile, operator and workflow. A profile is an execution loadout and a workflow is a map. currentText() is a display name; currentData() is the stable ID used for assignment. Selectors refresh when workspace entities change, preserving valid selection where possible. Check for an empty selection before launching.

def register(ctx):
    from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton

    page = QWidget()
    layout = QVBoxLayout(page)
    project = ctx.selector("project")
    profile = ctx.selector("profile")
    launch = QPushButton("Run selected project")

    def run_selected():
        if not project.currentData() or not profile.currentData():
            ctx.log("Create/select a project and execution loadout first")
            return
        ctx.window.safe(lambda: ctx.window.launch(project.currentData(), profile.currentData()))

    launch.clicked.connect(run_selected)
    layout.addWidget(project)
    layout.addWidget(profile)
    layout.addWidget(launch)
    ctx.add_page("Launcher", page)
Enter fullscreen mode Exit fullscreen mode

Pages inherit the app's styling unless overridden. Create widgets inside register, not at module import. A plugin's command module may also be imported in a runner process where there is no QApplication.

Complete example: reporting extension

Create a plugin with ID my_tools and the following files. It provides an action, a Workshop template, an output style and a reusable script command.

helpers.py:

def summarize(values):
    return {"count": len(values), "keys": sorted(str(key) for key in values)}
Enter fullscreen mode Exit fullscreen mode

plugin.py:

from .helpers import summarize

SCRIPT_COMMANDS = {"summarize": summarize}

def register(ctx):
    ctx.add_action("Inspect shared keys", lambda: ctx.log(summarize(ctx.store.shared())))
    ctx.add_style("highlight", foreground="#ff7b1c", bold=True)
    ctx.add_template("Shared report", '''from ultimate_terminal import terminal as term
from shared_loadout import all_values
from workspace_plugins import call

report = call("my_tools", "summarize", all_values())
term.print("Shared state report", style="my_tools.highlight")
term.table([["Keys", report["count"]]], headers=["Metric", "Value"])
term.panel("\\n".join(report["keys"]) or "No shared values", title="Keys", width=60)
''')
    ctx.on("run_finished", lambda run_id, status: ctx.log(f"Finished {run_id}: {status}"))

def unregister(ctx):
    # Release resources you create beyond the host-managed contributions.
    pass
Enter fullscreen mode Exit fullscreen mode

Save and enable the plugin. Click Inspect shared keys and check Console. In Workshop, choose my_tools // Shared report as a new-project template, or insert it using the editor toolbar. Save and run the project. You should see the plugin's orange heading, a table and a panel of shared keys, followed by a completion message in the plugin stream. No model or AI service is required.

Commands called by Workshop scripts

Export a module-level dictionary of callable values:

def transform(value, *, prefix=""):
    return prefix + str(value).upper()

SCRIPT_COMMANDS = {"transform": transform}
Enter fullscreen mode Exit fullscreen mode

Call it from a saved runner script:

from workspace_plugins import available, call

print(available())
result = call("my_tools", "transform", "ready", prefix="Status: ")
print(result)
Enter fullscreen mode Exit fullscreen mode

This second example requires adding transform to the example plugin's SCRIPT_COMMANDS. Commands execute inside the calling runner process. They are regular Python functions: arguments and results do not cross a remote transport, and exceptions propagate to the caller. They do not receive ctx automatically. UI register is not called in that process, so commands cannot depend on globals initialized only during registration.

A run receives a snapshot of enabled IDs, but loads command source on first call rather than receiving a frozen code bundle. Each runner process caches modules afterward. Start a fresh run after updating a plugin. Disabling a plugin affects future run snapshots; it does not revoke commands in an existing run.

Terminal output from plugins

For app actions/hooks, use ctx.log. It routes output to the Console; ordinary print in UI callbacks goes to the app's launching terminal and is not automatically routed. To log colored text, format a string and pass it to the context:

def register(ctx):
    from ultimate_terminal import Terminal
    output = Terminal(enabled=True, theme="fire", force_truecolor=True)
    ctx.add_action("Ready message", lambda: ctx.log(output.paint("Plugin ready", "success")))
Enter fullscreen mode Exit fullscreen mode

For commands invoked by Workshop scripts, normal print and term output are captured by the runner:

def report(message):
    from ultimate_terminal import terminal as term
    term.info(message)
    return {"reported": True}

SCRIPT_COMMANDS = {"report": report}
Enter fullscreen mode Exit fullscreen mode

ctx.add_style("highlight", foreground="#ff7b1c", bold=True) makes my_tools.highlight available to newly launched scripts while the plugin is enabled. It accepts the same validated style fields as Console Custom Styles: foreground/background hex colors, bold, dim, italic, underline, blink, reverse and strike. Visual support depends on the output display. Already printed text and already running script instances do not change when styles are edited or disabled.

Use tables/panels for final results and live progress for a single evolving task. Blocking terminal animations belong in runner scripts; calling them in the plugin UI thread freezes the interface. See WORKSHOP.md for examples and the full output behavior.

Shared data and persistence

UI plugins can inspect ctx.store.shared(); low-level Store methods are defined in runner_core.py. Commands running under the runner can use shared_loadout.get, set_value, update and all_values. The runner supplies the database environment; the app UI context should use its Store rather than assuming runner-only environment variables are present.

Plugin enablement is saved in workspace settings. Plugin source stays in the plugins directory and is not included in project export bundles. To distribute an extension, copy the whole plugin folder, include its companion files/resources and document dependencies. Install UI dependencies into the app interpreter and command dependencies into every execution-loadout interpreter that uses them. Rescan and explicitly enable it on the destination workspace.

There is no manifest-driven dependency installer, remote plugin registry, permission isolation, plugin settings schema or unloadable process sandbox. Custom settings pages/configuration can be implemented as plugin-owned features.

Troubleshooting and verification

Symptom Check
Plugin missing Put plugin.json in an immediate plugin subfolder; Rescan
Invalid manifest Check JSON, unique ID, API version and entry path
Validation fails Inspect every companion module and the top-level register(ctx) definition
Validation passes, enable fails Inspect runtime imports, dependencies and registration error
Saved edits do not affect UI Use Save & Enable / Reload
Script says plugin is not enabled Enable before launching a new run
Unknown command Export it in SCRIPT_COMMANDS; command names are separate from action names
Widgets fail in runner Move widget creation inside register; make command imports UI-independent
Dropdown has no entities Save the relevant project/loadout/operator/map first
UI freezes Move slow work out of UI callbacks/hooks
Duplicate timers or callbacks after reload Stop/disconnect owned resources in unregister
print does not appear in Console Use ctx.log for app callbacks; runner commands capture stdout
Style not found Use the plugin_id.style_name key and start a new run
Companion code seems stale Restart the app/run if necessary after external helper-module changes

Test registration and disabling, dynamic selectors, action errors, hook errors, commands, shared-data behavior and cleanup. Validate syntax first; enable in a workspace where the plugin's actual behavior can be exercised. Run the existing automated suite from the repository root:

python -m unittest discover -s . -p "test_*.py" -v
Enter fullscreen mode Exit fullscreen mode

The API implementation is plugin_system.py; child command loading is workspace_plugins.py; the editor builder is plugin_builder.py.

Creative experience plugin templates

Plugin Builder now includes 12 Experience / … templates: Constellation playground, Particle reactor, Wave synthesizer, Orbital observatory, Cellular life lab, Generative mandala, Digital rain, Terrain hologram, Focus garden, Branching story studio, Idea constellation board and Palette laboratory.

Each template adds an interactive page through ctx.add_page() and an equivalent Workshop starter through ctx.add_template(). Use New, choose a template, edit it, validate, save and explicitly enable it. These demonstrate creative applications alongside the existing tool-oriented templates. Implementation: creative_gallery.py and creative_templates.py.