Capabilities Reference
Capabilities are declared in glyx.config.ts under capabilities. Every system API is opt-in — calling an API without the matching capability throws at runtime with a clear error message naming the key to add.
import { defineConfig } from '@glyx-dev/config'
export default defineConfig({
capabilities: {
fs: { read: ['**'], write: ['documents/**'] },
db: true,
clipboard: true,
},
})Declare only what your app genuinely uses. The capability list is embedded in the binary and visible to anyone who inspects it. Future Glyx versions may surface it to end users as a permission summary.
fs — File system
fs?: {
read?: string[] // glob patterns for readable paths
write?: string[] // glob patterns for writable/creatable paths
delete?: string[] // glob patterns for deletable paths (falls back to write globs when omitted)
}Unlocks the fs API: readFile, writeFile, appendFile, deleteFile, listDir, stat, mkdir, exists, watchDir.
Glob rules
- Patterns are relative to the app's working directory.
*matches within a single path segment;**recurses into subdirectories."**"alone grants access to every path including absolute paths — required when handling OS file-picker results that live outside the app root.- Paths are traversal-safe:
data/../secrets.txtis normalised tosecrets.txtbefore matching...cannot escape a granted glob. - A denied call throws with the exact path and the config key to fix.
delete scoping
| Config | Behaviour |
|---|---|
delete omitted | Falls back to write globs |
delete: ['data/trash/**'] | Only data/trash/ subtree is deletable |
delete: [] | Writes allowed per write, but deletion denied everywhere |
Example
capabilities: {
fs: {
read: ['**'], // read anywhere (needed for file picker)
write: ['documents/**'],
delete: ['documents/trash/**'],
},
}db — SQLite database
db?: booleanUnlocks the db API: db.query, db.execute, db.transaction, db.migrate, and the @glyx-dev/drizzle ORM adapter.
The database file lives in the app's data directory (platform-standard: %APPDATA% on Windows, ~/Library/Application Support on macOS, ~/.local/share on Linux). The path is accessible via system.appDataDir() when system: true is also declared.
network — HTTP and WebSocket
network?: {
allow?: string[] // hostname allowlist; '*' permits all outbound
}Unlocks fetch (global), WebSocket, and network.request. The allow list is an exact-hostname allowlist (case-insensitive, no port):
network: { allow: ['api.myservice.com', 'cdn.myservice.com'] }- Subdomains are not implied — list them explicitly.
'*'allows all outbound connections.- Omitting
allow(or setting[]) blocks all outbound network calls. - WebSocket connections are checked against the same list by hostname.
dialog — File and folder dialogs
dialog?: booleanUnlocks dialog.openFile, dialog.openFolder, dialog.saveFile, dialog.message, dialog.confirm. Returns paths chosen by the user; combine with fs to read/write those paths.
clipboard — System clipboard
clipboard?: booleanUnlocks clipboard.readText() and clipboard.writeText(string).
Image clipboard read/write is not yet supported.
notification — Desktop notifications
notification?: booleanUnlocks notification.send({ title, body, icon? }).
Platform notes:
- macOS — requires an app bundle (
.app). Notifications do not appear when running the raw binary in dev mode. Useglyx dev --bundleto test. - Windows — shown via the Windows notification centre; no extra setup needed.
- Linux — delegates to
libnotify/ the D-Bus notification daemon.
tray — System tray icons
tray?: booleanUnlocks tray.create() / tray.destroy() / tray.pollEvents() and the tray context menu API.
Platform notes:
- Windows — tray icons appear in the system notification area.
- macOS — tray icons appear in the macOS menu bar.
- Linux — requires
libayatana-appindicator3-1(orlibappindicator3-1) installed. On GNOME, install the AppIndicator extension (opens in a new tab).
Requires a running winit event loop (Glyx provides this automatically).
credentials — OS keychain
credentials?: booleanUnlocks credentials.set(key, value), credentials.get(key), credentials.delete(key). Backed by:
- Windows — Windows Credential Manager (DPAPI-encrypted)
- macOS — Keychain
- Linux — libsecret / Secret Service
Data is encrypted by the OS and tied to the logged-in user. Never stored as plaintext on disk. Survives app restarts and updates.
See also @glyx-dev/keychain for a typed, async wrapper.
deeplink — Custom URL scheme
deeplink?: {
scheme: string // e.g. 'myapp' → handles myapp:// URLs
singleInstance?: boolean // focus existing window instead of opening a new one (default: false)
}Unlocks deeplink.onOpen(handler). The handler receives the full URL string whenever the OS activates the app via the registered scheme.
Registration happens automatically at first launch (user-scoped, no admin rights needed on all platforms). On macOS the scheme is also declared in Info.plist during packaging.
capabilities: {
deeplink: { scheme: 'myapp', singleInstance: true },
}import { deeplink } from '@glyx-dev/react'
deeplink.onOpen((url) => {
console.log('Opened via:', url) // e.g. 'myapp://auth/callback?token=...'
})audio — Audio playback
audio?: booleanUnlocks the audio API. audio.play(src, opts?) starts playback and returns a
player handle for all subsequent control:
handle.pause()/handle.resume()/handle.play()handle.stop()— stop and release the sinkhandle.seek(secs),handle.setVolume(0–1),handle.getVolume()handle.getTime(),handle.getDuration()(total duration;-1if unknown)handle.onEnded(cb)— fires when playback finishes naturally
Plays audio files from disk (MP3/FLAC/OGG/WAV) via the OS audio stack. See Audio for examples.
For microphone input, also declare microphone: true.
microphone — Microphone input
microphone?: booleanRequires audio: true to also be declared. Unlocks the microphone API for one-shot
WAV recording via the OS audio input (cpal + hound):
microphone.listDevices()→Promise<{ name: string }[]>— connected input devicesmicrophone.record(durationMs = 3000, deviceName = null)→Promise<string>— records fordurationMsmilliseconds and resolves with the absolute path to a.wavfile
const devices = await microphone.listDevices()
const wavPath = await microphone.record(5000) // 5s from the default deviceOn macOS and Windows the OS may show a permission prompt on first use.
video — Video playback
video?: booleanUnlocks video.load(path | url), video.play(), video.pause(), video.seek(seconds), video.setVolume(0–1), and the <Video> component in @glyx-dev/react.
camera — Camera capture
camera?: booleanUnlocks camera.enumerate() (list devices), camera.open(deviceId), camera.captureFrame(), camera.startStream(handler), camera.close(). On macOS and Windows the OS shows a permission prompt on first use.
webview — Native embedded webview
webview?: booleanUnlocks the <WebView> component in @glyx-dev/react — a real native OS
webview (WebView2 / WKWebView / WebKitGTK) embedded as a child window,
position-tracked to the component's layout rect. Use it for content that
genuinely needs a browser engine: an OAuth login page, a third-party embed, an
in-app browser panel. It does not replace or affect Glyx's own GPU-rendered
UI — everything else in your app keeps using the native renderer.
import { WebView } from '@glyx-dev/react'
<WebView src="https://example.com" style={{ flex: 1 }} />Per-instance options (not global config): sandbox (default true, disables
devtools), allowedOrigins (navigation allowlist — defaults to the initial
src's own origin if unset), assetsRoot (serves local files through a
glyx-asset:// scheme scoped to that directory, instead of raw file://),
and a two-way postMessage bridge (onMessage prop + ref.postMessage()).
See webview for the full API.
On integrated GPUs the rest of your UI may run on the CPU/software-present backend (see Rendering) — the embedded webview is unaffected either way, since the OS composites it independently as a real child window.
ai — On-device AI
ai?: booleanUnlocks the ai API:
ai.embed(text)→Float32Array— sentence embeddingai.generate({ prompt, model?, maxTokens? })→AsyncIterable<string>— local LLM text generationai.transcribe(audioPath)→string— Whisper speech-to-text
Models are downloaded on first use to the app data directory. No API key or network connection required after download. See Local AI for model selection and bundle size guidance.
updater — Auto-updater
updater?: booleanUnlocks updater.check(currentVersion), updater.update(currentVersion),
updater.getVersion(), updater.getPlatform(), updater.checkManifest(url, currentVersion?),
and updater.downloadJs(url, sigHex). The GitHub-release target (owner/repo/binName)
is separate app metadata, not part of this capability — see the
updater config block. Full reference:
updater API.
crash — Crash reporting
crash?: booleanUnlocks crash.getReports() — returns every persisted crash report
(JS and Rust panics) as an array — and crash.clearReports(). Reports are
written to ~/.glyx/crashes/ automatically as they happen; there's no
separate "last report" query, getReports() returns all of them. Rust
panics are always written regardless of this capability; the flag only
gates the JS-facing read/clear API and JS-side error capture.
system — OS and display info
system?: booleanUnlocks the system API:
system.getPlatform()→'windows' | 'macos' | 'linux'system.getOsVersion()→stringsystem.getDisplays()→ display geometry arraysystem.appDataDir()→ platform app-data pathsystem.isDarkMode()/system.watchDarkMode(handler)system.getLocale()→ BCP-47 locale stringsystem.getMemoryInfo()→ total/available RAM
battery — Battery status
battery?: booleanUnlocks system.battery() → { level: number, charging: boolean, chargeRate?: number }. Returns null on desktop systems without a battery.
storage — App data directory
storage?: booleanUnlocks system.appDataDir() and system.storageStat() (disk usage for the app's data directory). The path is also readable via fs once granted — storage just exposes the path string without requiring a broad fs grant.
power — Sleep and wake events
power?: booleanUnlocks system.onSleep(handler) and system.onWake(handler). Useful for pausing background work or flushing state before the system sleeps.
shell — Open external URLs
shell?: booleanEnables glyxWindow.openExternal(url) — opens an http://, https://, or mailto:
URI in the OS default browser / mail client. The URI is scheme-validated (web and
mailto only); it never spawns a shell, interprets shell metacharacters, or runs an
arbitrary executable. This is the Glyx equivalent of Electron's shell.openExternal().
import { glyxWindow } from '@glyx-dev/react'
glyxWindow.openExternal('https://glyx.dev/docs')
glyxWindow.openExternal('mailto:support@example.com')openExternal throws at runtime unless the shell capability is declared.
shellExec — Scoped shell execution
shellExec?: { allow: string[] }Runs a declared binary and waits for it to complete. Not the same capability
as shell above — shell only opens URLs; shellExec spawns arbitrary
declared executables with arbitrary arguments and is a meaningfully higher-trust
capability.
export default defineConfig({
capabilities: {
shellExec: { allow: ['git', 'ffmpeg'] },
},
})import { shell } from '@glyx-dev/react'
const { stdout, exitCode } = await shell.run('git', ['status', '--porcelain'])bin must exactly match an entry in allow — no globs, no prefix
matching, no PATH search (the binary is resolved to an explicit path before
spawning, closing PATH-hijacking on shared machines). Arguments are always
passed as a real argv array via the OS process API — never through a shell
interpreter (sh -c / cmd /c) — which is the actual injection defense;
metacharacter rejection on top of that is defense-in-depth, not the primary
guard. Every call has a 30s timeout and an 8 MiB per-stream output cap.
shell.run throws unless bin is listed in shellExec.allow. There is no
wildcard — every binary the app needs must be declared individually.
Streaming execution (shell.spawn/shell.poll) is designed but not yet
implemented. shell.run buffers all output and resolves once the process
exits — fine for short commands, not for long-running ones like an ffmpeg
transcode where you want progress as it happens. Do not write code that
depends on shell.spawn today.
shellAgent — Open-ended shell execution
shellAgent?: { scopeDir: string }For apps that can't enumerate which binaries they'll need ahead of time — an
AI coding assistant is the canonical example, since it decides what to run at
runtime based on whatever project the user points it at. No binary allowlist;
any command runs. The guardrail moves from which binaries to blast
radius: every spawned process's working directory is hard-scoped to
scopeDir (canonicalized; ../absolute-path escapes are rejected before
spawn, not just discouraged).
export default defineConfig({
capabilities: {
shellAgent: { scopeDir: './workspace' },
},
})This is a materially higher trust level than shellExec and is meant to
require a deliberate, loud opt-in rather than a boolean flip.
Not yet implemented. The capability declaration and cwd-scoping
enforcement exist in glyx-security, but the JS-facing binding and — more
importantly — the native, JS-independent activity overlay that shows every
command as it runs are not built yet. That overlay is a hard requirement
for this capability to ship, not an optional polish pass: without an
unsuppressable view of what's actually executing, shellAgent would just
be a trust-me boolean with no runtime accountability. Do not declare
shellAgent in a real app yet.
globalShortcuts — OS-level keyboard shortcuts
globalShortcuts?: booleanUnlocks globalShortcuts.register(accelerator, handler) and globalShortcuts.unregister(accelerator). Accelerators use Electron-style strings: 'CommandOrControl+Shift+P', 'F12', etc. Shortcuts fire even when the app window is not focused.
gamepads — Gamepad and controller input
gamepads?: booleanUnlocks system.gamepads.enumerate(), system.gamepads.onConnect(handler), system.gamepads.onDisconnect(handler), and per-frame system.gamepads.getState(id) → axes and buttons.
usb — USB device enumeration
usb?: booleanUnlocks usb.enumerate() → device list (vendor ID, product ID, manufacturer, serial). Read-only enumeration — does not grant raw USB transfer access (use hid for HID-class devices).
hid — HID device access
hid?: booleanUnlocks hid.enumerate(), hid.open(deviceId), hid.read(), hid.write(data), hid.close(). Suitable for joysticks, custom peripherals, and other HID-class USB/Bluetooth devices.
mdns — mDNS service discovery
mdns?: booleanUnlocks network.mdns.browse(serviceType, handler) and network.mdns.advertise({ name, type, port, txt? }). Useful for LAN service discovery without a central server.
perf — Performance diagnostics
Unlike every other entry on this page, perf is not an enforced
capability — there's no perf field on the runtime's Capabilities
struct, and perf.snapshot() has no gating check. The perf API is
always available regardless of what's declared in glyx.config.json.
Declaring perf: true in config is a no-op.
The perf API (import { perf } from '@glyx-dev/react') exposes:
perf.snapshot()— synchronous{ fps, frameTime, frameTimeP99, jsTime, layoutTime, gpuTime, memoryJS, nodeCount }perf.onBudgetExceeded(cb, { target? })— fires when a frame exceedstargetms (default 16.667ms / 60fps)perf.onLeakDetected(cb)— dev-build-only node-count leak warnings
See APIs → perf.
Checking capabilities at runtime
import { glyx } from '@glyx-dev/react'
if (glyx.hasCapability('camera')) {
// camera features available
}Useful when building components that degrade gracefully when a capability is not declared (e.g. an image picker that falls back to a URL input if dialog is absent).
Principle of least privilege
Each declared capability is a commitment that your app uses that API:
- Fewer capabilities = smaller attack surface
- The capability list is embedded in the binary
- Future versions may surface it to end users as a permission summary before install