LUCI / VINX DESKTOP ENVIRONMENT
MASTER MANUAL
For developers, .vx app authors, .nbg graph designers, plugin authors, and the VinX Brain itself.
v3.1 · Architecture by Vincent Ilagan · Constellation theme removed · post-v3.0 sections addedThis document is loaded into VinX's context so the Brain understands the full ecosystem. v3.1 adds: Cosmic Core service layer, app signing (Ed25519), permissions registry, native app adoption (luci_wm), per-workspace pinning, immersive mode, the VinX Bridge with action registry, and the AI Theme generator.
Chapter 1
What is this whole thing?
Luci is a custom fullscreen desktop environment in pure Python + Tk/Qt. It runs on FreeBSD (today) and provides a sovereign app ecosystem. Apps run inside Luci; Luci runs as a single process the desktop draws everything from.
- Luci Desktop — wallpaper, icons, dock, taskbar, themes
- .vx apps — Python files with a metadata header
- .nbg graphs — JSON visual programs (NodeBase format)
- .run bundles — packaged native Luci apps
- .nbgplugin / .zip — NodeBase plugins (community node types)
- VinX — local LLM brain (vinX.gguf) or user's chosen API / chat session
- Gate — AI-aware command shell
- NodeBase — visual scripting IDE + runtime
- Luna — built-in software 3D renderer
- Cosmic Core — userland service layer (Bus, Guard, Grants, HAL, FS, Proc, Registry, Prompt)
Everything is sandboxed inside the Luci folder. Nothing escapes that the user hasn't explicitly granted.
Chapter 2
What OS is Luci? — Architecture & roadmap
Be honest about the layering — the Brain must not claim more than what is real.
Luci is NOT a kernel. It does not boot from BIOS/UEFI, talk to hardware, manage memory, or schedule processes. A kernel does those.
Luci TODAY = a desktop environment / OS shell written in Python + Qt5/Tk, running on top of the FreeBSD 15 host kernel. Every window Luci draws is a real OS window underneath. This is a common architectural pattern: ChromeOS runs a shell on the Linux kernel; PlayStation OS runs a shell on a FreeBSD-forked kernel. None of them wrote a kernel from scratch; the innovation is the LAYER on top. Luci's layer is what is new: an AI-native desktop with capability-secured apps.
The Roadmap — three stages
- Stage 1 (NOW) — Python layer on FreeBSD 15. Build the Luci experience: .vx apps, NodeBase, VinX, Luna, the Cosmic Core service layer, the signing model. Prove and ship the product.
- Stage 2 — Performance pass + hardware reference unit. Shrink the ISO below 300 MB; cut idle RAM below 150 MB; co-design a laptop with the brand mark and TPN-aligned defaults.
- Stage 3 (GOAL) — A purpose-built AI kernel (C / Rust). A separate, multi-year effort — the long-term north star.
Cross-platform note: the Python core (Qt5/Tk, sockets, .vx runtime, NodeBase) runs on FreeBSD, Linux, and Windows. The Cosmic HAL routes host-specific calls so .vx apps stay portable.
Chapter 3
Architecture — in-process VinX Bridge
Luci runs as one Qt5 process. Every .vx app is loaded INTO that process. When an app wants the AI, it does a plain Python import vinx_bridge as vb — there's no socket, no separate orb process, no port hop. The bridge is the ONE supported entry to the brain, and routes the call to whichever provider the user picked.
Why this matters
- No port to break. An app can't "lose connection" to VinX — they're in the same Python process.
- One audit trail. Every brain call goes through
vinx_bridge.ask / act / assist— easy to log, easy to gate. - Three brains, one surface. The user picks
local·api·chat_sessionin VinX Settings; apps don't need to know which is active. - Local GGUF is the permanent fallback. Even with an API key set, if the API fails the bridge silently falls back to the local model — VinX never goes silent.
The provider config
# ~/.config/luci/vinx_brain.json — written by VinX Settings { "mode": "local" | "api" | "chat_session", "provider": "claude" | "openai" | "gemini" | "groq" | "deepseek", "service": "claude" | "chatgpt" | "gemini" | "grok", "model": "<model name>" }
vinx_bridge. The orb's UI elements (Heart rings, status indicators) are now drawn directly inside Luci's process.Chapter 4
What is a .vx file?
A .vx file is a Python file with metadata in the first comment lines. Luci loads, parses, and executes it. Each .vx represents one app.
Required header (must be the FIRST lines)
# VX:name=My App # VX:icon=🎯 # VX:version=1.0 # VX:author=Your Name
Optional header keys (post-v3.0)
# VX:net=none|local|allowed:host1,host2|full # network policy (Phase A) # VX:cam=requested # camera intent (Phase B) # VX:mic=requested # microphone intent # VX:screen=requested # screen capture intent # VX:files=read:~/path,write:~/other # filesystem scope # VX:sig=<base64 ed25519 signature> # present only on signed apps
Required function
def main(desktop): # build the app UI here ...
When the user double-clicks the app icon, Luci calls main(desktop) with a reference to the Luci instance. From desktop you have access to everything.
Chapter 5
The desktop object
Key attributes
| Attribute | Meaning |
|---|---|
desktop.root | Tk root (fullscreen window) |
desktop._windows | list of currently-open LucyWindow children |
desktop._theme | current theme name (string) |
desktop._canvas | wallpaper canvas (drawn behind everything) |
desktop._screen_w / _h | screen dimensions |
desktop.cosmic | live Cosmic Core singleton (post-v3.0) |
Key methods
| Method | Purpose |
|---|---|
desktop.launch_vx(path) | load + run another .vx app |
desktop._toast(msg) | show a transient toast notification |
desktop._raise_all_windows | bring Luci windows above other OS windows |
desktop._switch_theme(name) | change theme at runtime |
desktop.apply_chrome(win, title, icon) | install the standard hex traffic-light chrome on a Toplevel |
Built-in keyboard shortcuts
Escape— confirm-exit LuciF1/F4— open GateF2— open File ManagerF3— open App LauncherF11— toggle fullscreen- Right-click wallpaper → context menu
Chapter 6
Using LucyWindow
from luci import LucyWindow, C, FONT, FONT_BOLD import tkinter as tk class MyApp(LucyWindow): def __init__(self, desktop): super().__init__(desktop, "My App", 480, 320, "🎯") tk.Label(self.content, text="Inside self.content", bg=C["win_bg"], fg=C["text"], font=FONT_BOLD).pack(pady=20) def main(desktop): MyApp(desktop)
Inherited members
self._desk— the Luci instanceself.content— inner Frame/Widget to pack your UI intoself._minimize()— hide (taskbar still shows)self._restore()— un-hideself._close()— destroy + remove from taskbar
_close() directly, NOT closeEvent(). If your app spawned a subprocess (mpv, ffmpeg, etc.), override _close and stop the subprocess there — otherwise you'll get phantom audio / zombie processes.Chapter 7
Colors & theme (the C dict)
from luci import C
Standard keys
bg, panel, dock, border, accent, accent2, text, dim, white, green, red, yellow, win_title, win_bg.
Themes available
| Theme | Vibe |
|---|---|
midnight | deep navy — default |
void | dark purple — deep space |
forest | dark green |
biomech | organic carbon |
halo | glassmorphism — translucent panels |
Glass themes have _glass: True — windows get -alpha 0.92.
Fonts
from luci import FONT, FONT_BOLD, FONT_SM, FONT_TITLE from luci import FONT_CLOCK, FONT_ICON, FONT_DOCK
Chapter 8
Where to place .vx files
Two zones — system code under the install root, user data under $HOME. This is the standard Unix split (XDG Base Directory spec), which means rsync / Syncthing / Chrome / mpv all find your data where they expect.
System code (immutable — package manager owns it)
| Path | Purpose |
|---|---|
/usr/local/luci/Vinx-Desktop/programfiles/ | System apps (HIDDEN, shipped & signed) |
/usr/local/luci/Vinx-Desktop/programfiles/cosmic/ | Cosmic Core service layer (immutable) |
/usr/local/luci/Vinx-AI/ | VinX runtime (vinx_bridge.py lives here) |
/usr/local/luci/.keys/ | Owner Ed25519 keys (root:wheel · 600 · schg) |
User data (XDG-standard under $HOME)
| Path | Purpose |
|---|---|
~/Desktop/ | User desktop — visible .vx + .run icons |
~/Documents/ | Documents (notes, exports, activity log) |
~/Pictures/ · ~/Videos/ · ~/Downloads/ | Standard user media folders |
~/.config/luci/profile.json | Theme + user prefs |
~/.config/luci/vinx_brain.json | Active brain mode (local / api / chat_session) |
~/.config/luci/cosmic_grants.json | Per-app resource grants |
~/.config/luci/graphs/ | Saved .nbg graphs + .run bundles |
~/.config/luci/plugins/ | User-installed NodeBase plugins |
~/.local/share/luci/trash/ | Trash bin |
~/Apps/ | User-written / AI-generated apps (unsigned by default → sandboxed) |
Paths exposed by luci.py (POSIX values)
| Constant | Resolves to |
|---|---|
LUCI_DIR | /usr/local/luci/ (where luci.py lives) |
PROG_DIR | /usr/local/luci/Vinx-Desktop/programfiles/ |
HOME_DIR / ROOT_DIR | /home/luci/ (the user's home) |
DESK_DIR | ~/Desktop/ |
DOC_DIR | ~/Documents/ |
CONFIG_DIR | ~/.config/luci/ |
USER_FILE | ~/.config/luci/profile.json |
LOG_FILE | ~/Documents/activity_log.txt |
VINX_DIR | /usr/local/luci/ (parent of LUCI_DIR) |
<luci.py-dir>/Vinx-Desktop/... — no $HOME split. Only POSIX hosts use the XDG layout above.<install>/Vinx-Desktop/desktop|documents|Photos|Video|.user, _migrate_user_data() COPIES them to the new $HOME paths and renames the originals to <name>.MIGRATED-YYYYMMDD-HHMMSS. Migration is idempotent and non-destructive — failed copies leave both intact.Chapter 9
Gate — the AI-aware command shell
Open with F1, F4, the Gate dock button, or Gate(desktop).
Files
ls · cd · pwd · cat
Apps
new <name.vx>— create blank .vx templateedit <file.vx>— built-in code editorrun <name>— launch a .vx appapps— list all installed appsinfo <name>— show app metadatatrust <app.vx>·untrust <app.vx>— add / remove sha256 from~/.config/luci/user_trusted.jsonverify <app.vx>— show hash + trust source (system / user / signature / UNTRUSTED)verify manifest— check the system manifest signaturemanifest build|add|show|verify|hash— manage the system trusted-apps manifest (root)sign <app.vx>·license <app.vx>— LEGACY per-device Ed25519 signing (transitional)
Brain / AI
ask <question>·vinx <question>— query VinX (routes through the bridge)fix <name.vx>— VinX analyses a broken appunload— unload VinX from RAMheart— open Heart engine dashboardlearn— run the Learner
System
settings · theme [name] · log · monitor
Shell
git <cmd>— git passthroughpip <cmd>— pip → portable Python onlypy <file>— run a .py file with portable Pythonwhere <name>— locate an executableinstall <pkg>—sudo -n pkg install -y(sudoersluci-pkg)
C++
compile <f.cpp> — compile C++ to binary (needs g++) · g++ / gcc passthroughs
Gate
clear · exit
memory/gate_cmd.json → Luci polls it → Gate._inject_cmd() runs the command and prints [VinX] <cmd>.Chapter 10
Calling VinX AI from a .vx app
One way, always — direct in-process Python import. No socket, no port, no separate process to manage.
import vinx_bridge as vb reply = vb.ask("Your prompt here", timeout=60) # Routes through the user's active brain: # local GGUF · paid API · or web chat session. # Falls back to local on failure — never silent.
Three surfaces
| Call | What it does |
|---|---|
vb.ask(prompt, timeout=60) | Plain text answer from the active brain. Returns "" if total failure. |
vb.act(name, args) | Run a registered capability by name (brightness, files, processes, theme, apps — 17 actions). Skips the LLM entirely. |
vb.assist(user_text, max_turns=3) | Full agent loop: model writes <<ACT name {args}>> tokens, bridge dispatches them through act(), feeds results back. Returns {reply, actions: [...]}. |
vb.current_engine() | Returns the active {mode, provider, service, model} — for status UI. |
vb.has_provider() | True if any brain is reachable. Use to gate AI-only UI. |
Threading rule
Bridge calls are synchronous and can block for tens of seconds. Always run them off the Qt thread:
import threading import vinx_bridge as vb def _ask_off_thread(self, prompt): def _run(): r = vb.ask(prompt, timeout=60) or "(no reply)" # Marshal back to Qt thread before touching widgets QTimer.singleShot(0, lambda: self._show(r)) threading.Thread(target=_run, daemon=True).start()
vinx_local calls bypass the user's brain selection (their API key, their chat-session login) and skip the audit trail. The bridge IS the contract.Chapter 11
The Old Soul — VinX's engines
The Heart engine + ring visualisation live inside Luci's process (drawn by heart.vx + vinx.vx). Each ring = one engine's current "level".
- HEART (rose ring) — emotional state, updated by chat sentiment
- LEARN (green ring) — idle consolidation of memory into
learned_skills.json - MEM (blue ring) — long-term knowledge graph (
Trainer.vinxcore) - SYNC + TURBO (purple / yellow rings) — IPC heartbeat + token rate
Chapter 12
NodeBase — visual scripting (.nbg)
NodeBase is a visual dataflow scripting language. The user designs programs by wiring nodes together; VinX can also generate these graphs from natural-language prompts.
Graphs are saved as .nbg files (JSON) at .user/graphs/.
.nbg file format
{
"nodes": [
{"id":"<unique>", "type":"<type>", "x":123, "y":456,
"params":{"<key>":"<value as string>"}}
],
"wires": [
{"fn":"<from_node_id>", "fp":"<from_port>",
"tn":"<to_node_id>", "tp":"<to_port>"}
]
}
Rules
- Every graph MUST have a node of type
startwithid="start_0". - All param values are strings (even numbers).
- Wire fields:
fn=from_node,fp=from_port,tn=to_node,tp=to_port. - Flow wires (orange) sequence execution.
- Data wires carry values from output ports to input ports.
Port types (colour-coded on canvas)
| Type | Colour | Carries |
|---|---|---|
flow | orange | control flow — sequencing |
string | cyan | text data |
number | yellow | integer or float |
bool | green | True / False |
list | purple | array / iterable |
file | red | file path |
any | grey | accepts anything |
Chapter 13
NodeBase IDE — editing shortcuts
Mouse
- Double-click canvas — open Add menu (with search)
- Right-click canvas — Add menu near cursor / context menu
- Right-click node — delete / duplicate / params
- Click output port ● — begin a wire
- Drag wire to empty space → quick-add filtered to compatible types
- Click + drag node title — move node
- Middle-drag / Alt-drag — pan the canvas
- Scroll wheel — zoom in / out
Keyboard
| Key | Action |
|---|---|
Space | quick-add at cursor |
Ctrl+F | full Add menu (with search box) |
F | fit-all to view |
C | center on Start node |
M | toggle minimap |
F5 | run graph |
Ctrl+S / Ctrl+O | save / load .nbg |
Ctrl+Z / Ctrl+Y | undo / redo |
Delete | remove selected node |
Esc | cancel wire / close popup |
Toolbar
▶ Run · ⬡ Add · 💾 Save · 📂 Load · 🗑 Clear · ↩ Undo · ↪ Redo · 🔍 Check · 🔧 Fix · 🤖 VinX · 🔌 Plug · ⛶ Fit · ⊙ Center · 🗺 Map
Chapter 14
Node categories (17 total · ~150+ types)
Standard
- flow Start, End, If, For Each, Set/Get Var, Delay, Sequence
- value String, Number, Boolean, Make List
- math Math Op, Compare, Clamp, Random
- string Concat, String Op, Split, Format
- io Print, File Read, File Write, User Input
- ai Ask VinX, Summarize, Act (NEW)
- system Run Cmd, Open App, HTTP Get
- util Note, Type Convert, Length, Index
PRO
- ui Window, Canvas, Button, Label, Input, Slider, Color, Panel, Image View, Toolbar, Menu Bar, Layers, Inspector, Timeline, Viewport 2D/3D
- gfx Load/Save Image, Brush, Erase, Layer Composite, Blend Mode, Mask, Crop, Resize, Color Adjust, Blur, Sharpen, Selection, Transform, Text Tool, Filter, Brush Stroke, Canvas Clear, Canvas → PNG
- video Load/Extract Video, Chroma Key, Color Range Key, Mask Refine, Edge Feather, Track Point/Object, Stabilize, Composite, Export Video
- 3d Viewport 3D, Camera, Light (Omni/Sun/Spot/Area), Material, Texture, Orbit View, Raycast, 3D Brush, Sculpt, Particle System, Physics Body, Render Scene, Floor + Grid, Object Manager, Viewport Mode, Primitives (cube/sphere/plane/cyl/torus/cone/grid), Particles (emit/fire/smoke/water/dust/force), Luna Render, Render Pass, Denoise, Compose, Subdivide, Array, Mirror, Bevel, Transform, Import OBJ/FBX/USD/glTF/STL, Export OBJ
- sim Particle Emitter, Force Field, Noise Motion, Collision, Lifetime, Trail, Glow, Fluid Domain, Smoke Domain
- doc Doc Page, Rich Text, Table, Spreadsheet, Formula Cell, Chart, Slide, Export PDF/DOCX/XLSX
- logic Function, Custom Node, Class/Object, Event, State, Signal, Async, Try/Catch, Loop Break, Map, Filter, Reduce, Switch, Router, Cache, Database, History
- app Asset, Asset Browser, Import Asset, Export App, Install App, App Manifest, Permissions, Save State, Load State, Plugin
- gpu Shader, Fragment Shader, Vertex Shader, Texture Buffer, Compute Pass, Render Target, Post Process, Noise, Displacement
Chapter 15
3D subsystem — Donut3D / V3D
The 3D nodes work on a shared scene dict that all nodes mutate:
scene = {
"meshes": [{"kind", "params", "tx/ty/tz", "name", "material"}, ...],
"lights": [{"type":"sun"|"omni"|"spot"|"area", ...}, ...],
"floors": [{"size", "divisions", "y", "color"}, ...],
"particles": [...],
"camera": {"x","y","z","yaw","pitch","distance",
"target_x","target_y","target_z","fov"},
"viewport": <Tk Canvas>,
"mode": "wireframe" | "shaded" | "rendered",
"selected_mesh": <int index>,
"_render_fn": <callable to re-render>,
}
Mesh primitives
cube · sphere · plane · cyl · torus · cone · grid
Light types
omni · sun · spot · area
Viewport modes
wireframe (edges only) · shaded (filled flat) · rendered (filled + outlined)
Camera controls (in viewport)
- Left click — select mesh (no drag)
- Left drag — orbit
- Middle drag — pan target
- Scroll wheel — zoom
Import / export formats
| Node | Format | Dependency |
|---|---|---|
import_obj | Wavefront .obj | built-in parser, no deps |
import_stl | Stereolithography .stl | built-in, ASCII + binary |
import_fbx | Autodesk FBX | pip install pyassimp |
import_usd | Pixar USD | pip install usd-core |
import_gltf | glTF / GLB | pip install pygltflib |
export_obj | Write all scene meshes as .obj | built-in |
Chapter 16
Luna — built-in software 3D renderer
Pure-Python rasterizer using PIL. No GPU needed.
Key features
- Camera basis vectors (right/up/forward) with proper view xform
- Perspective projection with FOV
- Mesh primitives: 8-vert cubes, 96-vert spheres, 192-vert torus
- GLOBAL face depth sort (painter's algorithm across all meshes)
- Backface culling (skip ~50% of faces facing away)
- Lambertian shading per face: ambient + Σ max(0, dot(n, lightDir))
- Floor with world-aligned grid lines
- Particle billboards (fire = warm gradient, smoke = grayscale)
- Selected mesh outline (yellow + slight tint)
Pipeline
luna_render → reads scene, returns PIL.Image ALSO blits into scene.viewport canvas if present installs scene["_render_fn"] callback
Triggered re-renders: when camera orbits/pans/zooms, scene moves via _render_fn. Object Manager edits also call _render_fn.
Viewport performance — three rules
- Coalesced renders — at most ONE render in flight; mouse-motion events (~100/sec) can no longer pile up renders = no lag.
- Wireframe-while-dragging — orbit/pan/gizmo drag renders cheap wireframe (no face sort, no lighting, no fills); a full shaded render runs once on mouse release.
_render_fnre-rasterizes the SAME scene dict — it does NOT re-run the graph flow. Camera nodes mutate the scene in place.
Not yet implemented (BETA stubs)
luna_pass— render passes (beauty, depth, normal, AO)luna_denoise— Gaussian denoise (works as basic blur)luna_compose— over/add/multiply/screen/mix
Chapter 17
Plugin system (Blender-style addons)
Drop a .nbgplugin/ folder OR .zip into Vinx-Desktop/plugins/. NodeBase auto-loads them on startup.
Plugin structure
MyPlugin.nbgplugin/ manifest.json ← REQUIRED: name, version, author nodes.py ← OPTIONAL: register new node types graphs/ ← OPTIONAL: shipped .nbg templates assets/ ← OPTIONAL: icons, samples
manifest.json
{
"name": "MyPlugin",
"version": "1.0.0",
"author": "Your Name",
"description": "What this plugin adds.",
"category": "3d",
"permissions": ["graphics"],
"engine": "nodebase/1.0"
}
nodes.py — registering new node types
def register(register_node): def _exec_my_node(ex, node, inp, put, p): x = float(inp("x") or 0) put("doubled", x * 2) put("flow", True) register_node("my_double", { "label": "×2 Double", "cat": "math", "inputs": [{"n":"flow","t":"flow"}, {"n":"x", "t":"number"}], "outputs": [{"n":"flow", "t":"flow"}, {"n":"doubled","t":"number"}], "params": [], }, _exec_my_node)
Sample plugin shipped: ToonShader.nbgplugin/ — adds toon_ramp, toon_outline, toon_posterize.
Chapter 18
Executor API (for plugin authors)
Your executor receives 5 arguments:
def my_executor(ex, node, inp, put, p): # ex : the running _Executor (use ex.out("text", "sys"|"err")) # node : full node dict (id, type, x, y, params) # inp : function — inp("port_name") resolves upstream value # put : function — put("port_name", value) writes to output # p : node["params"] dict
Important patterns
- UI nodes should be IDEMPOTENT — check
ex._ui_kept[nid]first - Event-driven nodes get re-run on user actions (button clicks etc.)
- Mutate scene dict in-place; renderer reads on next call
Chapter 19
Build validator + auto-fix
Toolbar buttons
- 🔍 Check — scans graph, lists issues, draws ⚠ rings on bad nodes
- 🔧 Fix — auto-adds missing start/end/export_app, wires them
- 🤖 VinX — sends graph + issues to VinX, gets fixed graph back
What _check_build catches
- Missing start / end nodes
- Missing
export_app(informational) - Nodes with no incoming flow (won't execute)
- Dangling wires (pointing to deleted nodes)
Chapter 20
NodeBase: Editor vs Player — the split
nbcore.vx is FROZEN as "Core stdlib v1". Do NOT edit nbcore.vx to add a new node or app feature. A missing capability MUST come from a universal .nbgplugin — never from changing the player. nbcore.vx stays stable forever; the ecosystem grows through plugins.Name nodes by capability, never by app
- OK
media_trim·scene_render·ui_button·math_lerp - WRONG
donut3d_export·michael_music_editor·my_special_btn
Chapter 21
.run app bundles (native distribution format)
Click 📦 Export in NodeBase's toolbar. The exporter:
- Detects every node type used in the current graph
- Classifies each: built-in core · plugin-provided · missing
- Maps plugin nodes → their source
.nbgpluginfolder - Aborts with a clear error if any node is unresolved:
Missing node: media_luna_cache_seek Required plugin not found — install the .nbgplugin.
- Packages a self-contained folder with
.runextension:
MyApp.run/ main.nbg the visual program (graph source) runtime/ nbcore.vx the player — runs without nodebase.vx addons/ *.nbgplugin/ ONLY the plugins this graph actually uses
To install: drop the .run folder into desktop/ or .user/graphs/. To launch: double-click in Luci's File Manager. Luci runs runtime/nbcore.vx, which loads main.nbg + addons and executes the graph. nodebase.vx is NOT required.
.run bundles used manifest.json + graph.nbg + main.vx; Luci's File Manager still launches those for back-compat.Chapter 22
Public API exposed by NodeBase
External Python code can import nodebase.vx (via SourceFileLoader) and call:
| Function | Purpose |
|---|---|
nodebase.run_graph_standalone(graph_dict, output_callback=None) | Execute a parsed graph headlessly. Returns the executor. |
nodebase.run_graph_file(nbg_path, output_callback=None) | Convenience: load .nbg from disk + run. |
nodebase.register_node(ntype, defn, executor) | Add a new node type at runtime (used by plugin loader). |
nodebase.load_plugins(plugin_dir=None, verbose=True) | Re-scan the plugins folder. Idempotent. |
nodebase.get_plugin_info() | List of loaded plugin manifests. |
nodebase.DEFS | Global dict mapping ntype → node definition. |
nodebase._LIVE_EXECUTORS | List of executors kept alive by .run apps. |
Chapter 23
AI graph generation (VinX writes graphs)
nodebase_ai.generate(task, timeout=60) returns (ok, graph_dict, error).
Pipeline
- Format task into a short prompt with all valid node types listed
- Call
vinx_bridge.ask()(preferred) orvinx_local.ask()directly - Strip markdown fences, extract first
{...}block _repair_jsoncloses unclosed brackets if truncated_validateensures node types are valid; drops invalid wires- Failed parses save raw output to
.nb_last_failed.txt
Chapter 24
Worked examples in .user/graphs/
- PaintLite.nbg — simple paint app — canvas + brush + color picker + save PNG. Demonstrates real event binding.
- V3D.nbg — early 3D editor — viewport + cube/sphere primitives + camera orbit. Foundation for Donut3D.
- Donut3D.nbg — full V3D successor — torus default scene, floor + grid, 4 light types, 5 primitives, 3 viewport modes (wireframe/shaded/rendered), Object Manager with per-object color picker + delete + transform editor, camera orbit/pan/zoom, click-to-select.
Chapter 25 · post-v3.0
AI Theme generator
Type a vibe — get a palette. The AI Themes panel feeds the user's prompt through VinX Bridge, parses the strict-JSON response into Luci's C dictionary, and applies it live across the running desktop.
Flow
The result feeds straight into desktop._switch_theme(name) (after persistence to .user/profile.json). Wallpaper, dock, window-chrome accent, hex traffic-light hover state — all repaint at once.
Chapter 26 · post-v3.0
Cosmic Core — the userland service layer
A microkernel-style service layer between Luci's .vx apps and the host kernel. Eight modules, ~1100 lines, loaded once at startup as desktop.cosmic.
Modules
| Module | Role |
|---|---|
cosmic_core.vx | Singleton orchestrator — loads + wires every other module |
cosmic_bus.vx | Thread-safe pub/sub event bus with 500-event ring history |
cosmic_guard.vx | Per-.vx capability sandbox — see Chapters 28 + 30 |
cosmic_grants.vx | Registry of (app, resource) → state (always / once / blocked) |
cosmic_prompt.vx | Sync first-time grant dialog — blocks caller, fires Qt UI |
cosmic_hal.vx | Host adapter — run_cmd / shell / service per OS |
cosmic_fs.vx | luci:// URL resolver |
cosmic_proc.vx | Subprocess launcher with bus emissions |
cosmic_registry.vx | Plugin / node provider discovery |
Chapter 27 · post-v3.0
App trust model — hybrid checksum + manifest
Trust is decided by SHA-256, not per-device keys. The same .vx hashes the same on every Luci install — so an app trusted on PC-A is automatically trusted on PC-B, no key exchange. The old per-device Ed25519 signature stays as a transitional fallback only.
Three sources of trust
System manifest — universal trust set
| File | Purpose |
|---|---|
/usr/local/luci/.keys/trusted_apps.json | JSON of {rel_path: sha256_hex} for every first-party app |
/usr/local/luci/.keys/trusted_apps.sig | Ed25519 signature of the manifest's bytes — verified once at boot |
Ships with every Luci install. Same hashes everywhere → same universal trust set on every device. Local on-disk tampering is caught by the manifest signature check at boot — if the sig is invalid, the manifest is rejected and only user-trusted apps stay trusted.
User trust — per-recipient elevation
A user can explicitly trust a third-party app on their device. The hash is appended to ~/.config/luci/user_trusted.json:
{
"<sha256 hex>": {
"name": "cool_app.vx",
"path": "/home/luci/Apps/cool_app.vx",
"added_at": 1717898100
}
}
Cosmic Guard hot-reloads this file on mtime change — newly trusted apps go silent immediately, no restart.
Gate commands
| Command | Action |
|---|---|
trust <app.vx> | Add the file's sha256 to user_trusted.json |
untrust <app.vx> | Remove it from user_trusted.json |
verify <app.vx> | Show the hash + where trust comes from: system · user · signature · UNTRUSTED |
verify manifest | Check the system manifest signature |
manifest build | Rescan programfiles/ + sign a fresh manifest (root) |
manifest add <app.vx> | Add one app to the existing manifest + re-sign (root) |
manifest show | Print the manifest contents |
sign <app.vx> | LEGACY — sign with the per-device Ed25519 key (fallback path) |
What "UNTRUSTED" actually means
Untrusted apps still run — UI works, local logic works. They just can't:
- Call host commands (
pkg,sudo, system binaries) without a prompt - Reach the network beyond their declared
# VX:net=scope - Touch resources (camera, mic, screen) without going through the Cosmic grant prompt
Same UX as Gatekeeper / SmartScreen — third-party apps run, sensitive actions ask. Click "Allow Always" enough times and the user effectively trusts the app for its declared intent.
Two-PC scenario walkthrough
- Vincent writes
cool_app.vxon PC-A. - Sends it to PC-B via USB / network / chat.
- PC-B user opens Gate, types
trust cool_app.vx. - The file's sha256 is recorded in PC-B's
user_trusted.json. - Next launch: cosmic_guard sees the hash → silent full access, no prompts.
manifest build) after editing first-party apps, or re-trust (trust) after editing a third-party app._is_trusted falls back to the per-device signature check if neither manifest nor user trust matches. Once every first-party app is in the manifest, luci_sign.py can be retired.Chapter 28 · post-v3.0 · Phase A
Network gating — per-app policy
Every .vx declares its network intent in the header:
# VX:net=none # DEFAULT for unmarked apps # VX:net=local # loopback only (127.0.0.1, ::1) # VX:net=allowed:api.host.com,other.host # explicit whitelist # VX:net=full # opt-in escape hatch
On import, cosmic_guard rewrites socket.socket in the app's namespace with a guarded subclass. Every connect() is policy-checked. Higher-level libraries (urllib, http.client, requests, httpx) all route through socket — guarding it once covers them all. Blocked attempts emit cosmic.net.blocked for the audit log.
Chapter 29 · post-v3.0 · Phase B
Resource grants — camera, mic, screen, files
Apps that want sensitive resources declare intent in the header AND go through a user-consent prompt on first use.
# VX:cam=requested # VX:mic=requested # VX:screen=requested # VX:files=read:~/Pictures,write:~/Documents
On first cv2.VideoCapture(0) (or equivalent), Cosmic prompts the user:
[ Block ] [ Allow Once ] [ Allow Always ]
The decision is saved to ~/.config/luci/cosmic_grants.json. Apps that use a guarded API without declaring → automatic block, since omitting declaration is itself a signal.
Chapter 30 · post-v3.0
Permissions panel
A single pane of every grant Cosmic Core has issued — per app, per resource. List + revoke + audit-export.
- Lists per-resource sections: Camera · Microphone · Screen · Network · Files · Other
- Each row shows the app, the grant state, when it was set
- Revoke any grant with one click — next access re-prompts
- Export audit CSV button — for compliance reviewers
- Auto-refreshes when other apps trigger grant events on the bus
Lives at programfiles/permissions.vx.
Chapter 31 · post-v3.0
Native app adoption — luci_wm.py
A separate process (luci_wm.py) auto-frames pkg-installed apps (Blender, Chrome, GIMP) with Luci's hex traffic-light chrome. The native app's window manager hint is read; luci_wm creates an override_redirect=True frame, parents the client into it, and surfaces it in the dock + workspace switcher.
Key invariants
- Identify Luci-own windows by
WM_CLASS(luci.py / Luci), not just PID - Skip override-redirect AND Luci-own windows — but still
client.map()them (else they're invisible) - Frames are created
override_redirect=True— else cross-connection maps re-frame and kill the client - Never use a blocking
next_event()loop — a select/poll loop dropped the desktop's MapRequest once and produced a black screen
IPC contract
/tmp/luci-wm-windows.json— WM → Luci (window list)/tmp/luci-wm-cmd— Luci → WM (HIDE | SHOW | RESTORE <id>)
Chapter 32 · post-v3.0
Workspaces & immersive mode
Every Luci window is workspace-tagged. Per-workspace pinning means apps "stay on" the workspace they were created on. Swiping between workspaces hides/shows accordingly.
Immersive mode
When an app enters fullscreen (red hex on a maximised window, or programmatically), Luci auto-allocates a fresh workspace, moves the app there, and hides the chrome (no dock, no topbar) until Esc. On exit, the workspace is freed and the user returns to their prior workspace.
_wm_apply_workspace only SHOWs all (self-heals). The Overview HIDES all native windows on open and SHOWs all on close.Chapter 33 · post-v3.0
VinX Bridge — single entry to the brain
The Bridge is the single supported way to talk to VinX. Three modes (local · api · chat_session) selected through VinX Settings. The local GGUF is permanently retained as the offline fallback — it never gets removed by user choices.
Chapter 34 · post-v3.0
Music · Video · Camera
Video player (video.vx) & Music player (music.vx)
Both run in-process as _QtLucyWindow subclasses, driven by an external mpv process over JSON-IPC on a Unix socket at /tmp/luci-mpv-*.sock. CPU-only — no GPU dependency. mpv runs with --vo=x11 for video, --no-video for music.
_close() directly, NOT closeEvent(). Override _close to call mpv.stop() (quit IPC → terminate → os.killpg) — otherwise: phantom audio. Always match mpv JSON-IPC replies by request_id and skip events, or pos/duration/song-switch read the wrong data.Music tags & cover art
mutagen (pip-installed) reads ID3 / FLAC / MP4 metadata for the playlist + the now-playing card's cover art.
Camera (camera.vx)
OpenCV (cv2) feed with mode toolbar: Preview · Face detect · Face track · Face recognise · Hand track · Body detect. Hand track uses a YCrCb skin mask + convex-hull / convexity defects to extract a pixel-skeleton overlay (palm centre + fingertips). VinX bridge reads the live feed via /tmp/luci-vision.json for "what I see now" prompt context.
Chapter 35 · post-v3.0
Topbar widgets & foldering
The topbar is a horizontal strip of independent widget .vx apps. Each widget owns its own .vx file so they can be hot-swapped without touching luci.py.
- Brand hex — left-most, opens the Luci system menu
- App menu folders — current focused window's menu items grouped by section header
- Topbar foldering — Files dropdown shows favourites + recent items; can be nested
- Wi-Fi — wpa_supplicant + dhclient bridge
- Volume — mixer-driven slider with mute
- Brightness —
backlightcommand (real panel dim) with xrandr fallback - Battery — ACPI; warns at 25% / 7%
- Clock — date + time
- VinX status — current brain mode + ready/thinking indicator
Click any widget → floating panel opens just below the topbar. Click outside → panel closes.
Chapter 36
Minimal example — a complete .vx app
# VX:name=Tiny Counter # VX:icon=🔢 # VX:version=1.0 # VX:author=Your Name import tkinter as tk from luci import LucyWindow, C, FONT_BOLD class Counter(LucyWindow): def __init__(self, desktop): super().__init__(desktop, "Counter", 240, 160, "🔢") self._n = 0 self._lbl = tk.Label(self.content, text="0", bg=C["win_bg"], fg=C["accent2"], font=("Consolas", 32, "bold")) self._lbl.pack(pady=14) tk.Button(self.content, text="+1", bg=C["accent"], fg=C["white"], font=FONT_BOLD, relief="flat", bd=0, padx=20, pady=6, command=self._inc).pack() def _inc(self): self._n += 1 self._lbl.config(text=str(self._n)) def main(desktop): Counter(desktop)
Chapter 37
Real example — app that talks to VinX
# VX:name=Ask Bot # VX:icon=💬 # VX:author=Your Name import threading, tkinter as tk from luci import LucyWindow, C, FONT, FONT_BOLD class AskBot(LucyWindow): def __init__(self, desktop): super().__init__(desktop, "Ask Bot", 460, 360, "💬") self._out = tk.Text(self.content, bg=C["win_bg"], fg=C["text"], font=FONT, wrap="word", relief="flat", bd=8) self._out.pack(fill="both", expand=True) self._inp = tk.Entry(self.content, bg=C["border"], fg=C["text"], font=FONT, insertbackground=C["white"], relief="flat", bd=6) self._inp.pack(fill="x", padx=4, pady=4) self._inp.bind("<Return>", self._send) def _send(self, _=None): q = self._inp.get().strip() if not q: return self._inp.delete(0, "end") self._out.insert("end", f"You: {q}\nVinX: thinking...\n") threading.Thread(target=self._ask, args=(q,), daemon=True).start() def _ask(self, q): import vinx_bridge as vb reply = vb.ask(q, timeout=60) or "(no reply)" self.after(0, lambda: self._out.insert("end", f"VinX: {reply}\n")) def main(desktop): AskBot(desktop)
Chapter 38
Tips
- Always import
Cfresh inside your app code — don't cache. - Use
LucyWindow/_QtLucyWindowwhenever possible — never hand-roll close/min/max buttons. - For long ops, use
threading.Thread; marshal results viaself.after. - Use
lucy_log(category, message)for the activity log trail. - Built-in vars in
val_str:$desktop_path,$home,$os,$time. - Press
F1for Gate;F11for fullscreen toggle. - UI nodes should be idempotent (check
ex._ui_kept[nid]). - For 3D apps:
scenedict is the universal carrier between nodes. - Always declare
# VX:net+# VX:files+ resource intents in new apps — undeclared intent is itself a Cosmic Guard block. - Re-sign after every edit. Editing invalidates the signature.
Chapter 39
File system reference (current FreeBSD layout)
Two zones, hard split. System code at /usr/local/luci/ is package-manager territory — immutable (chflags schg), no user writes. User data at /home/luci/ (or $HOME) follows the XDG Base Directory spec — same place every other Unix tool expects to find it.
Zone 1 — System code (immutable install root)
/usr/local/luci/ install root · package manager owns it · chflags schg ├── luci.py desktop entry point (single Qt5 process) ├── luci_wm.py window manager · frames native apps ├── luci_sign.py Ed25519 signing tool (legacy fallback) ├── luci_manifest.py build + sign system trust manifest ├── .keys/ root:wheel · 600 · schg │ ├── luci_ed25519.priv private key (NEVER distribute) │ ├── luci_ed25519.pub public key (also embedded in cosmic_guard) │ ├── trusted_apps.json system manifest · sha256 per app │ └── trusted_apps.sig Ed25519 signature of the manifest ├── Vinx-AI/ VinX runtime — pure in-process Python │ ├── vinx_bridge.py ★ THE single entry to the brain │ ├── vinx_local.py local GGUF subprocess wrapper │ ├── gpt_engine.py multi-provider API router (Claude/GPT/...) │ ├── vinx_chat_session.py browser-automation brain (Claude.ai etc.) │ ├── vinx_chat_worker.py LLM worker (llama-cpp) │ ├── vinx_actions.py 17-action capability registry │ ├── vinx_os_manual.py system-prompt knowledge base │ ├── vinx_camera.py /tmp/luci-vision.json bridge │ ├── vinx_cosmic.py cosmic_bus subscription │ ├── vinx_memory.py long-term memory │ ├── heart_engine.py Old Soul: emotion │ ├── vinx_turbo.py autonomous reasoning loop │ ├── nodebase_ai.py text → .nbg graph generator │ └── floating_core.py legacy orb process (port 52525 · unused by new code) └── Vinx-Desktop/ └── programfiles/ HIDDEN system .vx apps · signed ├── vx_manual.txt ← THE manual content ├── manual.vx Tk viewer for vx_manual.txt ├── cosmic/ Cosmic Core service layer │ ├── cosmic_core.vx singleton orchestrator │ ├── cosmic_bus.vx pub/sub event bus │ ├── cosmic_guard.vx capability sandbox (chflags schg) │ ├── cosmic_grants.vx permission registry │ ├── cosmic_prompt.vx first-time grant dialog │ ├── cosmic_hal.vx host adapter │ ├── cosmic_fs.vx luci:// path resolver │ ├── cosmic_proc.vx subprocess launcher │ └── cosmic_registry.vx plugin / node provider discovery ├── docs/ HTML docs │ ├── luci-manual.html this rich-styled manual │ └── cosmic-security-pitch.html investor pitch ├── nodebase.vx NodeBase EDITOR (private) ├── nbcore.vx NodeBase PLAYER (ships in every .run) ├── terminal.vx Qt terminal ├── vinx.vx VinX chat panel + plug button ├── vinx_settings.vx 4-tab brain picker (local/api/chat/keys) ├── vinx_model.vx model picker UI ├── camera.vx cv2 face/hand/body tracking ├── permissions.vx cosmic.grants UI ├── brightness.vx backlight + xrandr ├── music.vx mpv music player ├── video.vx mpv video player ├── notes.vx notepad ├── pixel.vx image viewer ├── themes.vx theme picker (+AI Theme) ├── heart.vx Heart engine UI ├── learner.vx knowledge consolidator ├── monitor.vx system monitor ├── log.vx activity log viewer ├── check.vx system self-check ├── cpp_ide.vx C++ source editor ├── sysApps.vx external app launcher ├── trash.vx trash bin UI ├── hosting.vx localhost dev server panel ├── blackhole.vx private vault ├── python/ portable Python runtime (Windows builds) ├── icons/ app + dock icons └── models/vinX.gguf local LLM (always retained as fallback)
Zone 2 — User data (XDG-standard under $HOME)
/home/luci/ user home · all writable user state lives here ├── Desktop/ visible desktop icons (.vx + .run + shortcuts) ├── Documents/ user documents │ └── activity_log.txt Luci's activity log (LOG_FILE) ├── Downloads/ browser + Gate downloads ├── Pictures/ photos · screenshots ├── Videos/ video files ├── Apps/ user-written / AI-generated .vx apps · unsigned by default ├── .config/luci/ XDG config (CONFIG_DIR) │ ├── profile.json theme + user name (USER_FILE) │ ├── settings.json startup apps list │ ├── vinx_brain.json active brain: local / api / chat_session │ ├── cosmic_grants.json per-app per-resource permission state │ ├── user_trusted.json user-added trusted app hashes (sha256) │ ├── graphs/ saved NodeBase .nbg + exported .run bundles │ └── plugins/ user-installed NodeBase plugins (.nbgplugin / .zip) ├── .local/share/luci/ │ └── trash/ trash bin (XDG data dir) └── .Xauthority used by `su luci -c "env DISPLAY=:0 ..."` restart # Plus shared OS-level locations Luci writes to at runtime: /tmp/ ├── luci-wm-windows.json WM → Luci window list ├── luci-wm-cmd Luci → WM command pipe ├── luci-mpv-*.sock mpv JSON-IPC sockets (music/video) ├── luci-vision.json camera frame metadata for VinX ├── luci-stderr.log desktop stderr └── luci-vx-errors.log .vx app crash log
luci.py:_migrate_user_data() auto-COPIES (never moves) any old <install>/Vinx-Desktop/desktop|documents|Photos|Video|.user|.trash/ trees to the new $HOME XDG locations, then renames the originals to <name>.MIGRATED-YYYYMMDD-HHMMSS so legacy code paths can never accidentally write back. Idempotent and safe to re-run.<luci.py-dir>/Vinx-Desktop/.... The XDG split is a POSIX-only design choice.Chapter 40 · post-v3.0
Troubleshooting — when something breaks, look here first
| Symptom | Where to look | Likely cause |
|---|---|---|
| App opens then closes | /tmp/luci-vx-errors.log | Python exception in app's main() |
| App "won't talk to the network" | App's # VX:net= header | Default none if undeclared — add an allowed: host |
| Camera shows black | Permissions panel | Grant was set to blocked; revoke + re-launch |
| "App bypassed Cosmic" toast | Bus log: cosmic.bypass | App used os.system or kernel binary directly — switch to cosmic.hal.run_cmd |
| Window has no traffic lights | App's window code | Hand-rolled chrome — switch to _QtLucyWindow / LucyWindow |
| Phantom audio after closing video | video.vx _close override | Override _close, call mpv.stop() there |
| Black screen after WM restart | luci_wm.py event loop | Blocking next_event() drops desktop MapRequest — restore non-blocking path |
| Native app re-framed twice | luci_wm override-redirect handling | Frame must be override_redirect=True |
| Native app vanishes on workspace switch | _wm_apply_workspace | Native windows are not workspace-bound; only SHOW all |
| App got "ServiceUnknown" on "Show in folder" | luci_files1.py D-Bus registration | Keep a live reference to BusName + object or it GCs |
| Wi-Fi drops after sleep | /etc/rc.local watchdog | rtw88 + background_dhclient watchdog rebinds |
| "setsid not found" | FreeBSD command differences | setsid doesn't exist on FreeBSD — use daemon -f |
| Stale deploy persists | chflags schg + file overwrite | chflags noschg; chmod u+w; verify with grep -c marker |
| VinX won't answer | VinX Settings → active brain | Local model file missing OR API key invalid — bridge falls back to local |
| Signed app now blocks pkg/sudo | File was edited after signing | Hash changed — fall back to trust <app.vx> (user) or manifest add (system) in Gate |
| Third-party app prompts on every action | App not in any trust source | Open Gate, run verify <app.vx> → if UNTRUSTED, run trust <app.vx> |
| "manifest INVALID" toast at boot | System manifest tampered or sig mismatch | Run verify manifest; rebuild with manifest build (root) to re-sign |
| Vincent's app trusted on PC-A, prompts on PC-B | PC-B never added the hash | On PC-B: Gate → trust cool_app.vx · once-per-recipient-per-app |
Restart the desktop cleanly
pkill -9 -f luci_wm.py pkill -9 -f luci.py sleep 3 su luci -c "env DISPLAY=:0 XAUTHORITY=/home/luci/.Xauthority \\ nohup python3 /usr/local/luci/luci.py \\ > /tmp/luci-stderr.log 2>&1 </dev/null &"
Confirm the desktop actually rendered
ffmpeg -f x11grab -video_size 1920x1080 -i :0 -frames:v 1 /tmp/s.png # black ≈ 8 KB · real ≈ 800 KB+ # scp + view it
End of manual
That's the system.
Feed this entire file to VinX once at startup so the Brain has full context. Re-feed when this manual changes.
- Architecture: Vincent Ilagan
- v3.1 update: Cosmic Core service layer, app signing (Ed25519), permissions registry, native app adoption (luci_wm), per-workspace pinning + immersive mode, VinX Bridge + 17-action registry, AI Theme generator
- v3.0 update: Luci OS roadmap, system-port socket bridge, nbcore.vx player, new .run layout, exporter, 3D viewport perf
- Maintained at:
programfiles/vx_manual.txt· viewed viamanual.vx· stylised atdocs/luci-manual.html